From 4d933ed959cd9cf2965879991cd07f2636eacb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20ICH=C3=89?= <4037271+peeweek@users.noreply.github.com> Date: Fri, 20 Aug 2021 15:49:13 +0200 Subject: [PATCH 001/325] Added Settings for Color scheme --- apps.json | 3 +- apps/hcclock/ChangeLog | 2 +- apps/hcclock/hcclock.app.js | 47 +++++++++++++++++++++++++++----- apps/hcclock/hcclock.settings.js | 32 ++++++++++++++++++++++ core | 2 +- 5 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 apps/hcclock/hcclock.settings.js diff --git a/apps.json b/apps.json index 2b28509cf..a9a6d736d 100644 --- a/apps.json +++ b/apps.json @@ -3390,13 +3390,14 @@ { "id": "hcclock", "name": "Hi-Contrast Clock", "icon": "hcclock-icon.png", - "version":"0.01", + "version":"0.02", "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", "tags": "clock", "type":"clock", "allow_emulator":true, "storage": [ {"name":"hcclock.app.js","url":"hcclock.app.js"}, + {"name":"hcclock.settings.js","url":"hcclock.settings.js"}, {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} ] }, diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 0ca30d066..343be7f07 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,2 +1,2 @@ 0.01: base code - +0.02: added settings for color schemes \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index 98abbc6f3..4664dd763 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -174,19 +174,52 @@ function fmtDate(day,month,year,hour) let ap = "(AM)"; if(hour == 0 || hour > 12) ap = "(PM)"; - return months[month] + " " + day + " " + year + " "+ ap; + return months[month] + " " + day + " " + year + " "+ ap; } else return months[month] + ". " + day + " " + year; } -// Handles Flipping colors, then refreshes the UI + +////////////////////////////////////////// +// +// HANDLE COLORS + SETTINGS +// + +function getColorScheme() +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + if (!("scheme" in settings)) { + settings.scheme = 0; + } + return settings.scheme; +} + +function setColorScheme(value) +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + settings.scheme = value; + require('Storage').writeJSON('hcclock.json', settings); + + if(value == 0) // White + { + bg = 255; + fg = 0; + } + else // Black + { + bg = 0; + fg = 255; + } + redraw(); +} + function flipColors() { - let t = bg; - bg = fg; - fg = t; - redraw(); + if(getColorScheme() == 0) + setColorScheme(1); + else + setColorScheme(0); } ////////////////////////////////////////// @@ -197,7 +230,7 @@ function flipColors() // Initialize g.clear(); Bangle.loadWidgets(); -redraw(); +setColorScheme(getColorScheme()); // Define Refresh Interval setInterval(updateTime, interval); diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js new file mode 100644 index 000000000..34d6d191a --- /dev/null +++ b/apps/hcclock/hcclock.settings.js @@ -0,0 +1,32 @@ +(function(back) { + + function getColorScheme() + { + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + if (!("scheme" in settings)) { + settings.scheme = 0; + } + return settings.scheme; + } + function setColorScheme(value) + { + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + settings.scheme = value? 1 : 0; + require('Storage').writeJSON('hcclock.json', settings); + } + function setIcon(visible) { + updateSetting('showIcon', visible); + + } + var mainmenu = { + "" : { "title" : "Hi-Contrast Clock" }, + "Color Scheme" : { + value: getColorScheme, + format: v => v == 0?"White":"Black", + onchange: setColorScheme + }, + "< Back" : back, + }; + E.showMenu(mainmenu); + }) + \ No newline at end of file diff --git a/core b/core index 27f9a7125..8d9a012d6 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 27f9a7125146a38c4357d679ec783f6e98a983c6 +Subproject commit 8d9a012d62d40aae1b2304d0149440fb3c022393 From 183a36f5acac51c184d002e838a4642179414680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20ICH=C3=89?= <4037271+peeweek@users.noreply.github.com> Date: Fri, 20 Aug 2021 15:54:32 +0200 Subject: [PATCH 002/325] Added Cycling for Values in Color Schemes --- apps/hcclock/hcclock.settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js index 34d6d191a..92d5f47e5 100644 --- a/apps/hcclock/hcclock.settings.js +++ b/apps/hcclock/hcclock.settings.js @@ -10,6 +10,7 @@ } function setColorScheme(value) { + value = value + 1 % 2; let settings = require('Storage').readJSON("hcclock.json", true) || {}; settings.scheme = value? 1 : 0; require('Storage').writeJSON('hcclock.json', settings); From 902ea72ddedecf2abce4ea7c2111532108c2cdb5 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Mon, 4 Oct 2021 18:10:29 +0200 Subject: [PATCH 003/325] Set theme jekyll-theme-minimal --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..2f7efbeab --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file From 1ab8fbabffba72f92008fbd991bef9d7ac7e9b6a Mon Sep 17 00:00:00 2001 From: Vingelar Date: Mon, 4 Oct 2021 18:13:37 +0200 Subject: [PATCH 004/325] Create app.js --- apps/TheBinWatch/app.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/TheBinWatch/app.js diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js new file mode 100644 index 000000000..fd538074f --- /dev/null +++ b/apps/TheBinWatch/app.js @@ -0,0 +1 @@ +E.showMessage("My\nSimple\nApp","My App") From 2e34b337a4a1139942633b5f61e9536330356d44 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Mon, 4 Oct 2021 18:23:05 +0200 Subject: [PATCH 005/325] Create app-icon.js --- apps/TheBinWatch/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/TheBinWatch/app-icon.js diff --git a/apps/TheBinWatch/app-icon.js b/apps/TheBinWatch/app-icon.js new file mode 100644 index 000000000..bb87c1926 --- /dev/null +++ b/apps/TheBinWatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkGAoMM7oAQoMRiIXBrnMACHAGofMn//AB385gWCgvM/85nQTJ+RCBiMj5lQLwXD/+YpIXJmH/+MRj5ID5nf/+B/IXJ+EikIXBnhIBIwX/nGDLhUzAAM//oXBhnEOp4ADJANc54JF+MjFAUzmUfBoss4qlH5kIxAACwHNVI5eCAAfz5kBC4cREwwOBC4wgB6AXDhnMKwwXHlnM4pICwFc5jNBC5gHBMAcgAwQXMWAIABqAXBggGCcwIXKcAIAB4EowJeBAAJgFC4wPC5nEhGAAwYQHSwwACiUkAwipEM44APC4nziS7DxHz/4GE/4wDC4n8RQQAC/nfAwn9PIYXElnAjAPCx884f4Awf8bQYXEaYmIn4GB+YXD7gSFagkQLwaKCAwraCC4bUC4APBwf8aYU4AwoXFaYg/DAwJgBMoLaEAYTUEgSeBdYhlDbQYKCHARIDAwnPUoIGEC4c97oAC6MvAwnTQgIGFU4gASC4SDBWoc/aYeIwUjAwsvC4QJEn/NAwmAbQgGBPoQzBBIfyaYmIgoGFiBfEBATNCaYQABhgGFh4XEIITUC4YGCwFcbQYGBj4XEKQTUDAwUQbQgXBR4hIDagdPAwMEAwXfAwMCC404x88cgf4wMcAwRPBwJeB+YHBn5gCSwQAC/ClBAAfzUoP/H4Nc54wC+fUogABpv/kQGEn8iCAM84sM4j/MAA3M4DJBA4cRn8/+UiE4IDBkcj+YOC+nMqEAQAIICmcin/zCgIXBl8zC4heBgEAhnDBAQSBl/ymfyAgIfBDYIOC7nAC4RIDNQXzl8j/8yDgMvQwZeBC4JIBVIQANIwYABri1EABnFC4ZIBACBGC")) From 74a448a7156b1adf2068c98e092274d8c34c948a Mon Sep 17 00:00:00 2001 From: Vingelar Date: Mon, 4 Oct 2021 18:28:03 +0200 Subject: [PATCH 006/325] Update apps.json --- apps.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps.json b/apps.json index 83690cf3e..fb44dda2f 100644 --- a/apps.json +++ b/apps.json @@ -3540,5 +3540,17 @@ {"name":"waveclk.app.js","url":"app.js"}, {"name":"waveclk.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "TheBinWatch", + "name": "Binary Watch", + "shortName":"BinWatch", + "icon": "app.png", + "version":"0.01", + "description": "Famous binary watch", + "tags": "", + "storage": [ + {"name":"binwatch.app.js","url":"app.js"}, + {"name":"binwatch.img","url":"app-icon.js","evaluate":true} + ] } ] From 0cfee929d2e63eba8406e78e051fa44e15a3876b Mon Sep 17 00:00:00 2001 From: Vingelar Date: Mon, 4 Oct 2021 18:51:48 +0200 Subject: [PATCH 007/325] Update app.js --- apps/TheBinWatch/app.js | 265 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 1 deletion(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index fd538074f..c395c6684 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -1 +1,264 @@ -E.showMessage("My\nSimple\nApp","My App") +/*************************************************** +* BINARY WATCH +* for Bangle 1 / 2 +* inspired by RAL tec binary wrist watch +* +* TODO: +* - vibrate on full hour +* - +****************************************************/ + +/* reuqirements */ +var locale = require("locale"); +require("Font5x9Numeric7Seg").add(Graphics); + + +/* constants and definitions */ +/* Bangle 2: 176 x 176 */ + +/* +var x_step = 26; +var y_step = 34; + +var TIME_Y_OFFSET = 30; +var HX = 35, HY = 0 + TIME_Y_OFFSET; +var MX = 10, MY = 40 + TIME_Y_OFFSET; +var SX = 10, SY = 80 + TIME_Y_OFFSET; +var BT_X = 30, BT_Y = 10; +var DX = 160, DY = 148; +*/ +/* Bangle 1: 240 x 240 */ + +var x_step = 35; +var y_step = 46; + +var TIME_Y_OFFSET = 41; +var HX = 48, HY = 0 + TIME_Y_OFFSET; +var MX = 14, MY = 55 + TIME_Y_OFFSET; +var SX = 14, SY = 110 + TIME_Y_OFFSET; +var BT_X = 41, BT_Y = 14; +//var BT_X = 20, BT_Y = 14; +var DX = 160, DY = 205; + +var BAT_POS_X = 175, BAT_POS_Y = 21; +var BAT_SIZE_X = 3, BAT_SIZE_Y = 5; + +/* global variables */ + + +//var screen_size_x = 176; +//var screen_size_y = 176; + +var screen_size_x = 240; +var screen_size_y = 240; + +var showDateTime = 2; /* show noting, time or date */ +var cg = Graphics.createArrayBuffer( + screen_size_x,screen_size_y, 1, {msb:true}); +var cgimg = {width:screen_size_x, height:screen_size_y, bpp:1, + transparent:0, buffer:cg.buffer}; + +/* local functions */ + +/** + * function drawSquare(...) + * + * go through all bits and draw a square if a bit + * is set. So we get the binary representation + * of the value + * used to draw block for hours, mintutes, seconds, date + * + * @param gfx: graphic object to use + * @param x: x-coordinate of 1st the square + * @param y: y-coordinate of 1st the square + * @param data: data conatining the bit information + * @param numOfBits: number of bits to draw +*/ +function drawSquare(gfx, x, y, data, numOfBits) { + + for(i = numOfBits; i > 0 ; i--) { + if( (data & 1) != 0) { + gfx.fillRect(x + (i - 1) * x_step, y, + x + i * x_step , y + y_step); + } + data >>= 1; /* shift one bit right */ + } +} + +/** + * function drawBinary(...) + * draw the time in binary format + * default display for geeks and real men + + * @param h: hours + * @param m: minutes + * @param s: seconds +*/ +function drawBinary(gfx, hour, minute, second) { + gfx.clear(1); + gfx.setColor(1); + gfx.fillRect(0, 0, screen_size_x, screen_size_y); + gfx.setColor(0); + + if(hour > 12) { + hour -= 12; /* we use for bit for hours so we only display 12 hours*/ + } + drawSquare(gfx, HX, HY, hour, 4); /* set hour */ + drawSquare(gfx, MX, MY, minute, 6); /* set minute */ + drawSquare(gfx, SX, SY, second, 6); /* set second */ +} + +/** + * function drawTime(...) + * show time under the graphic + * for wimps and commies + * + * @param h: hours + * @param m: minutes + * @param s: seconds +*/ + +function drawTime(gfx, h, m, s) { + var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2)+ ":" + ("0"+s).substr(-2); + + gfx.setFontAlign(0,-1); // align right bottom + gfx.setFont("5x9Numeric7Seg", 2); + gfx.drawString(time, gfx.getWidth() / 2, DY, false /*clear background*/); + +} + +/** + * function drawDate(...) + * show date under the graphic + * (optionally) + * + * @param gfx: graphic object to use + * @param d: date object +*/ + +function drawDate(gfx, d) { + var dateString = "" + + ("0" + d.getDate()).substr(-2) + " " + + ("0" + d.getMonth()).substr(-2) + " " + + ("0" + d.getFullYear()).substr(-4) + ; + + gfx.setFontAlign(0,-1); // align right bottom + gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ + gfx.drawString(dateString, gfx.getWidth() / 2, DY, false /*clear background*/); + +// gfx.setFont("6x8",2); +// var date = locale.date(d, false); +// gfx.drawString(date, DX, DY, false); +// draw the seconds (2x size 7 segment) +// gfx.setFont("7x11Numeric7Seg",1); +// gfx.drawString(("0"+s).substr(-2), X+30, Y, false /*clear background*/); +} + +function toggleDateTime() { + showDateTime++; + if(showDateTime > 2){ + showDateTime = 0; + } + draw(); +} + +function updateVTime() { + second++; + if(second > 59) { + second = 0; + minute++; + if(minute > 59) { + minute = 0; + hour++; + if(hour > 12) { + hour = 0; + } + } + } +} + +/** + * function drawBattery(...) + * fill the battery symbol with blocks + * according to the battery level + * + * @param gfx: graphic object + * @param level: current battery level +*/ +function drawBattery(gfx, level) { + var pos_x = BAT_POS_X + 5 * (BAT_SIZE_X + 2); + var stepLevel = Math.round((level + 10) / 20); +/* + if(stepLevel < 2) { + gfx.setColor(2); + } else if(stepLevel < 4) { + gfx.setColor(3); + } else { + gfx.setColor(4); + } +*/ + console.log("stepLevel: " + stepLevel); + for(i = 0; i < stepLevel; i++) { + pos_x -= BAT_SIZE_X + 2; + gfx.fillRect(pos_x, BAT_POS_Y, + pos_x + BAT_SIZE_X, BAT_POS_Y + BAT_SIZE_Y); + } +} + +/** + * function drawBattery(...) + * fill the battery symbol with blocks + * according to the battery level + * + * @param gfx: graphic object + * @param level: current battery level +*/ +function drawBT(status) { +} +var hour = 0, minute = 1, second = 50; +var batVLevel = 0; +function draw() { + + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); +// gfx2(hour, minute, second); + drawBinary(cg, h, m, s); + cg.setColor(0); + + switch(showDateTime) { + case 1: +// drawTime(hour, minute, second); + drawTime(cg, h, m, s); + break; + case 2: + drawDate(cg, d); + break; + default: + /* do nothing */ + } + console.log("BatLevel: " + batVLevel); + drawBattery(cg, batVLevel /*E.getBattery()*/); + drawBT(1); + + batVLevel += 2; + if(batVLevel > 100) { + batVLevel = 0; + } + updateVTime(); + g.clear(); + g.drawImages([{image:cgimg}, +// {image:require("Storage").read("Background176_center.png")}, + {image:require("Storage").read("Background240_center.png")}, + { x:BT_X, y:BT_Y, rotate: 0, image:require("Storage").read("bt-icon.png")}, + ]); +} + +g.clear(); +setInterval(draw, 1000); +var x_size = g.getWidth(); +console.log("Startup: X-W = " + x_size); +console.log("BatLevel: " + E.getBattery()); + +setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); + From 9b8a01c23da192bd449a9687dc35a736dd726af3 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 09:52:28 +0200 Subject: [PATCH 008/325] Added backgrounds for V1 and V2 + Bluetooth symbol --- apps/TheBinWatch/Background176_center.png | Bin 0 -> 4158 bytes apps/TheBinWatch/Background240_center.png | Bin 0 -> 6492 bytes apps/TheBinWatch/bt-icon.png | Bin 0 -> 628 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/TheBinWatch/Background176_center.png create mode 100644 apps/TheBinWatch/Background240_center.png create mode 100644 apps/TheBinWatch/bt-icon.png diff --git a/apps/TheBinWatch/Background176_center.png b/apps/TheBinWatch/Background176_center.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8ecee53dae7bcf0f81fa2e42d8e3344317bc31 GIT binary patch literal 4158 zcma)9c~}!?8qdWb$3y7E3|f0G9PGmTG`f7PLj_g6-0-tJp3(A;%1D|L8nVj`_XU?|r}b zyQ-v;B&IvZokSurMZyFb_S<{_VPcitZ zAo|}w-Tv`hm0|p*=+-455rAERlN8Dx{no>$^0##i&0tF85}7286hASTi@mt1gxmL% zNOYFhU4$z?dqNLCh57~Yz|>h3Yx@&!1j_Qdsm8b8A| zC(}U>SV4I0VJH zNG}A|jlqK5Hcu3;xbQa+W8M9$LL~?YJV);I_xFRi>IG`ziX+{vcm*)~T?mzZac-l} z&j33V5aA>#Y-_X|ehi{IhF#b#myaFN(RQg5%x;n>$52q%3?wb{{cciB^(z7gJ)~ZC z6`)q*@esd_ZP_l>w6ZAs2qN_o_A$~}YrmVl5fF7bZG9Cpj>L;O?VygtZ&6Nto1>u^ z{-Q$DV(o)%MQ`-wkf|NydRn82fKCY~;E@epUgEH@u+XruB-s%NOdo!3(%xAJNk@|* zg5tLIRO${2wby$&W9+KgeAjH2>ThC$nkWIhB{$~hO+8l&h9%_i_< zw>4sk>wMGMw3b6q@Y!%kg>XX6=0!)Ao8$tJk*rEi?zEL3A-z$R!HjlI+DE1KdY0_} zlaa5_U@l9OVUH~ug{$BBzCR-L@&>n0GFsy=M0EdB0mPa+2uTt0G4xBEd13gKK6g zdq`dp9268~W7v}q1-5M(U?ii|J#ih3Qtk2DFNu=Brq|LQEMmIV(sTu_4$3wot!GfE zUlnr=l}=AzU>4by9UPN^2vSw=0?&n${DdCX>mwy!8}9 z#Wm3s#?Z z4gHlU6Ju=H=Y|b8u(5)WV{YsE%o++=hzhnSr6-UQ!nou@2N0@NaiYq z2*H%}$8D#$ctQw-g`n|!J#SnFj)!cHz-wcbP{~qM@Hyr#JCYD+Uh>SK(WH@9VOb5{ zW^8j#8Fmw+NVC-q3~{tY!|OQvKk^@b9(>tS7;IALb^uqa1Ds6+M}UVctSGWE zsoD3aWMwg`aIPmagtGP6BnnFKQ|hoi)dNv@2X^!Y?%Y8VQ9*Ir{e$f2(X!=sqIU4CRQ6EvzD9 zERG)25%kf^h#;uHWc7Kg+4mEo&w03@{od5@_`e8!f`XWjq|C;}JA3eMZ4j+AIa~dY zOdp~S=CO;)lN@Ml8R>93K|!VZyn1_6MKlcBUxC_e~kBG9B>hZ8G39hU%3o8xSnd01I;DQ&&9u0eZJxS$7 z@4kZ+XB=?ddmE=Ssr7qGuN#afeGv$|ullCh z_Z-8t@h(0SC!>s|)75dRj;>On2Q^i>pnlU2jI`wq5?=Gb1D9WmPW1e8Sg4e)^s-E9 zZ}Fp@tl{aDUYq3pqhbH+=Gias1+Q7WZ+v0f~q%#8)%! zirzcztIiGRz%uL*WjjK_D?@0{=-A$OP~W9+eN%JHI~dPpZ&!Hy&n|`1`dRQL3oDo! z@fqxFQ%cxT4VNKO4R)12i91*gxX`>qoR*XpFLYiQt2$C1cTnhWbgX?8?ug#|%G-u2 zCg)0d>_JGowibKr2ObKyiB-nGwLNYmE3T3u6B;m&j%|t(HX{?Ta(uGl(04X`bNP(P zM4vyIp`j^SI_l|z?UJ+Lp~l54E?ZB3$+5O06dGzuso?J1wJYY*MW3&3RnN>M?`z_- zm$1L7mdZM>?95$Cju4?UnJ%_yVJjMM)#MA=uV8lwZ#z`hD9MQT z+>w`;XQvd_-zY(~!55s4U}Z@CW>gzQM9})a0%Zp7x*_i?08k%JaCm{0kw(nsy=RLZ z!Qo+!;8j%1BX}9UA$T>z?1ja)g>Fzj^Wdnz*Sk4wMxn%fR50}t0l8;I0&y=5vu7ez zI1zYsb5b^{EjeJWTI)y>9(t0r9Tg1gmU8S>Zg{*ckx-b1#}mm5UQ4CK*BM}2p0K>4 w!Zwf%Gj%)i@PG|7!P$gYc=)cEsWsxksifxV!UIED&*pC>qC`nTGb~5{4@KTT(f|Me literal 0 HcmV?d00001 diff --git a/apps/TheBinWatch/Background240_center.png b/apps/TheBinWatch/Background240_center.png new file mode 100644 index 0000000000000000000000000000000000000000..6fa35f93fc6c5e92b98f781d6d42251f370eb8a8 GIT binary patch literal 6492 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 Q2>KnnWX0l>+zk1D04_NZ`v3p{ literal 0 HcmV?d00001 diff --git a/apps/TheBinWatch/bt-icon.png b/apps/TheBinWatch/bt-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c07800ea34b2935ba689265237bf2ccbc4fced55 GIT binary patch literal 628 zcmV-)0*n2LP)C}z~&b@7#oSU1p zg(Mbn7FO^8ayo+?Z-@cQLag!o1eS0gsx%Ei1s)H>I*swX*GEUl@rZfL!Cy{9VVO~>MAT{}{W{1Ek>m7dA>3zFE)&)3t_F;h zzfx-`k7yyxOll^XnUEolQT__b+4RQv1W3&c=3}tABfdGooGLuI7h{~6HNX&$J1AmR z8uGu4=c&xh+3^11-hFF;yn~DY!%}G O0000 Date: Thu, 7 Oct 2021 10:05:18 +0200 Subject: [PATCH 009/325] support V1 and V2 recognize the device we are running on, load related background image and set fitting values --- apps/TheBinWatch/app.js | 184 +++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 38 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index c395c6684..7e7c67966 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -26,22 +26,73 @@ var MX = 10, MY = 40 + TIME_Y_OFFSET; var SX = 10, SY = 80 + TIME_Y_OFFSET; var BT_X = 30, BT_Y = 10; var DX = 160, DY = 148; +var screen_size_x = 176; +var screen_size_y = 176; */ + +const V2_X_STEP = 26; +const V2_Y_STEP = 34; + +const V2_TIME_Y_OFFSET = 30; +const V2_HX = 35; +const V2_HY = 0 + V2_TIME_Y_OFFSET; +const V2_MX = 10; +const V2_MY = 40 + V2_TIME_Y_OFFSET; +const V2_SX = 10; +const V2_SY = 80 + V2_TIME_Y_OFFSET; +const V2_BT_X = 30; +const V2_BT_Y = 10; +const V2_DX = 160; +const V2_DY = 148; + +const V2_BAT_POS_X = 150; +const V2_BAT_POS_Y = 16; +const V2_BAT_SIZE_X = 2; +const V2_BAT_SIZE_Y = 4; +const V2_SCREEN_SIZE_X = 176; +const V2_SCREEN_SIZE_Y = 176; +const V2_BACKGROUND_IMAGE = "Background176_center.png"; + /* Bangle 1: 240 x 240 */ -var x_step = 35; -var y_step = 46; +const V1_X_STEP = 35; +const V1_Y_STEP = 46; -var TIME_Y_OFFSET = 41; -var HX = 48, HY = 0 + TIME_Y_OFFSET; -var MX = 14, MY = 55 + TIME_Y_OFFSET; -var SX = 14, SY = 110 + TIME_Y_OFFSET; -var BT_X = 41, BT_Y = 14; +const V1_TIME_Y_OFFSET = 41; +const V1_HX = 48; +const V1_HY = 0 + V1_TIME_Y_OFFSET; +const V1_MX = 14; +const V1_MY = 55 + V1_TIME_Y_OFFSET; +const V1_SX = 14; +const V1_SY = 110 + V1_TIME_Y_OFFSET; +const V1_BT_X = 41; +const V1_BT_Y = 14; //var BT_X = 20, BT_Y = 14; -var DX = 160, DY = 205; +const V1_DX = 160; +const V1_DY = 205; -var BAT_POS_X = 175, BAT_POS_Y = 21; -var BAT_SIZE_X = 3, BAT_SIZE_Y = 5; +const V1_BAT_POS_X = 175; +const V1_BAT_POS_Y = 21; +const V1_BAT_SIZE_X = 3; +const V1_BAT_SIZE_Y = 5; +const V1_SCREEN_SIZE_X = 240; +const V1_SCREEN_SIZE_Y = 240; +const V1_BACKGROUND_IMAGE = "Background240_center.png"; +/* runtime settings */ +var x_step = 0; +var y_step = 0; + +var time_y_offset = 0; +var hx = 0, hy = 0; +var mx = 0, my = 0; +var sx = 0, sy = 0; +var bt_x = 0, bt_y = 0; +var dx = 0, dy = 0; + +var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y; +var backgroundImage = ""; +var screen_size_x = 0; +var screen_size_y = 0; /* global variables */ @@ -49,14 +100,10 @@ var BAT_SIZE_X = 3, BAT_SIZE_Y = 5; //var screen_size_x = 176; //var screen_size_y = 176; -var screen_size_x = 240; -var screen_size_y = 240; var showDateTime = 2; /* show noting, time or date */ -var cg = Graphics.createArrayBuffer( - screen_size_x,screen_size_y, 1, {msb:true}); -var cgimg = {width:screen_size_x, height:screen_size_y, bpp:1, - transparent:0, buffer:cg.buffer}; +var cg; +var cgimg; /* local functions */ @@ -103,9 +150,9 @@ function drawBinary(gfx, hour, minute, second) { if(hour > 12) { hour -= 12; /* we use for bit for hours so we only display 12 hours*/ } - drawSquare(gfx, HX, HY, hour, 4); /* set hour */ - drawSquare(gfx, MX, MY, minute, 6); /* set minute */ - drawSquare(gfx, SX, SY, second, 6); /* set second */ + drawSquare(gfx, hx, hy, hour, 4); /* set hour */ + drawSquare(gfx, mx, my, minute, 6); /* set minute */ + drawSquare(gfx, sx, sy, second, 6); /* set second */ } /** @@ -145,7 +192,7 @@ function drawDate(gfx, d) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ - gfx.drawString(dateString, gfx.getWidth() / 2, DY, false /*clear background*/); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /*clear background*/); // gfx.setFont("6x8",2); // var date = locale.date(d, false); @@ -187,7 +234,7 @@ function updateVTime() { * @param level: current battery level */ function drawBattery(gfx, level) { - var pos_x = BAT_POS_X + 5 * (BAT_SIZE_X + 2); + var pos_x = bat_pos_x + 5 * (bat_size_x + 2); var stepLevel = Math.round((level + 10) / 20); /* if(stepLevel < 2) { @@ -197,12 +244,11 @@ function drawBattery(gfx, level) { } else { gfx.setColor(4); } -*/ - console.log("stepLevel: " + stepLevel); +*/ for(i = 0; i < stepLevel; i++) { - pos_x -= BAT_SIZE_X + 2; - gfx.fillRect(pos_x, BAT_POS_Y, - pos_x + BAT_SIZE_X, BAT_POS_Y + BAT_SIZE_Y); + pos_x -= bat_size_x + 2; + gfx.fillRect(pos_x, bat_pos_y, + pos_x + bat_size_x, bat_pos_y + bat_size_y); } } @@ -215,11 +261,66 @@ function drawBattery(gfx, level) { * @param level: current battery level */ function drawBT(status) { +} +function setRuntimeValues(resolution) { + if(240 == resolution) { + x_step = V1_X_STEP; + y_step = V1_Y_STEP; + + time_y_offset = V1_TIME_Y_OFFSET; + hx = V1_HX; + hy = V1_HY; + mx = V1_MX; + my = V1_MY; + sx = V1_SX; + sy = V1_SY; + bt_x = V1_BT_X; + bt_y = V1_BT_Y; + dx = V1_DX; + dy = V1_DY; + + screen_size_x = V1_SCREEN_SIZE_X; + screen_size_y = V1_SCREEN_SIZE_Y; + backgroundImage = V1_BACKGROUND_IMAGE; + + // TODO: set battery stuff + } else { + x_step = V2_X_STEP; + y_step = V2_Y_STEP; + + time_y_offset = V2_TIME_Y_OFFSET; + + hx = V2_HX; + hy = V2_HY; + mx = V2_MX; + my = V2_MY; + sx = V2_SX; + sy = V2_SY; + + bt_x = V2_BT_X; + bt_y = V2_BT_Y; + + dx = V2_DX; + dy = V2_DY; + + screen_size_x = V2_SCREEN_SIZE_X; + screen_size_y = V2_SCREEN_SIZE_Y; + backgroundImage = V2_BACKGROUND_IMAGE; + // TODO: set battery stuff + } + cg = Graphics.createArrayBuffer( + screen_size_x,screen_size_y, 1, {msb:true}); + + cgimg = {width:screen_size_x, height:screen_size_y, bpp:1, + transparent:0, buffer:cg.buffer}; + } var hour = 0, minute = 1, second = 50; var batVLevel = 0; + + function draw() { - + if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled var d = new Date(); var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); // gfx2(hour, minute, second); @@ -237,7 +338,6 @@ function draw() { default: /* do nothing */ } - console.log("BatLevel: " + batVLevel); drawBattery(cg, batVLevel /*E.getBattery()*/); drawBT(1); @@ -248,17 +348,25 @@ function draw() { updateVTime(); g.clear(); g.drawImages([{image:cgimg}, -// {image:require("Storage").read("Background176_center.png")}, - {image:require("Storage").read("Background240_center.png")}, - { x:BT_X, y:BT_Y, rotate: 0, image:require("Storage").read("bt-icon.png")}, + {image:require("Storage").read(backgroundImage)}, + { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")}, ]); + const millis = d.getMilliseconds(); + setTimeout(draw, 1000-millis); } -g.clear(); -setInterval(draw, 1000); -var x_size = g.getWidth(); -console.log("Startup: X-W = " + x_size); -console.log("BatLevel: " + E.getBattery()); - +// Show launcher when button pressed +Bangle.setUI("clock"); +setRuntimeValues(g.getWidth()); +Bangle.on("lcdPower", function(on) { + if (on) { + draw(); + } +}); +g.reset().clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +//setInterval(draw, 1000); +//var x_size = g.getWidth(); setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); - +draw(); From c60dd212f9598ab7ebd4a07288e9f2025f9a82b0 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 10:42:25 +0200 Subject: [PATCH 010/325] minor changes fix wrong variable name (DY -> dy) remove unused code --- apps/TheBinWatch/app.js | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 7e7c67966..6c0abc961 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -14,22 +14,9 @@ require("Font5x9Numeric7Seg").add(Graphics); /* constants and definitions */ + /* Bangle 2: 176 x 176 */ -/* -var x_step = 26; -var y_step = 34; - -var TIME_Y_OFFSET = 30; -var HX = 35, HY = 0 + TIME_Y_OFFSET; -var MX = 10, MY = 40 + TIME_Y_OFFSET; -var SX = 10, SY = 80 + TIME_Y_OFFSET; -var BT_X = 30, BT_Y = 10; -var DX = 160, DY = 148; -var screen_size_x = 176; -var screen_size_y = 176; -*/ - const V2_X_STEP = 26; const V2_Y_STEP = 34; @@ -78,7 +65,9 @@ const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; const V1_BACKGROUND_IMAGE = "Background240_center.png"; + /* runtime settings */ + var x_step = 0; var y_step = 0; @@ -96,11 +85,6 @@ var screen_size_y = 0; /* global variables */ - -//var screen_size_x = 176; -//var screen_size_y = 176; - - var showDateTime = 2; /* show noting, time or date */ var cg; var cgimg; @@ -170,8 +154,7 @@ function drawTime(gfx, h, m, s) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg", 2); - gfx.drawString(time, gfx.getWidth() / 2, DY, false /*clear background*/); - + gfx.drawString(time, gfx.getWidth() / 2, dy, false /*clear background*/); } /** @@ -187,19 +170,11 @@ function drawDate(gfx, d) { var dateString = "" + ("0" + d.getDate()).substr(-2) + " " + ("0" + d.getMonth()).substr(-2) + " " - + ("0" + d.getFullYear()).substr(-4) - ; + + ("0" + d.getFullYear()).substr(-4); gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ - gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /*clear background*/); - -// gfx.setFont("6x8",2); -// var date = locale.date(d, false); -// gfx.drawString(date, DX, DY, false); -// draw the seconds (2x size 7 segment) -// gfx.setFont("7x11Numeric7Seg",1); -// gfx.drawString(("0"+s).substr(-2), X+30, Y, false /*clear background*/); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } function toggleDateTime() { @@ -323,13 +298,12 @@ function draw() { if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled var d = new Date(); var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); -// gfx2(hour, minute, second); + drawBinary(cg, h, m, s); cg.setColor(0); switch(showDateTime) { case 1: -// drawTime(hour, minute, second); drawTime(cg, h, m, s); break; case 2: From ebda183d240192019cc3995cdc55460271c51d6a Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 11:06:23 +0200 Subject: [PATCH 011/325] adjusted battery level display settings --- apps/TheBinWatch/app.js | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 6c0abc961..8cb8669f6 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -32,13 +32,15 @@ const V2_BT_Y = 10; const V2_DX = 160; const V2_DY = 148; -const V2_BAT_POS_X = 150; -const V2_BAT_POS_Y = 16; +const V2_BAT_POS_X = 127; +const V2_BAT_POS_Y = 15; const V2_BAT_SIZE_X = 2; -const V2_BAT_SIZE_Y = 4; +const V2_BAT_SIZE_Y = 3; const V2_SCREEN_SIZE_X = 176; const V2_SCREEN_SIZE_Y = 176; const V2_BACKGROUND_IMAGE = "Background176_center.png"; +const V2_BG_COLOR = 0; +const V2_FG_COLOR = 1; /* Bangle 1: 240 x 240 */ @@ -65,6 +67,8 @@ const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; const V1_BACKGROUND_IMAGE = "Background240_center.png"; +const V1_BG_COLOR = 1; +const V1_FG_COLOR = 0; /* runtime settings */ @@ -82,6 +86,8 @@ var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y; var backgroundImage = ""; var screen_size_x = 0; var screen_size_y = 0; +var bg_color = 0; +var fg_color = 1; /* global variables */ @@ -127,9 +133,9 @@ function drawSquare(gfx, x, y, data, numOfBits) { */ function drawBinary(gfx, hour, minute, second) { gfx.clear(1); - gfx.setColor(1); + gfx.setColor(bg_color); gfx.fillRect(0, 0, screen_size_x, screen_size_y); - gfx.setColor(0); + gfx.setColor(fg_color); if(hour > 12) { hour -= 12; /* we use for bit for hours so we only display 12 hours*/ @@ -154,6 +160,8 @@ function drawTime(gfx, h, m, s) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg", 2); + gfx.setColor(fg_color); + gfx.drawString(time, gfx.getWidth() / 2, dy, false /*clear background*/); } @@ -174,6 +182,8 @@ function drawDate(gfx, d) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ + gfx.setColor(fg_color); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } @@ -220,6 +230,8 @@ function drawBattery(gfx, level) { gfx.setColor(4); } */ + gfx.setColor(fg_color); + for(i = 0; i < stepLevel; i++) { pos_x -= bat_size_x + 2; gfx.fillRect(pos_x, bat_pos_y, @@ -257,8 +269,14 @@ function setRuntimeValues(resolution) { screen_size_x = V1_SCREEN_SIZE_X; screen_size_y = V1_SCREEN_SIZE_Y; backgroundImage = V1_BACKGROUND_IMAGE; - - // TODO: set battery stuff + bg_color = V1_BG_COLOR; + fg_color = V1_FG_COLOR; + + bat_pos_x = V1_BAT_POS_X; + bat_pos_y = V1_BAT_POS_Y; + bat_size_x = V1_BAT_SIZE_X; + bat_size_y = V1_BAT_SIZE_Y; + } else { x_step = V2_X_STEP; y_step = V2_Y_STEP; @@ -281,8 +299,14 @@ function setRuntimeValues(resolution) { screen_size_x = V2_SCREEN_SIZE_X; screen_size_y = V2_SCREEN_SIZE_Y; backgroundImage = V2_BACKGROUND_IMAGE; - // TODO: set battery stuff - } + bg_color = V2_BG_COLOR; + fg_color = V2_FG_COLOR; + + bat_pos_x = V2_BAT_POS_X; + bat_pos_y = V2_BAT_POS_Y; + bat_size_x = V2_BAT_SIZE_X; + bat_size_y = V2_BAT_SIZE_Y; +} cg = Graphics.createArrayBuffer( screen_size_x,screen_size_y, 1, {msb:true}); @@ -291,7 +315,7 @@ function setRuntimeValues(resolution) { } var hour = 0, minute = 1, second = 50; -var batVLevel = 0; +var batVLevel = 20; function draw() { @@ -300,7 +324,7 @@ function draw() { var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); drawBinary(cg, h, m, s); - cg.setColor(0); + cg.setColor(fg_color); switch(showDateTime) { case 1: @@ -312,6 +336,7 @@ function draw() { default: /* do nothing */ } + cg.setColor(fg_color); drawBattery(cg, batVLevel /*E.getBattery()*/); drawBT(1); From bffb2577796ffd078944c242348b4ff1c3768aca Mon Sep 17 00:00:00 2001 From: Vingelar Date: Fri, 8 Oct 2021 15:58:40 +0200 Subject: [PATCH 012/325] added swipe for V2 use "swipe" to change time/date/nothing for V2 --- apps/TheBinWatch/app.js | 64 ++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 8cb8669f6..bc5de8f4c 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -94,6 +94,7 @@ var fg_color = 1; var showDateTime = 2; /* show noting, time or date */ var cg; var cgimg; +var btImage = null; /* local functions */ @@ -186,11 +187,15 @@ function drawDate(gfx, d) { gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } - -function toggleDateTime() { - showDateTime++; +function toggleDateTimeUp() { + toggleDateTime(1); +} +function toggleDateTime(adder) { + showDateTime += adder; if(showDateTime > 2){ showDateTime = 0; + } else if(showDateTime < 0) { + showDateTime = 2; } draw(); } @@ -221,7 +226,7 @@ function updateVTime() { function drawBattery(gfx, level) { var pos_x = bat_pos_x + 5 * (bat_size_x + 2); var stepLevel = Math.round((level + 10) / 20); -/* +/* if(stepLevel < 2) { gfx.setColor(2); } else if(stepLevel < 4) { @@ -247,7 +252,12 @@ function drawBattery(gfx, level) { * @param gfx: graphic object * @param level: current battery level */ -function drawBT(status) { +function drawBT() { + if (NRF.getSecurityStatus().connected) { + btImage = "bt-icon.png"; + } else { + btImage = "nbt-icon.png"; + } } function setRuntimeValues(resolution) { if(240 == resolution) { @@ -277,6 +287,9 @@ function setRuntimeValues(resolution) { bat_size_x = V1_BAT_SIZE_X; bat_size_y = V1_BAT_SIZE_Y; + /* use button 1 to change date / time / nothing display */ + setWatch(toggleDateTimeUp, BTN1, { repeat : true, edge: "falling"}); + } else { x_step = V2_X_STEP; y_step = V2_Y_STEP; @@ -306,7 +319,17 @@ function setRuntimeValues(resolution) { bat_pos_y = V2_BAT_POS_Y; bat_size_x = V2_BAT_SIZE_X; bat_size_y = V2_BAT_SIZE_Y; -} + + /* use swipe to change date / time / nothing display */ + Bangle.on('swipe', function(direction) { toggleDateTime(direction);}); + + } + + dg = Graphics.createArrayBuffer( + screen_size_x,screen_size_y, 1, {msb:true}); + dgimg = {width:screen_size_x, height:screen_size_y, bpp:1, + transparent:0, buffer:dg.buffer}; + cg = Graphics.createArrayBuffer( screen_size_x,screen_size_y, 1, {msb:true}); @@ -315,7 +338,7 @@ function setRuntimeValues(resolution) { } var hour = 0, minute = 1, second = 50; -var batVLevel = 20; +var batVLevel = 20, batLevel = 0; function draw() { @@ -336,20 +359,28 @@ function draw() { default: /* do nothing */ } - cg.setColor(fg_color); - drawBattery(cg, batVLevel /*E.getBattery()*/); - drawBT(1); - batVLevel += 2; - if(batVLevel > 100) { - batVLevel = 0; + cg.setColor(fg_color); + + if (Bangle.isCharging()) { + batVLevel += 20; + if(batVLevel > 100) { + batVLevel = 0; + } + batLevel = batVLevel; + } else { + batLevel = E.getBattery(); } + + drawBattery(cg, batVLevel); + drawBT(); + updateVTime(); g.clear(); g.drawImages([{image:cgimg}, {image:require("Storage").read(backgroundImage)}, - { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")}, - ]); + { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read(btImage)} + ]); const millis = d.getMilliseconds(); setTimeout(draw, 1000-millis); } @@ -365,7 +396,8 @@ Bangle.on("lcdPower", function(on) { g.reset().clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + //setInterval(draw, 1000); //var x_size = g.getWidth(); -setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); + draw(); From 98d625fb267125e22ed0ca543db18d3c9da155ff Mon Sep 17 00:00:00 2001 From: Vingelar Date: Fri, 8 Oct 2021 16:00:24 +0200 Subject: [PATCH 013/325] BT connected icon --- apps/TheBinWatch/nbt-icon.png | Bin 0 -> 789 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/TheBinWatch/nbt-icon.png diff --git a/apps/TheBinWatch/nbt-icon.png b/apps/TheBinWatch/nbt-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6b28c50dfecef751d0952530b8c55c7bb34a8e45 GIT binary patch literal 789 zcmV+w1M2*VP)Q2T*_KEk1X zh117?p~Jwgoj|z))HKlC2sCWtKZ{_HYZF90BbpNZn$XLW@fAekWS^$qY`~8zz+k@{a+}#YXJz0~f^L3xy{T+EoQP z9yfy5Ct;jv9d#EbPPDVT30<8mC>wC~R%Bmdq1jc_j+~rDf@-EOcnP3uH@C8zi{IU7 z0C5m@VZ>Tmuy@}l6W0{0INp8$78Y4hN~2bbPqj{z#e{RKQ+U3>brWEts?giRg1I?6 zTNr<>zI@}G=0bDO+B)vEj=89wkf z^Hm%fOu7-vRy7zz4V=-Gn~$#HQkF}hgHz=1N+mYS)et9InBg^iHW3x`NF&o``$4h) z6}FFgJL&z)x=B~iMrCvkNwEVrr>gFWB~CP*vKs2+XQIKggdYWEo#_&m6Wi@W5dZk@ z{w!j_eh^7F$;<8S;0>(&vT1AOp^(U!z93>j*5+)RK4{x8Eln&aXW5?2nj4CW1W7em zXX_hofN4yVap$kOCkNxJM`@GF@b76m8pBr3zP?asW@kR;JX|iZG8>awiYvgW3xpX? zPd+C>CJU~aY`{|1KJ%SzB7%a}_7aLKL@#qhg_6DelY3}Xn>aU5RIb=}>mm9J5=u^& Tmz64P00000NkvXXu0mjfG<;lH literal 0 HcmV?d00001 From 9c84d3f42020415200b0468fbbf0c79b5eb82c15 Mon Sep 17 00:00:00 2001 From: Etienne Deux Date: Fri, 15 Oct 2021 11:51:18 +0200 Subject: [PATCH 014/325] Adding locales to vclock-simple.js --- apps/svclock/vclock-simple.js | 41 +++++++++++++++-------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index f3ab911bc..62aad0dc3 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,4 +1,6 @@ /* jshint esversion: 6 */ +const locale = require("locale"); + const timeFontSize = 65; const dateFontSize = 20; const gmtFontSize = 10; @@ -18,50 +20,41 @@ function drawSimpleClock() { Bangle.drawWidgets(); // get date - var d = new Date(); - var da = d.toString().split(" "); + //var d = new Date(); + var d = new Date(Date.parse('2011-04-11T14:5:30Z')); g.reset(); // default draw styles // drawSting centered g.setFontAlign(0, 0); - // draw time - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - var meridian = ""; + // drawTime + var hours; if (is12Hour) { - hours = parseInt(hours,10); - meridian = "AM"; - if (hours == 0) { - hours = 12; - meridian = "AM"; - } else if (hours >= 12) { - meridian = "PM"; - if (hours>12) hours -= 12; - } - hours = (" "+hours).substr(-2); + hours = ("0" + d.getHours()%12).slice(-2); + } else { + hours = ("0" + d.getHours()).slice(-2); } + var minutes = ("0" + d.getMinutes()).slice(-2); g.setFont(font, timeFontSize); g.drawString(`${hours}:${minutes}`, xyCenter, yposTime, true); - g.setFont(font, gmtFontSize); - g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + if (is12Hour) { + g.setFont(font, gmtFontSize); + g.drawString(locale.meridian(d), xyCenter + 102, yposTime + 10, true); + } // draw Day, name of month, Date - var date = [da[0], da[1], da[2]].join(" "); g.setFont(font, dateFontSize); - - g.drawString(date, xyCenter, yposDate, true); + g.drawString([locale.dow(d,1), locale.month(d,1), d.getDate()].join(" "), xyCenter, yposDate, true); // draw year g.setFont(font, dateFontSize); g.drawString(d.getFullYear(), xyCenter, yposYear, true); // draw gmt - var gmt = da[5]; g.setFont(font, gmtFontSize); - g.drawString(gmt, xyCenter, yposGMT, true); + g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); } // handle switch display on by pressing BTN1 From f1973805471bcc0f2054ba9a297b9adeb60930fa Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 14:49:13 +0100 Subject: [PATCH 015/325] Toucher: support for b2, Pastel: added 2 new custom fonts --- apps.json | 6 +- apps/pastel/ChangeLog | 1 + apps/pastel/README.md | 5 +- apps/pastel/pastel.app.js | 14 ++++ apps/pastel/pastel.settings.js | 4 +- apps/toucher/ChangeLog | 3 +- apps/toucher/README.md | 22 +++++ apps/toucher/app.js | 145 +++++++++++++++++++++++---------- apps/toucher/screenshot1.jpg | Bin 0 -> 21329 bytes 9 files changed, 152 insertions(+), 48 deletions(-) create mode 100644 apps/toucher/README.md create mode 100644 apps/toucher/screenshot1.jpg diff --git a/apps.json b/apps.json index c7abcf9f0..8e0dd604d 100644 --- a/apps.json +++ b/apps.json @@ -1478,9 +1478,9 @@ "name": "Touch Launcher", "shortName":"Toucher", "icon": "app.png", - "version":"0.06", + "version":"0.07", "description": "Touch enable left to right launcher.", - "tags": "tool,system,launcher", + "tags": "tool,system,launcher,b2", "type":"launch", "data": [ {"name":"toucher.json"} @@ -3517,7 +3517,7 @@ "name": "Pastel Clock", "shortName": "Pastel", "icon": "pastel.png", - "version":"0.04", + "version":"0.05", "description": "A Configurable clock with custom fonts and background", "tags": "clock,b2", "type":"clock", diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index e0e967166..1277f0d9d 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -2,3 +2,4 @@ 0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 0.04: Leave space at the bottom for Chrono widget, set back option at first option +0.05: Added 2 new fonts diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 9e8c133ec..324c3915a 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,7 +1,7 @@ # Pastel Clock - a configurable clock with custom fonts and background * Designed specifically for Bangle 1 and Bangle 2 -* A choice of 5 different custom fonts +* A choice of 7 different custom fonts * Supports the Light and Dark themes * Has a settings menu, change font, enable/disable the grid and the date display @@ -15,3 +15,6 @@ I came up with the name Pastel due to the shade of the grid background. ![](screenshot_b1_light.jpg) ![](screenshot_b2_dark.jpg) +![](screenshot_monoton.jpg) +![](screenshot_elite.jpg) + diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 98f8af7f9..1fe3e4a58 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -47,6 +47,16 @@ var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); }; +Graphics.prototype.setFontMonoton = function(scale) { + // Actual height 44 (43 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); +} + const SETTINGS_FILE = "pastel.json"; let settings = undefined; @@ -113,6 +123,10 @@ function draw() { g.setFontCabinSketch(); else if (settings.font == "Orbitron") g.setFontOrbitron(); + else if (settings.font == "Monoton") + g.setFontMonoton(); + else if (settings.font == "Elite") + g.setFontSpecialElite(); else g.setFontLato(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index 2e4afadc8..a8aadd58f 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -22,14 +22,14 @@ storage.write(SETTINGS_FILE, settings) } - var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"]; + var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron","Monoton","Elite"]; E.showMenu({ '': { 'title': 'Pastel Clock' }, '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), - min: 0, max: 4, + min: 0, max: 6, format: v => font_options[v], onchange: v => { s.font = font_options[v]; diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog index 494110d55..7b5c53de7 100644 --- a/apps/toucher/ChangeLog +++ b/apps/toucher/ChangeLog @@ -3,4 +3,5 @@ 0.03: Close launcher when lcd turn off 0.04: Complete rewrite to add animation and loop ( issue #210 ) 0.05: Improve perf -0.06: Complete rewrite in 80x80, better perf, add settings \ No newline at end of file +0.06: Complete rewrite in 80x80, better perf, add settings +0.07: Added suppport for Bangle 2, added README file diff --git a/apps/toucher/README.md b/apps/toucher/README.md new file mode 100644 index 000000000..a327a654c --- /dev/null +++ b/apps/toucher/README.md @@ -0,0 +1,22 @@ +# Toucher - A touch based launcher, swipe left, switch right, tap to launch + +* Designed specifically for Bangle 1 and Bangle 2 + +## Installation +- Use the App loader to install toucher +- Then delete the existing launcher +- When you restart the new launcher will be loaded +- To return to the default launcher, delete toucher and install the default launcher. + +## Bangle 1 +In the settings menu 'Low Res' refers to setting the Bangle 1 screen into 80x80 mode. +This significantly improves the animation performance. + +## Bangle 2 +The Hires/Lowres settings is ignored. +Touch the top third of the screen to launch the selected app. +Press button 1 to launch the selected app. + +## Screenshots + +![](screenshot1.jpg) diff --git a/apps/toucher/app.js b/apps/toucher/app.js index 455a29c5d..b6c37f0d0 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -7,8 +7,11 @@ let settings = Storage.readJSON(filename,1) || { debug: false }; -if(!settings.highres) Bangle.setLCDMode("80x80"); -else Bangle.setLCDMode(); +// this means that setFont('6x8',1) is actually setFont('6x8',3) +if (process.env.HWVERSION == 1) { + if(!settings.highres) Bangle.setLCDMode("80x80"); + else Bangle.setLCDMode(); +} g.clear(); g.flip(); @@ -23,7 +26,7 @@ const ORIGINAL_ICON_SIZE = 48; const STATE = { settings_open: false, index: 0, - target: 240, + target: g.getWidth(), offset: 0 }; @@ -63,7 +66,7 @@ const APPS = getApps(); function noIcon(x, y, scale){ if(scale < 0.2) return; - g.setColor(scale, scale, scale); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont('6x8',settings.highres ? 6:3); g.drawString('x_x', x+1.5, y); @@ -81,23 +84,24 @@ function render(){ g.clear(); const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); + let cycle = 0; + let lastCycle = visibleApps.length; + visibleApps.forEach(app => { - - const x = app.x+HALF-STATE.offset; - const y = HALF - (HALF*0.3); + cycle++; + const x = app.x + HALF - STATE.offset; + const y = HALF; let dist = HALF - x; if(dist < 0) dist *= -1; - const scale = 1 - (dist / HALF); if(!scale) return; if(app.special){ - const font = settings.highres ? '6x8' : '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setColor(scale,scale,scale); + const fontSize = (process.env.HWVERSION == 2) ? 4 : (settings.highres ? 6 : 2); + g.setFont('6x8', fontSize); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.drawString(app.name, HALF, HALF); return; @@ -111,26 +115,69 @@ function render(){ if(icon){ icons[app.name] = icon; try { - const rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); - const imageScale = settings.highres ? scale*2 : scale; + let rescale; + let imageScale; + + if (process.env.HWVERSION == 1) { + // on a bangle 1 !highres means 80x80 + rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); + imageScale = settings.highres ? scale*2 : scale; + } else { + // !highres mode is meaningless on a bangle 2 at present + rescale = 1.25*scale*ORIGINAL_ICON_SIZE; + imageScale = 2.5*scale; + } + g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); - } catch(e){ + } catch(e) { noIcon(x, y, scale); } - }else{ + } else { noIcon(x, y, scale); } //draw text - g.setColor(scale,scale,scale); - if(scale > 0.1){ - const font = settings.highres ? '6x8': '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setFontAlign(0,0); - g.drawString(app.name, HALF, HEIGHT/4*3); - } + g.setColor(g.theme.fg); + if (cycle == 2 && scale > 0.1) { + const fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + if (process.env.HWVERSION == 1) { + fontSize = (settings.highres) ? 3 : 1; + } + + if (app.name.length <= 12) { + g.setFont("6x8", fontSize); + g.setFontAlign(0,1); + g.drawString(app.name, HALF, HEIGHT); + } else { + // some app names are too long for one line + var name = app.name; + var first = name.substring(0, name.indexOf(" ")); + var last = name.substring(name.indexOf(" ") + 1, name.length); + + // all this to handle long names like + // Simple 7 Segment Clock + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,-1); + g.drawString(first, HALF, 0); + + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,1); + g.drawString(last, HALF, HEIGHT); + } + } + + /* if(settings.highres){ const type = app.type ? app.type : 'App'; const version = app.version ? app.version : '0.00'; @@ -138,18 +185,21 @@ function render(){ g.setFontAlign(0,1); g.setFont('6x8', 1.5); g.setColor(scale,scale,scale); - g.drawString(info, HALF, 215, { scale: scale }); + g.drawString(info, HALF, HEIGHT/8*7, { scale: scale }); } + */ }); const duration = Math.floor(Date.now()-start); if(settings.debug){ g.setFontAlign(0,1); - g.setColor(0, 1, 0); - const fontSize = settings.highres ? 2 : 1; - g.setFont('4x6',fontSize); - g.drawString('Render: '+duration+'ms', HALF, HEIGHT); + g.setColor(g.theme.fgH); + const fontSize = (process.env.HWVERSION == 2) ? 2 : (settings.highres ? 2 : 1); + g.setFont(((process.env.HWVERSION == 2) ? '6x8' : (settings.highres ? '6x8' :'4x6')), fontSize); + // steal the bottom line, and print the duration + g.clearRect(0, HEIGHT - (process.env.HWVERSION == 1 && !settings.highres ? 8 : 24), WIDTH, HEIGHT); + g.drawString('Render: '+duration+' ms', HALF, HEIGHT); } g.flip(); if(STATE.offset == STATE.target) return; @@ -202,7 +252,7 @@ function run(){ E.showMessage("App Source\nNot found"); setTimeout(render, 2000); } else { - Bangle.setLCDMode(); + if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); E.showMessage("Loading..."); @@ -211,10 +261,11 @@ function run(){ } -// Screen event -Bangle.on('touch', function(button){ - if(STATE.settings_open) return; - switch(button){ +if (process.env.HWVERSION == 1) { + // Screen event + Bangle.on('touch', function(button){ + if(STATE.settings_open) return; + switch(button){ case 1: prev(); break; @@ -224,8 +275,17 @@ Bangle.on('touch', function(button){ case 3: run(); break; - } -}); + } + }); +} + +if (process.env.HWVERSION == 2) { + // tap at top 1/3 of screen to launch app + Bangle.on('touch', function(button, xy) { + if (xy.y < HEIGHT / 3) + run(); + }); +} Bangle.on('swipe', dir => { if(STATE.settings_open) return; @@ -238,9 +298,12 @@ Bangle.on('lcdPower', on => { if(!on) return load(); }); +if (process.env.HWVERSION == 1) { + setWatch(prev, BTN1, { repeat: true }); + setWatch(next, BTN3, { repeat: true }); + setWatch(run, BTN2, { repeat:true }); +} else { + setWatch(run, BTN1, { repeat:true }); +} -setWatch(prev, BTN1, { repeat: true }); -setWatch(next, BTN3, { repeat: true }); -setWatch(run, BTN2, { repeat:true }); - -jumpTo(1); \ No newline at end of file +jumpTo(1); diff --git a/apps/toucher/screenshot1.jpg b/apps/toucher/screenshot1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..698121cbe9790999983c97c5d1503c749c65f28e GIT binary patch literal 21329 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

Wm?3!Mt<_!7vupX1HX?3>?LHW#^ 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 OaCrVtk_Ynp^8WyThLmdn literal 0 HcmV?d00001 From ae8895f5dbce68270d4e8fd49a1d6f6435130b6e Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 14:58:04 +0100 Subject: [PATCH 016/325] added screenshot files --- apps/chronowid/chrono_with_pastel.jpg | Bin 0 -> 29883 bytes apps/chronowid/chrono_with_wave.jpg | Bin 0 -> 61355 bytes apps/pastel/screenshot_elite.jpg | Bin 0 -> 9486 bytes apps/pastel/screenshot_monoton.jpg | Bin 0 -> 11281 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/chronowid/chrono_with_pastel.jpg create mode 100644 apps/chronowid/chrono_with_wave.jpg create mode 100644 apps/pastel/screenshot_elite.jpg create mode 100644 apps/pastel/screenshot_monoton.jpg diff --git a/apps/chronowid/chrono_with_pastel.jpg b/apps/chronowid/chrono_with_pastel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f5993e79cd54fc590a5ef534adfb09eb26d996b GIT binary patch literal 29883 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�*5)8`ksFQ}K!J!fzWzs*sQ<-DfniE=dGhj|j8`wO99X(_%? ztT43es(Yw~Yu*k>C$y`eThx(E&&Cj^D7%_HGz$+~R z>~BKHoC9stuS-~Xw(9Lv)L@X7lT*!F=J2{ab8{c}JXh)BOH_OqRMndVx7!r96qR(v zY3+&a7uUI^8P{Ieb&pcpnUwTZhf$&FG&N!n^O#*SJrh0m|C8DUfEVbU7uEzOmwDQ2R2@Y?;&&z(7R@Et8R#bK&#!+hQdjizJEq)Pr>~1>m|?B)GLek3|IAt7O0(j96WQQOmwsW7vH-W zmP?I3ZW5XCjbKvmj%b7MQoTP_uJzf8jpjNkt2&9LTLl2DJI~^3A>27Oks#`$-7^|j zR4HXVL8@KMp=_!qRBAGBK>lpFb1Ze;n7VoK#1CUSCcqN3R76=P`i*ik2iu_0bf1$%NH85f}n;ZqEEeTu6 z&qd`Yq+d)+pYv+m?w*|`3=-arj=p2lSJ$~N3x&$kgq_7E60!?5Y(S+&rTRgg$6dnGv63 z^#v*+&ze}knK;iuU1`e-Ii6z%f&pdTJMPp%J8CEga(R12Igp5iC_p{TF(V+yLREdn z!_&5N+;mdUs+r27?pqh{>Ym6fp)x6vt|_vxnM_`md@~w{R{QLP_ zuiUFgDCBQF$<#!L@bih&<_Eua>SG#DpVnUBsNXtDTWW$$L3^}L7!>o=v?~Of2mZEr zJ%6?NI0u#Ke*S`t@kZLS2qG9F*H}^Y15oG;*wD1<3B-P|As^hR;ojZ|3*eYbK1e84 zS^c=i&hx=W!fqkP4;BvI{mhf4bE+JJ)_IS)w2=L^rgrZ4Bh4NUyH8*qCL+?;u#X13p;(_J|k9=-BDoAedLYPSZr!Z+f!V*mCtv7n45ml%Dr7e9&_8600a@%fF^OO@Y`-LggCD=K zJftto$44@Re$cIFrdFX;huT8py25*w;QcA!hI?07>fa6@O__HQNKPO7c@EjX`?q&S z=$LM~=WFU(JqPy1{tp0|#GKv3?_}`Yr5TqQ%bKY~9-NW2=?1p!hLRC5eRP|4p`qhz z%44oMr99WwS#_jm`XydfB>v_Pz_leSb6p6WJuVHQvG!f~m?|99C}tbit>mkq;jE%h zfNz!Ji45Dow?HL($!Qz%wQoKLITcQUSL4xLILG92h?MTJB15QqMZTkU;fgstp+tDR zGC0-e(NWlK%#pai2c$4p?4_5ABZWW)M?Xz`U^+U$sN#O}RradW4MxW&+A5Q`rQh#z zUax4oHZ**>Qd)Z|qC$Hcz+2w(NN*h<&`;%vJawNaP{>GCH4KTp4Bk6`nh0fHhp4vr zY8`A_9XnNBo2z*TE~gOME2tXWti{{75s#yJpIu#LTqT%mmXB4jRpvphQePV?QgwPD z)=yWT+0Rqq<)D6oW)l1+;K)uqiw}7SQ-u^p1Xx|ShI@(XOVC~7A(oIr4u$jzC1?NbG*}(Y7J-zD9f;y0mggV&cC9J=&bpNyc*cX zcNs@bt5~Q*b|aQ(jjDyhXG=1*6-yry?l)5_N%S*>WoWDRDV8R2Je6Ql^04mHJ^(Fi b9~BJ{2M;nzaQP1a3-x8SY=!=3!k`ObG9 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 RNxorkG7uK~AHshd{{z#+XtV$T literal 0 HcmV?d00001 diff --git a/apps/pastel/screenshot_elite.jpg b/apps/pastel/screenshot_elite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b881830ed9c15b927ac46fda6b7ad4b0eb3c300c GIT binary patch literal 9486 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 xeFAyxBs-Y|mReSOecL1idHXHzv=cbKkvx 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 Date: Sat, 16 Oct 2021 15:03:22 +0100 Subject: [PATCH 017/325] Updated for toucher README file --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 8e0dd604d..f9d1102d6 100644 --- a/apps.json +++ b/apps.json @@ -1482,6 +1482,7 @@ "description": "Touch enable left to right launcher.", "tags": "tool,system,launcher,b2", "type":"launch", + "readme": "README.md", "data": [ {"name":"toucher.json"} ], From 249b3f9de23ac7455086e02c52099fda83bd8446 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 15:21:53 +0100 Subject: [PATCH 018/325] corrected typo --- apps/toucher/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/toucher/README.md b/apps/toucher/README.md index a327a654c..27cb32eeb 100644 --- a/apps/toucher/README.md +++ b/apps/toucher/README.md @@ -1,4 +1,4 @@ -# Toucher - A touch based launcher, swipe left, switch right, tap to launch +# Toucher - A touch based launcher, swipe left, swipe right, tap to launch * Designed specifically for Bangle 1 and Bangle 2 From cbfd73e122cd604c014a144e3d53f86a538757f6 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 22:33:46 +0100 Subject: [PATCH 019/325] Matrix clock support for Bangle 2 --- apps.json | 4 ++-- apps/matrixclock/ChangeLog | 3 ++- apps/matrixclock/matrixclock.js | 35 ++++++++++++++++----------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps.json b/apps.json index f9d1102d6..4c557e847 100644 --- a/apps.json +++ b/apps.json @@ -337,9 +337,9 @@ { "id": "matrixclock", "name": "Matrix Clock", "icon": "matrixclock.png", - "version":"0.01", + "version":"0.02", "description": "inspired by The Matrix, a clock of the same style", - "tags": "clock", + "tags": "clock,b2", "type":"clock", "allow_emulator":true, "readme": "README.md", diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog index d53df991b..7cc9144b1 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1 +1,2 @@ -0.01: Initial Release +0.01: Initial Release +0.02: Support for Bangle 2 diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index 0bf33fd68..ab18c13b8 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -12,6 +12,8 @@ const Locale = require('locale'); const SHARD_COLOR =[0,1.0,0]; const SHARD_FONT_SIZE = 12; const SHARD_Y_START = 30; +const w = g.getWidth(); + /** * The text shard object is responsible for creating the * shards of text that move down the screen. As the @@ -111,7 +113,7 @@ var dateStr = ""; var last_draw_time = null; const TIME_X_COORD = 20; -const TIME_Y_COORD = 100; +const TIME_Y_COORD = g.getHeight() / 2; const DATE_X_COORD = 170; const DATE_Y_COORD = 30; const RESET_PROBABILITY = 0.5; @@ -141,29 +143,26 @@ function draw_clock(){ } var now = new Date(); // draw time. Have to draw time on every loop - g.setFont("Vector",45); - g.setFontAlign(-1,-1,0); + + g.setFont("Vector", g.getWidth() / 5); + g.setFontAlign(0,-1); if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){ - g.setColor(0,0,0); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(timeStr, w/2, TIME_Y_COORD); timeStr = format_time(now); } - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(timeStr, w/2, TIME_Y_COORD); // // draw date when it changes g.setFont("Vector",15); - g.setFontAlign(-1,-1,0); + g.setFontAlign(0,-1,0); if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){ - g.setColor(0,0,0); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(dateStr, w/2, DATE_Y_COORD); dateStr = format_date(now); - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(dateStr, w/2, DATE_Y_COORD); } last_draw_time = now; } @@ -232,10 +231,10 @@ function startTimers(){ Bangle.on('lcdPower', (on) => { if (on) { - console.log("lcdPower: on"); + //console.log("lcdPower: on"); startTimers(); } else { - console.log("lcdPower: off"); + //console.log("lcdPower: off"); clearTimers(); } }); From 75422b7ff85e7a004a6d512166aeba3bd3e15232 Mon Sep 17 00:00:00 2001 From: Etienne Deux Date: Mon, 18 Oct 2021 10:21:50 +0200 Subject: [PATCH 020/325] Scale UI to banglejs 2 --- apps.json | 2 +- apps/svclock/ChangeLog | 1 + apps/svclock/vclock-simple.js | 43 ++++++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps.json b/apps.json index c8e614625..e15ff5b9c 100644 --- a/apps.json +++ b/apps.json @@ -946,7 +946,7 @@ { "id": "svclock", "name": "Simple V-Clock", "icon": "vclock-simple.png", - "version":"0.02", + "version":"0.03", "description": "Modification of Simple Clock 0.04 to use Vectorfont", "tags": "clock", "type":"clock", diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 671de492c..4db60ecd5 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling +0.03: Scale to BangleJS 2 and add locale \ No newline at end of file diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index 62aad0dc3..637701a3f 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,17 +1,39 @@ /* jshint esversion: 6 */ const locale = require("locale"); -const timeFontSize = 65; -const dateFontSize = 20; -const gmtFontSize = 10; -const font = "Vector"; +var timeFontSize; +var dateFontSize; +var gmtFontSize; +var font = "Vector"; -const xyCenter = g.getWidth() / 2; -const yposTime = 75; -const yposDate = 130; -const yposYear = 175; -const yposGMT = 220; +var xyCenter = g.getWidth() / 2; +var yposTime; +var yposDate; +var yposYear; +var yposGMT; +switch (process.env.BOARD) { + case "EMSCRIPTEN": + timeFontSize = 65; + dateFontSize = 20; + gmtFontSize = 10; + + yposTime = 75; + yposDate = 130; + yposYear = 175; + yposGMT = 220; + break; + case "EMSCRIPTEN2": + timeFontSize = 48; + dateFontSize = 15; + gmtFontSize = 10; + + yposTime = 55; + yposDate = 95; + yposYear = 128; + yposGMT = 161; + break; +} // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; @@ -20,8 +42,7 @@ function drawSimpleClock() { Bangle.drawWidgets(); // get date - //var d = new Date(); - var d = new Date(Date.parse('2011-04-11T14:5:30Z')); + var d = new Date(); g.reset(); // default draw styles // drawSting centered From 09c2718d52513917aba239c8c1f5f040052711f5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 18 Oct 2021 22:17:31 +0100 Subject: [PATCH 021/325] fixed travis build error in toucher, rordered options in App Manager, back first --- apps/files/files.js | 20 ++++++++++---------- apps/toucher/app.js | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/files/files.js b/apps/files/files.js index 9ac6ebb35..e7b42c101 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -7,13 +7,9 @@ function showMainMenu() { '': { 'title': 'App Manager', }, - 'Free': { - value: undefined, - format: (v) => { - return store.getFree(); - }, - onchange: () => {} - }, + '< Back': ()=> {load();}, + 'Sort Apps': () => showSortAppsMenu(), + 'Manage Apps': ()=> showApps(), 'Compact': () => { E.showMessage('Compacting...'); try { @@ -22,9 +18,13 @@ function showMainMenu() { } showMainMenu(); }, - 'Apps': ()=> showApps(), - 'Sort Apps': () => showSortAppsMenu(), - '< Back': ()=> {load();} + 'Free': { + value: undefined, + format: (v) => { + return store.getFree(); + }, + onchange: () => {} + }, }; E.showMenu(mainmenu); } diff --git a/apps/toucher/app.js b/apps/toucher/app.js index b6c37f0d0..8ac198f52 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -140,7 +140,7 @@ function render(){ g.setColor(g.theme.fg); if (cycle == 2 && scale > 0.1) { - const fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + let fontSize = (process.env.HWVERSION == 2) ? 2 : 1; if (process.env.HWVERSION == 1) { fontSize = (settings.highres) ? 3 : 1; } From 526dfa58bea77123f31e1be4e02ce1c0cbd60bb3 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:02:29 +1300 Subject: [PATCH 022/325] Create app.js Initial import --- apps/speedalt2/app.js | 609 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 apps/speedalt2/app.js diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js new file mode 100644 index 000000000..f96c1c320 --- /dev/null +++ b/apps/speedalt2/app.js @@ -0,0 +1,609 @@ +/* +Speed and Altitude [speedalt2] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ +var v = '0.01a'; + +/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ +var KalmanFilter = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * KalmanFilter + * @class + * @author Wouter Bulten + * @see {@link http://github.com/wouterbulten/kalmanjs} + * @version Version: 1.0.0-beta + * @copyright Copyright 2015-2018 Wouter Bulten + * @license MIT License + * @preserve + */ + var KalmanFilter = + /*#__PURE__*/ + function () { + /** + * Create 1-dimensional kalman filter + * @param {Number} options.R Process noise + * @param {Number} options.Q Measurement noise + * @param {Number} options.A State vector + * @param {Number} options.B Control vector + * @param {Number} options.C Measurement vector + * @return {KalmanFilter} + */ + function KalmanFilter() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$R = _ref.R, + R = _ref$R === void 0 ? 1 : _ref$R, + _ref$Q = _ref.Q, + Q = _ref$Q === void 0 ? 1 : _ref$Q, + _ref$A = _ref.A, + A = _ref$A === void 0 ? 1 : _ref$A, + _ref$B = _ref.B, + B = _ref$B === void 0 ? 0 : _ref$B, + _ref$C = _ref.C, + C = _ref$C === void 0 ? 1 : _ref$C; + + _classCallCheck(this, KalmanFilter); + + this.R = R; // noise power desirable + + this.Q = Q; // noise power estimated + + this.A = A; + this.C = C; + this.B = B; + this.cov = NaN; + this.x = NaN; // estimated signal without noise + } + /** + * Filter a new value + * @param {Number} z Measurement + * @param {Number} u Control + * @return {Number} + */ + + + _createClass(KalmanFilter, [{ + key: "filter", + value: function filter(z) { + var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (isNaN(this.x)) { + this.x = 1 / this.C * z; + this.cov = 1 / this.C * this.Q * (1 / this.C); + } else { + // Compute prediction + var predX = this.predict(u); + var predCov = this.uncertainty(); // Kalman gain + + var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction + + this.x = predX + K * (z - this.C * predX); + this.cov = predCov - K * this.C * predCov; + } + + return this.x; + } + /** + * Predict next value + * @param {Number} [u] Control + * @return {Number} + */ + + }, { + key: "predict", + value: function predict() { + var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return this.A * this.x + this.B * u; + } + /** + * Return uncertainty of filter + * @return {Number} + */ + + }, { + key: "uncertainty", + value: function uncertainty() { + return this.A * this.cov * this.A + this.R; + } + /** + * Return the last filtered measurement + * @return {Number} + */ + + }, { + key: "lastMeasurement", + value: function lastMeasurement() { + return this.x; + } + /** + * Set measurement noise Q + * @param {Number} noise + */ + + }, { + key: "setMeasurementNoise", + value: function setMeasurementNoise(noise) { + this.Q = noise; + } + /** + * Set the process noise R + * @param {Number} noise + */ + + }, { + key: "setProcessNoise", + value: function setProcessNoise(noise) { + this.R = noise; + } + }]); + + return KalmanFilter; + }(); + + return KalmanFilter; + +}()); + + +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); + +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); + +var lf = {fix:0,satellites:0}; +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var pwrSav = 1; // 1 = default power saving with watch screen off and GPS to PMOO mode. 0 = screen kept on. +var canDraw = 1; +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var tmrLP; // Timer for delay in switching to low power after screen turns off + +var max = {}; +max.spd = 0; +max.alt = 0; +max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data. + +var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values; + +var wp = {}; // Waypoint to use for distance from cur position. + +function nxtWp(inc){ + cfg.wp+=inc; + loadWp(); +} + +function loadWp() { + var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + if (cfg.wp>=w.length) cfg.wp=0; + if (cfg.wp<0) cfg.wp = w.length-1; + savSettings(); + wp = w[cfg.wp]; +} + +function radians(a) { + return a*Math.PI/180; +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + + // Distance in selected units + var d = Math.sqrt(x*x + y*y) * 6371000; + d = (d/parseFloat(cfg.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + +function drawScrn(dat) { + + if (!canDraw) return; + + buf.clear(); + + var n; + n = dat.val.toString(); + + var s=40; // Font size + var l=n.length; + + if ( l <= 7 ) s=48; + if ( l <= 6 ) s=55; + if ( l <= 5 ) s=68; + if ( l <= 4 ) s=90; + if ( l <= 3 ) s=110; + + buf.setFontAlign(0,-1); //Centre + buf.setColor(1); + buf.setFontVector(s); + buf.drawString(n,110,-0); + + // Primary Units + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(2); + buf.setFontVector(35); + buf.drawString(dat.unit,5,164); + + if ( dat.max ) drawMax(); // MAX display indicator + if ( dat.wp ) drawWP(); // Waypoint name + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + +} + +function drawClock() { + if (!canDraw) return; + buf.clear(); + var x, y; + x=120; + y=0; + buf.setFontAlign(0,-1); + buf.setFontVector(80); + time = require("locale").time(new Date(),1); + buf.setColor(3); + buf.drawString(time,x,y); + + g.reset(); + g.drawImage(img,0,40); +} + +function drawWP() { + var nm = wp.name; + if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = ''; + buf.setColor(2); + + buf.setFontAlign(0,1); //left, bottom + buf.setFontVector(48); + buf.drawString(nm.substring(0,8),120,140); + +} + +function drawSats(sats) { + buf.setColor(3); + buf.setFont("6x8", 2); + buf.setFontAlign(1,1); //right, bottom + buf.drawString(sats,240,160); +} + +function drawMax() { + buf.setFontVector(30); + buf.setColor(2); + buf.setFontAlign(0,1); //centre, bottom + buf.drawString('MAX',120,164); +} + +function onGPS(fix) { + + if ( emulator ) { + fix.fix = 1; + fix.speed = 10 + (Math.random()*5); + fix.alt = 354 + (Math.random()*50); + fix.lat = -38.92; + fix.lon = 175.7613350; + fix.course = 245; + fix.satellites = 12; + fix.time = new Date(); + fix.smoothed = 0; + } + + var m; + + var sp = '---'; + var al = '---'; + var di = '---'; + var age = '---'; + + if (fix.fix) lf = fix; + + if (lf.fix) { + + // Smooth data + if ( lf.smoothed !== 1 ) { + if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed); + if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt); + lf.smoothed = 1; + if ( max.n <= 15 ) max.n++; + } + + + // Speed + if ( cfg.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + cfg.spd_unit = m[2]; + } + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units + + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + } + + if ( cfg.modeA == 0 ) { + // Speed +// if ( di <= 0 ) +// drawScrn({ +// val:sp, +// unit:cfg.spd_unit, +// sats:lf.satellites, +// age:age, +// fix:lf.fix +// }); // No WP selected +// else + if ( showMax ) + drawScrn({ + val:max.spd, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:true, + wp:false, + sat:true + }); // Speed maximums + else + drawScrn({ + val:sp, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 1 ) { + // Alt + if ( showMax ) + drawScrn({ + val:max.alt, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:true, + wp:false, + sat:true + }); // Alt maximums + else + drawScrn({ + val:al, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 2 ) { + // Dist + drawScrn({ + val:di, + unit:cfg.dist_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:true, + sat:true + }); + } + + if ( cfg.modeA == 3 ) { + // Large clock + drawClock(); + } + +} + +function setButtons(){ + + // BTN1 - Max speed/alt or next waypoint + setWatch(function(e) { + var dur = e.time - e.lastTime; + if ( cfg.modeA == 0 || cfg.modeA == 1 ) { + // Spd+Alt mode - Switch between fix and MAX + if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display + else { max.spd = 0; max.alt = 0; } // Long press resets max values. + } + else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint + onGPS(lf); + }, BTN1, { edge:"falling",repeat:true}); + + // Power saving on/off + setWatch(function(e){ + pwrSav=!pwrSav; + if ( pwrSav ) { + LED1.reset(); + var s = require('Storage').readJSON('setting.json',1)||{}; + var t = s.timeout||10; + Bangle.setLCDTimeout(t); + } + else { + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(1); + LED1.set(); + } + }, BTN2, {repeat:true,edge:"falling"}); + + // BTN3 - next screen + setWatch(function(e){ + cfg.modeA = cfg.modeA+1; + if ( cfg.modeA > 3 ) cfg.modeA = 0; + savSettings(); + onGPS(lf); + }, BTN3, {repeat:true,edge:"falling"}); + +/* + // Touch left screen to toggle display + setWatch(function(e){ + cfg.primSpd = !cfg.primSpd; + savSettings(); + onGPS(lf); // Update display + }, BTN4, {repeat:true,edge:"falling"}); +*/ + +} + +function updateClock() { + if (!canDraw) return; +// drawTime(); + g.reset(); + g.drawImage(img,0,40); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + setLpMode('SuperE'); // off + g.clear(); + Bangle.drawWidgets(); + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// =Main Prog + +// Read settings. +let cfg = require('Storage').readJSON('speedalt.json',1)||{}; + +cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed +cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit +cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions. +cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units +cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions. +cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units +cfg.colour = cfg.colour||0; // Colour scheme. +cfg.wp = cfg.wp||0; // Last selected waypoint for dist +cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Clock [0 = [D]ist, 1 = [A]ltitude, 2 = [C]lock] +cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary + +cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; +cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; + +if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); +if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); + +loadWp(); + +/* +Colour Pallet Idx +0 : Background (black) +1 : Speed/Alt +2 : Units +3 : Sats +*/ +var img = { + width:buf.getWidth(), + height:buf.getHeight(), + bpp:2, + buffer:buf.buffer, + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) +}; + +if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); +if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); + +var SCREENACCESS = { + withApp:true, + request:function(){this.withApp=false;stopDraw();}, + release:function(){this.withApp=true;startDraw();} +}; + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); + +var gpssetup; +try { + gpssetup = require("gpssetup"); +} catch(e) { + gpssetup = false; +} + +// All set up. Lets go. +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +onGPS(lf); +Bangle.setGPSPower(1); + +if ( gpssetup ) { + gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); +} +else { + Bangle.setGPSPower(1); +} + +Bangle.on('GPS', onGPS); + +setButtons(); +setInterval(updateClock, 10000); From d18acdcee8cc0692ee1f3e5d376578a3a1dba73a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:04:27 +1300 Subject: [PATCH 023/325] Create ChangeLog --- apps/speedalt2/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedalt2/ChangeLog diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog new file mode 100644 index 000000000..6876bcec9 --- /dev/null +++ b/apps/speedalt2/ChangeLog @@ -0,0 +1 @@ +0.01: Initial import. From 62fcb5b9714bd4095db66e5f3664d629165bdb6a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:06:26 +1300 Subject: [PATCH 024/325] Create README.md --- apps/speedalt2/README.md | 150 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 apps/speedalt2/README.md diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md new file mode 100644 index 000000000..24596e512 --- /dev/null +++ b/apps/speedalt2/README.md @@ -0,0 +1,150 @@ +# GPS Speed, Altimeter and Distance to Waypoint + +You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. + +Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. + +The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. + +## Buttons and Controls + +BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint + +### [A]ltitude mode + +BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. + +BTN1 : Long press > 2 secs resets the recorded maximum values. + +### [D]istance mode + +BTN1 : Select next waypoint. Last fix distance from selected waypoint is displayed. + +### Large mode + +BTN1 : Select next waypoint. + +### All modes + +BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. + +BTN3 : Long press exit and return to watch. + +BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. + +## App Settings + +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). + +## Kalman Filter + +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new vlaues. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. + +## Loss of fix + +When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. + +## Screens + +Speed and Altitude:
+![](screen1.png)

+Left tap swaps displays:
+![](screen2.png)

+Distance to waypoint DeltaW:
+![](screen5.png)

+MAX Values instead:
+![](screen3.png)

+Settings:
+![](screen4.png)

+ +## Power Saving + +The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. + +This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps%20setup) if it is installed. You may choose to use the GPS Setup App to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Setup App Readme to understand what this does. + +When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. + +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. + +## Waypoints + +Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. + +The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) + +Sample waypoints.json (My sailing waypoints) + +

+[
+  {
+  "name":"NONE"
+  },
+  {
+  "name":"Omori",
+  "lat":-38.9058670,
+  "lon":175.7613350
+  },
+  {
+  "name":"DeltaW",
+  "lat":-38.9438550,
+  "lon":175.7676930
+  },
+  {
+  "name":"DeltaE",
+  "lat":-38.9395240,
+  "lon":175.7814420
+  },
+  {
+  "name":"BtClub",
+  "lat":-38.9446020,
+  "lon":175.8475720
+  },
+  {
+  "name":"Hapua",
+  "lat":-38.8177750,
+  "lon":175.8088720
+  },
+  {
+  "name":"Nook",
+  "lat":-38.7848090,
+  "lon":175.7839440
+  },
+  {
+  "name":"ChryBy",
+  "lat":-38.7975050,
+  "lon":175.7551960
+  },
+  {
+  "name":"Waiha",
+  "lat":-38.7219630,
+  "lon":175.7481520
+  },
+  {
+  "name":"KwaKwa",
+  "lat":-38.6632310,
+  "lon":175.8670320
+  },
+  {
+  "name":"Hatepe",
+  "lat":-38.8547420,
+  "lon":176.0089124
+  },
+  {
+  "name":"Kinloc",
+  "lat":-38.6614442,
+  "lon":175.9161607
+  }
+]
+
+ +## Comments and Feedback + +Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy! + +## Thanks + +Many thanks to Gordon Williams. Awesome job. + +Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code. + From 54bb8b56607264d5f2794ed4402b6cc91ca280f1 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:07:32 +1300 Subject: [PATCH 025/325] Create app-icon.js --- apps/speedalt2/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedalt2/app-icon.js diff --git a/apps/speedalt2/app-icon.js b/apps/speedalt2/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedalt2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) From 76d2616a6bb196fbb71facccb96c65f9e8a8251e Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:08:43 +1300 Subject: [PATCH 026/325] Add files via upload --- apps/speedalt2/app.png | Bin 0 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedalt2/app.png diff --git a/apps/speedalt2/app.png b/apps/speedalt2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Tue, 19 Oct 2021 14:15:17 +1300 Subject: [PATCH 027/325] Added speedalt2 Alternative version of the original speedalt. --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index d16213364..957bf723b 100644 --- a/apps.json +++ b/apps.json @@ -2896,6 +2896,25 @@ {"name":"speedalt.json"} ] }, +{ "id": "speedalt2", + "name": "GPS Adventure Sports II", + "shortName":"GPS Adv Sport", + "icon": "app.png", + "version":"0.01", + "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "tags": "tool,outdoors", + "type":"app", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"speedalt.app.js","url":"app.js"}, + {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"speedalt.json"} + ] +}, { "id": "de-stress", "name": "De-Stress", "shortName":"De-Stress", From 1218892d1c8ff0601760450ad5820e9a533ff110 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:16:57 +1300 Subject: [PATCH 028/325] Create settings.js --- apps/speedalt2/settings.js | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 apps/speedalt2/settings.js diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js new file mode 100644 index 000000000..488ba3b81 --- /dev/null +++ b/apps/speedalt2/settings.js @@ -0,0 +1,89 @@ +(function(back) { + + let settings = require('Storage').readJSON('speedalt.json',1)||{}; + //settings.buzz = settings.buzz||1; + + function writeSettings() { + require('Storage').write('speedalt.json',settings); + } + + function setUnits(m,u) { + settings.spd = m; + settings.spd_unit = u; + writeSettings(); + } + + function setUnitsAlt(m,u) { + settings.alt = m; + settings.alt_unit = u; + writeSettings(); + } + + function setUnitsDist(d,u) { + settings.dist = d; + settings.dist_unit = u; + writeSettings(); + } + + function setColour(c) { + settings.colour = c; + writeSettings(); + } + + + const appMenu = { + '': {'title': 'GPS Speed Alt'}, + '< Back': back, + '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, + 'Units' : function() { E.showMenu(unitsMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }, + 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, + 'Vibrate' : { + value : settings.buzz, + format : v => v?"On":"Off", + onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } + }*/ + }; + + const unitsMenu = { + '': {'title': 'Units'}, + '< Back': function() { E.showMenu(appMenu); }, + 'default (spd)' : function() { setUnits(0,''); }, + 'Kph (spd)' : function() { setUnits(1,'kph'); }, + 'Knots (spd)' : function() { setUnits(1.852,'kts'); }, + 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, + 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Km (dist)' : function() { setUnitsDist(1000,'km'); }, + 'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); }, + 'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); }, + 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, + 'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setColour(0); }, + 'Hi Contrast' : function() { setColour(1); }, + 'Night' : function() { setColour(2); } + }; + + const kalMenu = { + '': {'title': 'Kalman Filter'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Speed' : { + value : settings.spdFilt, + format : v => v?"On":"Off", + onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } + }, + 'Altitude' : { + value : settings.altFilt, + format : v => v?"On":"Off", + onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } + } + }; + + + E.showMenu(appMenu); + +}); From 9c4b020dc259b1c0c4a8ccbcb3522d3cfcd4c00f Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:25:25 +1300 Subject: [PATCH 029/325] Update README.md --- apps/speedalt2/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 24596e512..f918a7c12 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,5 +1,10 @@ # GPS Speed, Altimeter and Distance to Waypoint +What is the difference between GPS Adventure Sports and GPS Adventure Sports II ? +GPS Adventure Sports has 3 screens, each of which display different sets of information. +GPS Adventure Sports II has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. +Use [BTN3] to cycle through the screens. + You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. From 1a487fa7d81c470cdfb834d8115ef8cd643582d9 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:32:12 +1300 Subject: [PATCH 030/325] Update README.md --- apps/speedalt2/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index f918a7c12..96deac7d3 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,9 +1,12 @@ # GPS Speed, Altimeter and Distance to Waypoint -What is the difference between GPS Adventure Sports and GPS Adventure Sports II ? -GPS Adventure Sports has 3 screens, each of which display different sets of information. -GPS Adventure Sports II has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. -Use [BTN3] to cycle through the screens. +What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports II] ? + +[GPS Adventure Sports] has 3 screens, each of which display different sets of information. + +[GPS Adventure Sports II] has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. + +In all other respect they perform the same functions. Use BTN3 to cycle through the screens. You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. From 3be92b67865bf0aa6b8bfde03763250f294f40cd Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:50:15 +1300 Subject: [PATCH 031/325] Update settings.js --- apps/speedalt2/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 488ba3b81..2b3de16d3 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -1,10 +1,10 @@ (function(back) { - let settings = require('Storage').readJSON('speedalt.json',1)||{}; + let settings = require('Storage').readJSON('speedalt2.json',1)||{}; //settings.buzz = settings.buzz||1; function writeSettings() { - require('Storage').write('speedalt.json',settings); + require('Storage').write('speedalt2.json',settings); } function setUnits(m,u) { @@ -34,7 +34,7 @@ const appMenu = { '': {'title': 'GPS Speed Alt'}, '< Back': back, - '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, + '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, From 23f28c1698be53d32698de1af00d564ed3ea7470 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:52:54 +1300 Subject: [PATCH 032/325] Update apps.json --- apps.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 957bf723b..30ba692fc 100644 --- a/apps.json +++ b/apps.json @@ -2907,12 +2907,12 @@ "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedalt.app.js","url":"app.js"}, - {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt.settings.js","url":"settings.js"} + {"name":"speedalt2.app.js","url":"app.js"}, + {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt2.settings.js","url":"settings.js"} ], "data": [ - {"name":"speedalt.json"} + {"name":"speedalt2.json"} ] }, { "id": "de-stress", From 893a4f66f920c64a1e5f310ffab75c8c0de7261a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:56:23 +1300 Subject: [PATCH 033/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 30ba692fc..514b75313 100644 --- a/apps.json +++ b/apps.json @@ -2898,7 +2898,7 @@ }, { "id": "speedalt2", "name": "GPS Adventure Sports II", - "shortName":"GPS Adv Sport", + "shortName":"GPS Adv Sport II", "icon": "app.png", "version":"0.01", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", From 39a4cb8e174c57511f0bfe0763f9786d2d0c6a2b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:59:20 +1300 Subject: [PATCH 034/325] Update settings.js --- apps/speedalt2/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 2b3de16d3..26ae010bb 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt II'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, From 8b6dadc3fbcafe0387db022605b65d5ce82ee00b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:00:09 +1300 Subject: [PATCH 035/325] Update settings.js --- apps/speedalt/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 488ba3b81..63d77971e 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, From b5e41bc3964c1f1810aa7074fecca12736585fa0 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:01:11 +1300 Subject: [PATCH 036/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 514b75313..1b5e6470b 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 10ad5074d2eba1ec6216e6848ded17ebcfa7bba7 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:14:44 +1300 Subject: [PATCH 037/325] Update app.js --- apps/speedalt2/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index f96c1c320..ba5a6768c 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v = '0.01a'; +var v = '0.03a'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -519,7 +519,7 @@ function stopDraw() { } function savSettings() { - require("Storage").write('speedalt.json',cfg); + require("Storage").write('speedalt2.json',cfg); } function setLpMode(m) { @@ -531,7 +531,7 @@ function setLpMode(m) { // =Main Prog // Read settings. -let cfg = require('Storage').readJSON('speedalt.json',1)||{}; +let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit From b3a4bde0ea15cf7a04d82249bdc61da035abb994 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:16:04 +1300 Subject: [PATCH 038/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 1b5e6470b..1488aa4bc 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.02", + "version":"0.03", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From b9672a18dc652d024986f47ff9b5e8552762bbf0 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:29:48 +1300 Subject: [PATCH 039/325] Update README.md --- apps/speedalt2/README.md | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 96deac7d3..ab2047af6 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -8,37 +8,19 @@ What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports In all other respect they perform the same functions. Use BTN3 to cycle through the screens. -You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. - -Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. - The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. ## Buttons and Controls -BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint +BTN1 ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. -### [A]ltitude mode - -BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. - -BTN1 : Long press > 2 secs resets the recorded maximum values. - -### [D]istance mode - -BTN1 : Select next waypoint. Last fix distance from selected waypoint is displayed. - -### Large mode - -BTN1 : Select next waypoint. - -### All modes +BTN1 ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. -BTN3 : Long press exit and return to watch. +BTN3 : Cycles the modes between Speed, Altitude, Distance to waypoint and Time -BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. +BTN3 : Long press exit and return to watch. ## App Settings @@ -52,19 +34,6 @@ This filter smooths the altitude and the speed values and reduces these values ' When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. -## Screens - -Speed and Altitude:
-![](screen1.png)

-Left tap swaps displays:
-![](screen2.png)

-Distance to waypoint DeltaW:
-![](screen5.png)

-MAX Values instead:
-![](screen3.png)

-Settings:
-![](screen4.png)

- ## Power Saving The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. From 47979bc979a36fe19215514e2ca6fa20fcc23e2b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:32:12 +1300 Subject: [PATCH 040/325] Update README.md --- apps/speedalt2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index ab2047af6..236558916 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -28,7 +28,7 @@ Select the desired display units. Speed can be as per the default locale, kph, k ## Kalman Filter -This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new vlaues. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new values. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. ## Loss of fix From 8f2d849be6a209a250ad12903da9cf27c8d39ebf Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 16:09:29 +1300 Subject: [PATCH 041/325] Update app.js --- apps/speedalt2/app.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index ba5a6768c..e4557b05d 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v = '0.03a'; +var v = '0.04'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -229,20 +229,21 @@ function drawScrn(dat) { var n; n = dat.val.toString(); - var s=40; // Font size + var s=50; // Font size var l=n.length; - if ( l <= 7 ) s=48; - if ( l <= 6 ) s=55; - if ( l <= 5 ) s=68; - if ( l <= 4 ) s=90; - if ( l <= 3 ) s=110; + if ( l <= 7 ) s=55; + if ( l <= 6 ) s=60; + if ( l <= 5 ) s=80; + if ( l <= 4 ) s=100; + if ( l <= 3 ) s=120; - buf.setFontAlign(0,-1); //Centre + buf.setFontAlign(0,0); //Centre buf.setColor(1); buf.setFontVector(s); - buf.drawString(n,110,-0); - + buf.drawString(n,126,52); + + // Primary Units buf.setFontAlign(-1,1); //left, bottom buf.setColor(2); @@ -263,7 +264,7 @@ function drawScrn(dat) { g.reset(); g.drawImage(img,0,40); - + } function drawClock() { From ac366472544d9ee1350d7bb6b9cc3da19c844908 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 16:10:37 +1300 Subject: [PATCH 042/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 1488aa4bc..09565b6e8 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.03", + "version":"0.04", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From a6df5ca41a506e62169735b7cb377f2804adee95 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 16:58:58 +1300 Subject: [PATCH 043/325] Update app.js --- apps/speedalt2/app.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index e4557b05d..7bb76d8aa 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v = '0.04'; +var v = '0.05'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -264,6 +264,9 @@ function drawScrn(dat) { g.reset(); g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); } @@ -369,15 +372,6 @@ function onGPS(fix) { if ( cfg.modeA == 0 ) { // Speed -// if ( di <= 0 ) -// drawScrn({ -// val:sp, -// unit:cfg.spd_unit, -// sats:lf.satellites, -// age:age, -// fix:lf.fix -// }); // No WP selected -// else if ( showMax ) drawScrn({ val:max.spd, From 3f52cffd823cb69c7c957e50a24cab66b77289f4 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 16:59:42 +1300 Subject: [PATCH 044/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 09565b6e8..ed4d38dd0 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.04", + "version":"0.05", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 22b6bc5dee8779038cff071aa8d6e7628d671502 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 19 Oct 2021 20:56:49 +0100 Subject: [PATCH 045/325] Simplest: fixed for bangle 2 --- apps/simplest/ChangeLog | 1 + apps/simplest/app.js | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/simplest/ChangeLog b/apps/simplest/ChangeLog index d69da4ddc..f37015d6a 100644 --- a/apps/simplest/ChangeLog +++ b/apps/simplest/ChangeLog @@ -1,2 +1,3 @@ 0.01: Modified for use with new bootloader and firmware 0.02: Use Bangle.setUI for button/launcher handling +0.03: Fix display for Bangle 2 diff --git a/apps/simplest/app.js b/apps/simplest/app.js index 2ed4e5580..68564ff33 100644 --- a/apps/simplest/app.js +++ b/apps/simplest/app.js @@ -1,14 +1,17 @@ +const h = g.getHeight(); +const w = g.getWidth(); + function draw() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); g.reset(); - g.clearRect(0, 30, 239, 99); + g.clearRect(0, 30, w, 99); g.setFontAlign(0, -1); - g.setFont("Vector", 80); - g.drawString(time, 120, 40); + g.setFont("Vector", w/3); + g.drawString(time, w/2, 40); } // handle switch display on by pressing BTN1 From e36125e570f9ef8792d012470d367244abdfd96f Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 19 Oct 2021 20:58:16 +0100 Subject: [PATCH 046/325] Simplest: fixed for bangle 2 --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index f74288128..56b8ff476 100644 --- a/apps.json +++ b/apps.json @@ -3142,9 +3142,9 @@ { "id": "simplest", "name": "Simplest Clock", "icon": "simplest.png", - "version":"0.02", + "version":"0.03", "description": "The simplest working clock, acts as a tutorial piece", - "tags": "clock", + "tags": "clock,b2", "type":"clock", "readme": "README.md", "storage": [ From 0fc03f77f983be6e0b30459bfa067c5cd429fa41 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 14:39:46 +1300 Subject: [PATCH 047/325] Update app.js --- apps/speedalt2/app.js | 94 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 14 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 7bb76d8aa..89ab09a56 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -2,8 +2,9 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial +0.06 : Add Posn screen */ -var v = '0.05'; +var v = '0.06'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -270,17 +271,71 @@ function drawScrn(dat) { } +function drawPosn(dat) { + if (!canDraw) return; + buf.clear(); + ///////// + + var x, y; + x=210; + y=0; + buf.setFontAlign(1,-1); + buf.setFontVector(60); + buf.setColor(1); + + var lat = dat.lat; + var lon = dat.lon; + + var ns = 'N'; + if ( lat < 0 ) ns = 'S'; + lat = Math.abs(lat.toFixed(2)); + + var ew = 'E'; + if ( lon < 0 ) ew = 'W'; + lon = Math.abs(lon.toFixed(2)); + + buf.drawString(lat,x,y); + buf.drawString(lon,x,y+70); + + x = 240; + buf.setColor(2); + buf.setFontVector(40); + buf.drawString(ns,x,y); + buf.drawString(ew,x,y+70); + + + //// + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); + +} + function drawClock() { if (!canDraw) return; buf.clear(); var x, y; - x=120; + x=185; y=0; - buf.setFontAlign(0,-1); - buf.setFontVector(80); + buf.setFontAlign(1,-1); + buf.setFontVector(94); time = require("locale").time(new Date(),1); - buf.setColor(3); - buf.drawString(time,x,y); + + buf.setColor(3); + + buf.drawString(time.substring(0,2),x,y); + buf.drawString(time.substring(3,5),x,y+80); g.reset(); g.drawImage(img,0,40); @@ -378,7 +433,7 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, - fix:lf.fix, + // fix:lf.fix, max:true, wp:false, sat:true @@ -389,7 +444,7 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, - fix:lf.fix, +// fix:lf.fix, max:false, wp:false, sat:true @@ -404,7 +459,7 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, - fix:lf.fix, + // fix:lf.fix, max:true, wp:false, sat:true @@ -415,7 +470,7 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, - fix:lf.fix, +// fix:lf.fix, max:false, wp:false, sat:true @@ -429,14 +484,25 @@ function onGPS(fix) { unit:cfg.dist_unit, sats:lf.satellites, age:age, - fix:lf.fix, +// fix:lf.fix, max:false, wp:true, sat:true }); } + + if ( cfg.modeA == 3 ) { + // Position + drawPosn({ + sats:lf.satellites, + age:age, + lat:lf.lat, + lon:lf.lon, + sat:true + }); + } - if ( cfg.modeA == 3 ) { + if ( cfg.modeA == 4 ) { // Large clock drawClock(); } @@ -476,7 +542,7 @@ function setButtons(){ // BTN3 - next screen setWatch(function(e){ cfg.modeA = cfg.modeA+1; - if ( cfg.modeA > 3 ) cfg.modeA = 0; + if ( cfg.modeA > 4 ) cfg.modeA = 0; savSettings(); onGPS(lf); }, BTN3, {repeat:true,edge:"falling"}); @@ -536,7 +602,7 @@ cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions. cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units cfg.colour = cfg.colour||0; // Colour scheme. cfg.wp = cfg.wp||0; // Last selected waypoint for dist -cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Clock [0 = [D]ist, 1 = [A]ltitude, 2 = [C]lock] +cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Position 4=Clock cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; From de8e308023b015c1ffbd5e8cbd8597d413212346 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 14:44:35 +1300 Subject: [PATCH 048/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ed4d38dd0..cd46a56f9 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.05", + "version":"0.06", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From d1eba1e40ff275b9bdc5c276364c7b6ea62e9830 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 14:55:05 +1300 Subject: [PATCH 049/325] Update README.md --- apps/speedalt2/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 236558916..2d50f01e9 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -4,7 +4,7 @@ What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports [GPS Adventure Sports] has 3 screens, each of which display different sets of information. -[GPS Adventure Sports II] has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. +[GPS Adventure Sports II] has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. In all other respect they perform the same functions. Use BTN3 to cycle through the screens. @@ -18,7 +18,7 @@ BTN1 ( Distance ) Select next waypoint. Last fix distance from selected waypoint BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. -BTN3 : Cycles the modes between Speed, Altitude, Distance to waypoint and Time +BTN3 : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time BTN3 : Long press exit and return to watch. From e3975fc5c12b311a3ff4307fabf73f831a1c80b8 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 15:11:01 +1300 Subject: [PATCH 050/325] Update app.js --- apps/speedalt2/app.js | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 89ab09a56..3efa78cb9 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,8 +3,9 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial 0.06 : Add Posn screen +0.07 : Add swipe to change screens */ -var v = '0.06'; +var v = '0.07'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -509,6 +510,21 @@ function onGPS(fix) { } +function prevScrn() { + cfg.modeA = cfg.modeA-1; + if ( cfg.modeA < 0 ) cfg.modeA = 4; + savSettings(); + onGPS(lf); +} + +function nextScrn() { + cfg.modeA = cfg.modeA+1; + if ( cfg.modeA > 4 ) cfg.modeA = 0; + savSettings(); + onGPS(lf); +} + + function setButtons(){ // BTN1 - Max speed/alt or next waypoint @@ -541,10 +557,7 @@ function setButtons(){ // BTN3 - next screen setWatch(function(e){ - cfg.modeA = cfg.modeA+1; - if ( cfg.modeA > 4 ) cfg.modeA = 0; - savSettings(); - onGPS(lf); + nextScrn(); }, BTN3, {repeat:true,edge:"falling"}); /* @@ -643,6 +656,17 @@ Bangle.on('lcdPower',function(on) { else stopDraw(); }); +//Bangle.on('swipe', dir => { +// if(STATE.settings_open) return; +// if(dir == 1) prev(); +// else next(); +//}); + +Bangle.on('swipe',function(dir) { + if(dir == 1) prevScrn(); + else nextScrn(); +}); + var gpssetup; try { gpssetup = require("gpssetup"); From 37dc64d769c4df45d5843cdb5d8fee3b39bd07cf Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 15:34:44 +1300 Subject: [PATCH 052/325] Update app.js --- apps/speedalt2/app.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 3efa78cb9..f7c1acb8d 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens */ -var v = '0.07'; +var v = '0.07b'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -656,12 +656,6 @@ Bangle.on('lcdPower',function(on) { else stopDraw(); }); -//Bangle.on('swipe', dir => { -// if(STATE.settings_open) return; -// if(dir == 1) prev(); -// else next(); -//}); - Bangle.on('swipe',function(dir) { if(dir == 1) prevScrn(); else nextScrn(); From b5cd96d12120ec9ea0f42113bacb154d5b3eb0d4 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 15:36:22 +1300 Subject: [PATCH 053/325] Update README.md --- apps/speedalt2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 2d50f01e9..511932298 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -6,7 +6,7 @@ What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports [GPS Adventure Sports II] has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. -In all other respect they perform the same functions. Use BTN3 to cycle through the screens. +In all other respect they perform the same functions. Use BTN3 or swipe left/right to cycle through the screens. The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. From 17f006c9f0a5dcf0478162bdee7512f8178bc3fa Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 15:54:43 +1300 Subject: [PATCH 054/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index cd46a56f9..5ae4dd5da 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.06", + "version":"0.07", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 0f8b915d26f1ce4823f61a69e97a93ec23ecadf8 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 15:55:37 +1300 Subject: [PATCH 055/325] Update ChangeLog --- apps/speedalt2/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index 6876bcec9..91f01988e 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -1 +1,2 @@ 0.01: Initial import. +0.07: Add swipe to change screens. From 24fe02336472ffc7112e99afffa18aa77d19e926 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 16:50:20 +1300 Subject: [PATCH 056/325] Update app.js --- apps/speedalt2/app.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index f7c1acb8d..86c2ef7d0 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,9 +3,10 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial 0.06 : Add Posn screen -0.07 : Add swipe to change screens +0.07 : Add swipe to change screens same as BTN3 +0.08 : Add dbl tap on front same as short BTN1 */ -var v = '0.07b'; +var v = '0.07c'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -524,12 +525,8 @@ function nextScrn() { onGPS(lf); } - -function setButtons(){ - - // BTN1 - Max speed/alt or next waypoint - setWatch(function(e) { - var dur = e.time - e.lastTime; +// Next function on a screen +nextFunc(dur) { if ( cfg.modeA == 0 || cfg.modeA == 1 ) { // Spd+Alt mode - Switch between fix and MAX if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display @@ -537,6 +534,14 @@ function setButtons(){ } else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint onGPS(lf); +} + +function setButtons(){ + + // BTN1 - Max speed/alt or next waypoint + setWatch(function(e) { + var dur = e.time - e.lastTime; + nextFunc(dur); }, BTN1, { edge:"falling",repeat:true}); // Power saving on/off @@ -661,6 +666,20 @@ Bangle.on('swipe',function(dir) { else nextScrn(); }); +/* +dir : "left/right/top/bottom/front/back", + double : true/false // was this a double-tap? + x : -2 .. 2, // the axis of the tap + y : -2 .. 2, // the axis of the tap + z : -2 .. 2 // the axis of the tap +*/ + +Bangle.on('tap',function(tap) { + if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 +}); + +//nextFunc(dur) + var gpssetup; try { gpssetup = require("gpssetup"); From 47b3e15826920f17ecbb36edcb616265e1cad2c7 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 16:50:49 +1300 Subject: [PATCH 057/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 86c2ef7d0..61c67f017 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -6,7 +6,7 @@ Mike Bennett mike[at]kereru.com 0.07 : Add swipe to change screens same as BTN3 0.08 : Add dbl tap on front same as short BTN1 */ -var v = '0.07c'; +var v = '0.08a'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From 69dec737cf91c0fe6459e3c54e47ead25938dd53 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 16:51:48 +1300 Subject: [PATCH 058/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 3c2458a67..d7f6cea69 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.07", + "version":"0.08", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 51d47cf7c442493edf5d4bfd804157544b1b926b Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 17:01:09 +1300 Subject: [PATCH 059/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 61c67f017..27ff0c886 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -526,7 +526,7 @@ function nextScrn() { } // Next function on a screen -nextFunc(dur) { +function nextFunc(dur) { if ( cfg.modeA == 0 || cfg.modeA == 1 ) { // Spd+Alt mode - Switch between fix and MAX if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display From a51d090276186e034c756196939cb49dd3c4d2be Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 17:17:48 +1300 Subject: [PATCH 060/325] Update app.js --- apps/speedalt2/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 27ff0c886..6c03c14cc 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -662,6 +662,7 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { + console.log('Swipe : '+dir); if(dir == 1) prevScrn(); else nextScrn(); }); @@ -675,6 +676,7 @@ dir : "left/right/top/bottom/front/back", */ Bangle.on('tap',function(tap) { + console.log('Tap : '+tap.dir); if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 }); From 3de91e1e8c8ba2ef0147cf7fd8a04db5214b67ed Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 17:18:03 +1300 Subject: [PATCH 061/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 6c03c14cc..d6e94a489 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -6,7 +6,7 @@ Mike Bennett mike[at]kereru.com 0.07 : Add swipe to change screens same as BTN3 0.08 : Add dbl tap on front same as short BTN1 */ -var v = '0.08a'; +var v = '0.08b'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From 470e0cd1e5c9a3020e61804d0505e31bf8501e53 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 17:33:24 +1300 Subject: [PATCH 062/325] Update app.js --- apps/speedalt2/app.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index d6e94a489..82bd8525f 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -6,7 +6,7 @@ Mike Bennett mike[at]kereru.com 0.07 : Add swipe to change screens same as BTN3 0.08 : Add dbl tap on front same as short BTN1 */ -var v = '0.08b'; +var v = '0.08d'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -566,12 +566,13 @@ function setButtons(){ }, BTN3, {repeat:true,edge:"falling"}); /* - // Touch left screen to toggle display + // Touch screen same as BTN1 short setWatch(function(e){ - cfg.primSpd = !cfg.primSpd; - savSettings(); - onGPS(lf); // Update display + nextFunc(1); // Same as BTN1 short }, BTN4, {repeat:true,edge:"falling"}); + setWatch(function(e){ + nextFunc(1); // Same as BTN1 short + }, BTN5, {repeat:true,edge:"falling"}); */ } @@ -673,14 +674,14 @@ dir : "left/right/top/bottom/front/back", x : -2 .. 2, // the axis of the tap y : -2 .. 2, // the axis of the tap z : -2 .. 2 // the axis of the tap -*/ + Bangle.on('tap',function(tap) { console.log('Tap : '+tap.dir); if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 }); -//nextFunc(dur) +*/ var gpssetup; try { From 6a3d25b3f14f8d1885f337115fb6928cb9e11e82 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:12:03 +1300 Subject: [PATCH 063/325] Update app.js --- apps/speedalt2/app.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 82bd8525f..ff58eeab8 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -4,9 +4,8 @@ Mike Bennett mike[at]kereru.com 0.01 : Initial 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 -0.08 : Add dbl tap on front same as short BTN1 */ -var v = '0.08d'; +var v = '0.08e'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -326,6 +325,7 @@ function drawPosn(dat) { function drawClock() { if (!canDraw) return; + buf.clear(); var x, y; x=185; @@ -334,7 +334,7 @@ function drawClock() { buf.setFontVector(94); time = require("locale").time(new Date(),1); - buf.setColor(3); + buf.setColor(1); buf.drawString(time.substring(0,2),x,y); buf.drawString(time.substring(3,5),x,y+80); @@ -579,9 +579,8 @@ function setButtons(){ function updateClock() { if (!canDraw) return; -// drawTime(); - g.reset(); - g.drawImage(img,0,40); + if ( cfg.modeA != 4 ) return; + drawClock(); if ( emulator ) {max.spd++;max.alt++;} } @@ -663,7 +662,6 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { - console.log('Swipe : '+dir); if(dir == 1) prevScrn(); else nextScrn(); }); @@ -674,13 +672,10 @@ dir : "left/right/top/bottom/front/back", x : -2 .. 2, // the axis of the tap y : -2 .. 2, // the axis of the tap z : -2 .. 2 // the axis of the tap - - Bangle.on('tap',function(tap) { console.log('Tap : '+tap.dir); if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 }); - */ var gpssetup; From f074db47980b077aef299dce5c9df881e66ed512 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:13:15 +1300 Subject: [PATCH 064/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index d7f6cea69..efa712416 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.08", + "version":"0.09", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From fa30fa1cd090d8071c08f85638ae9a6748d3806f Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:23:43 +1300 Subject: [PATCH 065/325] Update app.js --- apps/speedalt2/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index ff58eeab8..546e58e63 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '0.08e'; +var v = '0.08f'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -341,6 +341,9 @@ function drawClock() { g.reset(); g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); } function drawWP() { From b80da3682e360955cd2b72322cf645c03258e520 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:24:09 +1300 Subject: [PATCH 066/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 546e58e63..221b7ab3f 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '0.08f'; +var v = '0.09a'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From 6b28026c3397ce43154f36bef946e23d736a3d1c Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:24:52 +1300 Subject: [PATCH 067/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index efa712416..3db371108 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.09", + "version":"1.00", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 0c853240ab7da180f9f020c81d09648eff64227f Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 18:25:09 +1300 Subject: [PATCH 068/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 221b7ab3f..dcde220ec 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '0.09a'; +var v = '1.00'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From 3564483406b8f7b01f0420e2101aea7b7c0ffe41 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 19:47:15 +1300 Subject: [PATCH 069/325] Update app.js --- apps/speedalt2/app.js | 60 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index dcde220ec..adb2d3d66 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.00'; +var v = '1.00b'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -174,7 +174,7 @@ var KalmanFilter = (function () { var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts -require("Font7x11Numeric7Seg").add(Graphics); +//require("Font7x11Numeric7Seg").add(Graphics); var lf = {fix:0,satellites:0}; var showMax = 0; // 1 = display the max values. 0 = display the cur fix @@ -275,7 +275,6 @@ function drawScrn(dat) { function drawPosn(dat) { if (!canDraw) return; buf.clear(); - ///////// var x, y; x=210; @@ -283,29 +282,17 @@ function drawPosn(dat) { buf.setFontAlign(1,-1); buf.setFontVector(60); buf.setColor(1); - - var lat = dat.lat; - var lon = dat.lon; - - var ns = 'N'; - if ( lat < 0 ) ns = 'S'; - lat = Math.abs(lat.toFixed(2)); - - var ew = 'E'; - if ( lon < 0 ) ew = 'W'; - lon = Math.abs(lon.toFixed(2)); - - buf.drawString(lat,x,y); - buf.drawString(lon,x,y+70); + + buf.drawString(dat.lat,x,y); + buf.drawString(dat.lon,x,y+70); x = 240; buf.setColor(2); buf.setFontVector(40); - buf.drawString(ns,x,y); - buf.drawString(ew,x,y+70); + buf.drawString(dat.ns,x,y); + buf.drawString(dat.ew,x,y+70); - //// //Sats if ( dat.sat ) { if ( dat.age > 10 ) { @@ -314,10 +301,10 @@ function drawPosn(dat) { } else drawSats('Sats:'+dat.sats); } - + g.reset(); g.drawImage(img,0,40); - + if ( pwrSav ) LED1.reset(); else LED1.set(); @@ -391,6 +378,11 @@ function onGPS(fix) { var al = '---'; var di = '---'; var age = '---'; + var lat = '---.--'; + var ns = ''; + var ew = ''; + var lon = '---.--'; + if (fix.fix) lf = fix; @@ -403,8 +395,8 @@ function onGPS(fix) { lf.smoothed = 1; if ( max.n <= 15 ) max.n++; } - - + + // Speed if ( cfg.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units @@ -412,7 +404,7 @@ function onGPS(fix) { cfg.spd_unit = m[2]; } else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units - + if ( sp < 10 ) sp = sp.toFixed(1); else sp = Math.round(sp); if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); @@ -428,8 +420,18 @@ function onGPS(fix) { // Age of last fix (secs) age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + + // Lat / Lon + ns = 'N'; + if ( lf.lat < 0 ) ns = 'S'; + lat = Math.abs(lf.lat.toFixed(2)); + + ew = 'E'; + if ( lf.lon < 0 ) ew = 'W'; + lon = Math.abs(lf.lon.toFixed(2)); + } - + if ( cfg.modeA == 0 ) { // Speed if ( showMax ) @@ -501,8 +503,10 @@ function onGPS(fix) { drawPosn({ sats:lf.satellites, age:age, - lat:lf.lat, - lon:lf.lon, + lat:lat, + lon:lon, + ns:ns, + ew:ew, sat:true }); } From 08f78e5fe141e2cd2057e6246496264454d91f8b Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 19:47:38 +1300 Subject: [PATCH 070/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index adb2d3d66..886c6ab70 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.00b'; +var v = '1.01'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From dac990e410fc43657f44649f365561bb085a7b78 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 20 Oct 2021 19:48:33 +1300 Subject: [PATCH 071/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 3db371108..153f026da 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.00", + "version":"1.01", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From b6d333ae749274b47289cc735333ef114272667d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 08:49:53 +0100 Subject: [PATCH 072/325] cliock 0.14: Fix BTN1 (fix #853) Add light/dark theme support --- apps.json | 2 +- apps/cliock/ChangeLog | 2 ++ apps/cliock/app.js | 23 +++++++++++------------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps.json b/apps.json index d16213364..a4fc86f5b 100644 --- a/apps.json +++ b/apps.json @@ -1371,7 +1371,7 @@ "name": "Commandline-Clock", "shortName":"CLI-Clock", "icon": "app.png", - "version":"0.13", + "version":"0.14", "description": "Simple CLI-Styled Clock", "tags": "clock,cli,command,bash,shell,b2", "type":"clock", diff --git a/apps/cliock/ChangeLog b/apps/cliock/ChangeLog index 66ef62eae..2a93a0d5f 100644 --- a/apps/cliock/ChangeLog +++ b/apps/cliock/ChangeLog @@ -5,3 +5,5 @@ 0.11: added Heart Rate Monitor status and ability to turn on/off 0.12: added support for different locales 0.13: Use setUI, work with smaller screens and themes +0.14: Fix BTN1 (fix #853) + Add light/dark theme support diff --git a/apps/cliock/app.js b/apps/cliock/app.js index 6853aaf6f..0fd6ea580 100644 --- a/apps/cliock/app.js +++ b/apps/cliock/app.js @@ -20,6 +20,8 @@ const HRT_FN_MODE = "fn_hrt"; let infoMode = NONE_MODE; let functionMode = NONE_FN_MODE; +let textCol = g.theme.dark ? "#0f0" : "#080"; + function drawAll(){ updateTime(); updateRest(new Date()); @@ -45,9 +47,7 @@ function writeLineStart(line){ function writeLine(str,line){ var y = marginTop+line*fontheight; g.setFont("6x8",fontsize); - //g.setColor(0,1,0); - g.setColor("#0f0"); - g.setFontAlign(-1,-1); + g.setColor(textCol).setFontAlign(-1,-1); g.clearRect(0,y,((str.length+1)*20),y+fontheight-1); writeLineStart(line); g.drawString(str,25,y); @@ -56,7 +56,7 @@ function writeLine(str,line){ function drawInfo(line) { let val; let str = ""; - let col = "#0f0"; // green + let col = textCol; // green //console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode); @@ -64,7 +64,7 @@ function drawInfo(line) { case NONE_FN_MODE: break; case HRT_FN_MODE: - col = "#0ff"; // cyan + col = g.theme.dark ? "#0ff": "#088"; // cyan str = "HRM: " + (hrtOn ? "ON" : "OFF"); drawModeLine(line,str,col); return; @@ -72,7 +72,7 @@ function drawInfo(line) { switch(infoMode) { case NONE_MODE: - col = "#fff"; + col = g.theme.bg; str = ""; break; case HRT_MODE: @@ -104,9 +104,8 @@ function drawModeLine(line, str, col) { g.setColor(col); var y = marginTop+line*fontheight; g.fillRect(0, y, 239, y+fontheight-1); - g.setColor(0); - g.setFontAlign(0, -1); - g.drawString(str, g.getWidth()/2, y); + g.setColor(g.theme.bg).setFontAlign(0, 0); + g.drawString(str, g.getWidth()/2, y+fontheight/2); } function changeInfoMode() { @@ -193,7 +192,7 @@ Bangle.on('lcdPower',function(on) { var click = setInterval(updateTime, 1000); // Show launcher when button pressed Bangle.setUI("clockupdown", btn=>{ - if (btn==0) changeInfoMode(); - if (btn==1) changeFunctionMode(); + if (btn<0) changeInfoMode(); + if (btn>0) changeFunctionMode(); drawAll(); -}); +}); \ No newline at end of file From cf51c17dc587e23a591ac01dae20c27666ce6954 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 08:50:51 +0100 Subject: [PATCH 073/325] Adding Bangle.js 2 firmware update app --- apps.json | 11 ++ apps/fwupdate/ChangeLog | 1 + apps/fwupdate/app.png | Bin 0 -> 1024 bytes apps/fwupdate/custom.html | 284 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 apps/fwupdate/ChangeLog create mode 100644 apps/fwupdate/app.png create mode 100644 apps/fwupdate/custom.html diff --git a/apps.json b/apps.json index a4fc86f5b..a28e57c59 100644 --- a/apps.json +++ b/apps.json @@ -1,4 +1,15 @@ [ + { "id": "fwupdate", + "name": "Firmware Update", + "icon": "app.png", + "version":"0.01", + "description": "Uploads new Espruino firmwares to Bangle.js 2", + "custom": "custom.html", "customConnect":true, + "tags": "tools,system,b2", + "type": "RAM", + "storage": [ ], + "sortorder" : -20 + }, { "id": "boot", "name": "Bootloader", "tags": "tool,system,b2", diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/fwupdate/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/fwupdate/app.png b/apps/fwupdate/app.png new file mode 100644 index 0000000000000000000000000000000000000000..1fabf06a231757bb3ce7c0a975acc7584b4a56f9 GIT binary patch literal 1024 zcmV+b1poVqP)uaT$&HuBly3iqutecYL@+#hAWNDI*M@qvcj z{0p#GG4V2PIwi-Bb)w2?MHL93MeBN27Y|^ytcB~M%o%9`h-?fpQR!@mGUY&6hekg8 zsE1~pi{4?W)fO+G^9FPip1;3h-3nB@#`bJZ)-R+Vvqb8da^Iy|Ar(D@#8pODO`9f~DsD2SbV9!{}uJqB$5ffXZ z0(9%v!A9CW1>6`$NMgrMP*^NYg>eJ;Ig@s8lc`!W$k6B`uBSV%3ld7aef!-cC!Pnu zJ3EJa^2adlqxF`(A9yRs&}bPILUt>KNNCJ%sZgKd+VJb*PExWablRE%<(?E|7u z0lylVIu5D*?;wujJLZp`L3F~z(x?EZTrIc7-K^+GzJ@+`yX&d=An2DKa)(o{mQA#U z?uF*_xyLQhL#oSLhU7CZlGwh}*MITbDcT=@xRMf2kQXxL6aF`8nOhGu^4V6J66^wF*Y362uo7SYt9OGurtQVS@X{qR|Yu9Cl zrR*~i4-?|gwhIA3o86>tnMF_KTulB#)XpUSpY)r1FpzRz{1kyIThaaTzACj zq(>2bW9hi)iwI5NwM^>5NZWFULG}T^EKRpP%bg6PDRe8*C}bb;R2}E)<`F5*wlAN% zRLD-)$|t@7lqa2|mCw6|wrok%*IT>#ey9(k`M|w7&+>loP3XxzneR^2qRJWVe<9W2 zGefd-Cd!nN7BG0t=vlz^F>%vFH~$u8&PWT8V$l_t@iF;cibcNyV`5@Lvg$tY^~gw{ u&Mm$1@Z8*rTBF?u{sZCyVq#)q*2Ld{{4L>OJ%qXd0000G`?< literal 0 HcmV?d00001 diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html new file mode 100644 index 000000000..5286ef062 --- /dev/null +++ b/apps/fwupdate/custom.html @@ -0,0 +1,284 @@ + + + + + +

+ + +

+
+    
+
+    
+  
+

From a94790391a9f13ab83c9658ba48080b9d5967190 Mon Sep 17 00:00:00 2001
From: Gordon Williams 
Date: Wed, 20 Oct 2021 15:11:04 +0100
Subject: [PATCH 074/325] Get rid of "b2" and "bno2" tags and add "supports"
 array. Use the opportunity to refactor apps.json so everything looks pretty

---
 README.md                             |    4 +-
 apps.json                             | 4404 +++++++++++++------------
 apps/_example_app/add_to_apps.json    |    5 +-
 apps/_example_widget/add_to_apps.json |    5 +-
 bin/create_app_supports_field.js      |   83 +
 bin/sanitycheck.js                    |   11 +-
 6 files changed, 2473 insertions(+), 2039 deletions(-)
 create mode 100644 bin/create_app_supports_field.js

diff --git a/README.md b/README.md
index 49f616964..3da301dba 100644
--- a/README.md
+++ b/README.md
@@ -217,8 +217,9 @@ and which gives information about the app for the Launcher.
 { "id": "appid",              // 7 character app id
   "name": "Readable name",    // readable name
   "shortName": "Short name",  // short name for launcher
-  "icon": "icon.png",         // icon in apps/
+  "version": "0v01",          // the version of this app
   "description": "...",       // long description (can contain markdown)
+  "icon": "icon.png",         // icon in apps/
   "type":"...",               // optional(if app) -  
                               //   'app' - an application
                               //   'widget' - a widget
@@ -226,6 +227,7 @@ and which gives information about the app for the Launcher.
                               //   'bootloader' - code that runs at startup only
                               //   'RAM' - code that runs and doesn't upload anything to storage
   "tags": "",                 // comma separated tag list for searching
+  "supports": ["BANGLEJS2"],  // List of device IDs supported, either BANGLEJS or BANGLEJS2
   "dependencies" : { "notify":"type" } // optional, app 'types' we depend on
                               // for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
   "readme": "README.md",      // if supplied, a link to a markdown-style text file
diff --git a/apps.json b/apps.json
index 0c1517035..8e7c4202b 100644
--- a/apps.json
+++ b/apps.json
@@ -1,35 +1,44 @@
 [
-  { "id": "fwupdate",
+  {
+    "id": "fwupdate",
     "name": "Firmware Update",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Uploads new Espruino firmwares to Bangle.js 2",
-    "custom": "custom.html", "customConnect":true,
-    "tags": "tools,system,b2",
+    "icon": "app.png",
     "type": "RAM",
-    "storage": [ ],
-    "sortorder" : -20
+    "tags": "tools,system",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "custom": "custom.html",
+    "customConnect": true,
+    "storage": [
+      
+    ],
+    "sortorder": -20
   },
-  { "id": "boot",
+  {
+    "id": "boot",
     "name": "Bootloader",
-    "tags": "tool,system,b2",
-    "type":"bootloader",
-    "icon": "bootloader.png",
-    "version":"0.31",
+    "version": "0.31",
     "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
+    "icon": "bootloader.png",
+    "type": "bootloader",
+    "tags": "tool,system",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":".boot0","url":"boot0.js"},
       {"name":".bootcde","url":"bootloader.js"},
       {"name":"bootupdate.js","url":"bootupdate.js"}
     ],
-    "sortorder" : -10
+    "sortorder": -10
   },
-  { "id": "health",
+  {
+    "id": "health",
     "name": "Health Tracking",
-    "tags": "tool,system,b2",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
+    "icon": "app.png",
+    "tags": "tool,system",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
     "storage": [
       {"name":"health.app.js","url":"app.js"},
@@ -37,200 +46,220 @@
       {"name":"health.boot.js","url":"boot.js"},
       {"name":"health","url":"lib.js"}
     ],
-    "sortorder" : -10
+    "sortorder": -10
   },
-  { "id": "moonphase",
+  {
+    "id": "moonphase",
     "name": "Moonphase",
-    "icon": "app.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Shows current moon phase. Now with GPS function.",
+    "icon": "app.png",
     "tags": "",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"moonphase.app.js","url":"app.js"},
       {"name":"moonphase.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "daysl",
+  {
+    "id": "daysl",
     "name": "Days left",
-    "icon": "app.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.",
+    "icon": "app.png",
     "tags": "",
-    "allow_emulator":false,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": false,
     "storage": [
-        {"name":"daysl.app.js","url":"app.js"},
-        {"name":"daysl.img","url":"app-icon.js","evaluate":true},
-        {"name":"daysl.wid.js","url":"widget.js"}
+      {"name":"daysl.app.js","url":"app.js"},
+      {"name":"daysl.img","url":"app-icon.js","evaluate":true},
+      {"name":"daysl.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "launch",
+  {
+    "id": "launch",
     "name": "Launcher (Default)",
-    "shortName":"Launcher",
-    "icon": "app.png",
-    "version":"0.07",
+    "shortName": "Launcher",
+    "version": "0.07",
     "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
-    "tags": "tool,system,launcher,b2",
-    "type":"launch",
+    "icon": "app.png",
+    "type": "launch",
+    "tags": "tool,system,launcher",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"launch.app.js","url":"app.js"}
     ],
-    "sortorder" : -10
+    "sortorder": -10
   },
-  { "id": "launchb2",
+  {
+    "id": "launchb2",
     "name": "Launcher (Bangle.js 2)",
-    "shortName":"Launcher",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Launcher",
+    "version": "0.03",
     "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.",
-    "tags": "tool,system,launcher,b2,bno1",
-    "type":"launch",
+    "icon": "app.png",
+    "type": "launch",
+    "tags": "tool,system,launcher",
+    "supports": ["BANGLEJS2"],
     "storage": [
       {"name":"launchb2.app.js","url":"app.js"}
     ],
-    "sortorder" : -10
+    "sortorder": -10
   },
-  { "id": "about",
+  {
+    "id": "about",
     "name": "About",
-    "icon": "app.png",
-    "version":"0.09",
+    "version": "0.09",
     "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
-    "tags": "tool,system,b2",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "tags": "tool,system",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"about.app.js","url":"app.js"},
       {"name":"about.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "locale",
+  {
+    "id": "locale",
     "name": "Languages",
-    "icon": "locale.png",
-    "version":"0.09",
+    "version": "0.09",
     "description": "Translations for different countries",
-    "tags": "tool,system,locale,translate,b2",
+    "icon": "locale.png",
     "type": "locale",
-    "custom":"locale.html",
+    "tags": "tool,system,locale,translate",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
+    "custom": "locale.html",
     "storage": [
       {"name":"locale"}
     ],
-    "sortorder" : -10
+    "sortorder": -10
   },
-  { "id": "notify",
+  {
+    "id": "notify",
     "name": "Notifications (default)",
-    "shortName":"Notifications",
-    "icon": "notify.png",
-    "version":"0.11",
+    "shortName": "Notifications",
+    "version": "0.11",
     "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app.  Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen",
-    "tags": "widget",
+    "icon": "notify.png",
     "type": "notify",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"notify","url":"notify.js"}
     ]
   },
-  { "id": "notifyfs",
+  {
+    "id": "notifyfs",
     "name": "Fullscreen Notifications",
-    "shortName":"Notifications",
-    "icon": "notify.png",
-    "version":"0.12",
+    "shortName": "Notifications",
+    "version": "0.12",
     "description": "Provides a replacement for the `Notifications (default)` `notify` module.   This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.",
-    "tags": "widget,b2",
+    "icon": "notify.png",
     "type": "notify",
+    "tags": "widget",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"notify","url":"notify.js"}
     ]
   },
-  { "id": "welcome",
+  {
+    "id": "welcome",
     "name": "Welcome",
-    "icon": "app.png",
-    "version":"0.12",
+    "version": "0.12",
     "description": "Appears at first boot and explains how to use Bangle.js",
+    "icon": "app.png",
     "tags": "start,welcome",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"welcome.boot.js","url":"boot.js"},
       {"name":"welcome.app.js","url":"app.js"},
       {"name":"welcome.settings.js","url":"settings.js"},
       {"name":"welcome.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"welcome.json"}
-    ]
+    "data": [{"name":"welcome.json"}]
   },
-  { "id": "mywelcome",
+  {
+    "id": "mywelcome",
     "name": "Customised Welcome",
     "shortName": "My Welcome",
-    "icon": "app.png",
-    "version":"0.12",
+    "version": "0.12",
     "description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
+    "icon": "app.png",
     "tags": "start,welcome",
-    "custom":"custom.html",
+    "supports": ["BANGLEJS"],
+    "custom": "custom.html",
     "storage": [
       {"name":"mywelcome.boot.js","url":"boot.js"},
       {"name":"mywelcome.app.js","url":"app.js"},
       {"name":"mywelcome.settings.js","url":"settings.js"},
       {"name":"mywelcome.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"mywelcome.json"}
-    ]
+    "data": [{"name":"mywelcome.json"}]
   },
-  { "id": "gbridge",
+  {
+    "id": "gbridge",
     "name": "Gadgetbridge",
-    "icon": "app.png",
-    "version":"0.24",
+    "version": "0.24",
     "description": "The default notification handler for Gadgetbridge notifications from Android",
-    "tags": "tool,system,android,widget,b2",
+    "icon": "app.png",
+    "type": "widget",
+    "tags": "tool,system,android,widget",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "dependencies": {"notify":"type"},
     "readme": "README.md",
-    "type":"widget",
-    "dependencies": { "notify":"type" },
     "storage": [
       {"name":"gbridge.settings.js","url":"settings.js"},
       {"name":"gbridge.img","url":"app-icon.js","evaluate":true},
       {"name":"gbridge.wid.js","url":"widget.js"}
     ],
-    "data": [
-      {"name":"gbridge.json"}
-    ]
+    "data": [{"name":"gbridge.json"}]
   },
-  { "id": "mclock",
+  {
+    "id": "mclock",
     "name": "Morphing Clock",
-    "icon": "clock-morphing.png",
-    "version":"0.07",
+    "version": "0.07",
     "description": "7 segment clock that morphs between minutes and hours",
+    "icon": "clock-morphing.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"mclock.app.js","url":"clock-morphing.js"},
       {"name":"mclock.img","url":"clock-morphing-icon.js","evaluate":true}
     ],
-    "sortorder" : -9
+    "sortorder": -9
   },
-  { "id": "setting",
+  {
+    "id": "setting",
     "name": "Settings",
-    "icon": "settings.png",
-    "version":"0.30",
+    "version": "0.30",
     "description": "A menu for setting up Bangle.js",
-    "tags": "tool,system,b2",
+    "icon": "settings.png",
+    "tags": "tool,system",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
     "storage": [
       {"name":"setting.app.js","url":"settings.js"},
       {"name":"setting.img","url":"settings-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"setting.json", "url":"settings.min.json","evaluate":true}
-    ],
-    "sortorder" : -2
+    "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}],
+    "sortorder": -2
   },
-  { "id": "alarm",
+  {
+    "id": "alarm",
     "name": "Default Alarm & Timer",
-    "shortName":"Alarms",
-    "icon": "app.png",
-    "version":"0.13",
+    "shortName": "Alarms",
+    "version": "0.13",
     "description": "Set and respond to alarms and timers",
-    "tags": "tool,alarm,widget,b2",
+    "icon": "app.png",
+    "tags": "tool,alarm,widget",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"alarm.app.js","url":"app.js"},
       {"name":"alarm.boot.js","url":"boot.js"},
@@ -238,33 +267,35 @@
       {"name":"alarm.img","url":"app-icon.js","evaluate":true},
       {"name":"alarm.wid.js","url":"widget.js"}
     ],
-    "data": [
-      {"name":"alarm.json"}
-    ]
+    "data": [{"name":"alarm.json"}]
   },
-  { "id": "wclock",
+  {
+    "id": "wclock",
     "name": "Word Clock",
-    "icon": "clock-word.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Display Time as Text",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-word.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"wclock.app.js","url":"clock-word.js"},
       {"name":"wclock.img","url":"clock-word-icon.js","evaluate":true}
     ]
   },
-  { "id": "fontclock",
+  {
+    "id": "fontclock",
     "name": "Font Clock",
-    "icon": "fontclock.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Choose the font and design of clock face from a library of available designs",
+    "icon": "fontclock.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":false,
-     "readme": "README.md",
-    "custom":"custom.html",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "custom": "custom.html",
+    "allow_emulator": false,
     "storage": [
       {"name":"fontclock.app.js","url":"fontclock.js"},
       {"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true},
@@ -281,16 +312,18 @@
       {"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"}
     ]
   },
-  { "id": "slidingtext",
+  {
+    "id": "slidingtext",
     "name": "Sliding Clock",
-    "icon": "slidingtext.png",
-    "version":"0.07",
+    "version": "0.07",
     "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported",
+    "icon": "slidingtext.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":false,
-     "readme": "README.md",
-    "custom":"custom.html",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "custom": "custom.html",
+    "allow_emulator": false,
     "storage": [
       {"name":"slidingtext.app.js","url":"slidingtext.js"},
       {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
@@ -304,16 +337,18 @@
       {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
     ]
   },
-  { "id": "solarclock",
+  {
+    "id": "solarclock",
     "name": "Solar Clock",
-    "icon": "solar_clock.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Using your current or chosen location the solar watch face shows the Sun's sky position, time and date. Also allows you to wind backwards and forwards in time to see the sun's position",
+    "icon": "solar_clock.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":false,
-     "readme": "README.md",
-    "custom":"custom.html",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "custom": "custom.html",
+    "allow_emulator": false,
     "storage": [
       {"name":"solarclock.app.js","url":"solar_clock.js"},
       {"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true},
@@ -331,42 +366,48 @@
       {"name":"solar_loc.Seoul.json","url":"solar_loc.Seoul.json"}
     ]
   },
-  { "id": "sweepclock",
+  {
+    "id": "sweepclock",
     "name": "Sweep Clock",
-    "icon": "sweepclock.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "Smooth sweep secondhand with single hour numeral. Use button 1 to toggle the numeral font, button 3 to change the colour theme and button 4 to change the date placement",
+    "icon": "sweepclock.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
-     "readme": "README.md",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"sweepclock.app.js","url":"sweepclock.js"},
       {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true}
     ]
   },
-  { "id": "matrixclock",
+  {
+    "id": "matrixclock",
     "name": "Matrix Clock",
-    "icon": "matrixclock.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "inspired by The Matrix, a clock of the same style",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
-     "readme": "README.md",
+    "icon": "matrixclock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"matrixclock.app.js","url":"matrixclock.js"},
       {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true}
     ]
   },
-  { "id": "imgclock",
+  {
+    "id": "imgclock",
     "name": "Image background clock",
-    "shortName":"Image Clock",
-    "icon": "app.png",
-    "version":"0.08",
+    "shortName": "Image Clock",
+    "version": "0.08",
     "description": "A clock with an image as a background",
+    "icon": "app.png",
+    "type": "clock",
     "tags": "clock",
-    "type" : "clock",
+    "supports": ["BANGLEJS"],
     "custom": "custom.html",
     "storage": [
       {"name":"imgclock.app.js","url":"app.js"},
@@ -376,245 +417,261 @@
       {"name":"imgclock.face.bg","content":""}
     ]
   },
-  { "id": "impwclock",
+  {
+    "id": "impwclock",
     "name": "Imprecise Word Clock",
-    "icon": "clock-impword.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.",
+    "icon": "clock-impword.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"impwclock.app.js","url":"clock-impword.js"},
       {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true}
     ]
   },
-  { "id": "aclock",
+  {
+    "id": "aclock",
     "name": "Analog Clock",
-    "icon": "clock-analog.png",
     "version": "0.15",
     "description": "An Analog Clock",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-analog.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"aclock.app.js","url":"clock-analog.js"},
       {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true}
     ]
   },
-  { "id": "clock2x3",
+  {
+    "id": "clock2x3",
     "name": "2x3 Pixel Clock",
-    "icon": "clock2x3.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "This is a simple clock using minimalist 2x3 pixel numerical digits",
-    "tags": "clock,b2",
+    "icon": "clock2x3.png",
     "type": "clock",
-    "allow_emulator":true,
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"clock2x3.app.js","url":"clock2x3-app.js"},
       {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true}
     ]
   },
-  { "id": "geissclk",
+  {
+    "id": "geissclk",
     "name": "Geiss Clock",
-    "icon": "clock.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation",
-    "tags": "clock,bno2",
-    "type":"clock",
+    "icon": "clock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"geissclk.app.js","url":"clock.js"},
       {"name":"geissclk.precompute.js","url":"precompute.js"},
       {"name":"geissclk.img","url":"clock-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"geissclk.0.map"},
-      {"name":"geissclk.1.map"},
-      {"name":"geissclk.2.map"},
-      {"name":"geissclk.3.map"},
-      {"name":"geissclk.4.map"},
-      {"name":"geissclk.5.map"},
-      {"name":"geissclk.0.pal"},
-      {"name":"geissclk.1.pal"},
-      {"name":"geissclk.2.pal"}
-    ]
+    "data": [{"name":"geissclk.0.map"},{"name":"geissclk.1.map"},{"name":"geissclk.2.map"},{"name":"geissclk.3.map"},{"name":"geissclk.4.map"},{"name":"geissclk.5.map"},{"name":"geissclk.0.pal"},{"name":"geissclk.1.pal"},{"name":"geissclk.2.pal"}]
   },
-  { "id": "trex",
+  {
+    "id": "trex",
     "name": "T-Rex",
-    "icon": "trex.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "T-Rex game in the style of Chrome's offline game",
-    "tags": "game,b2",
-    "allow_emulator":true,
+    "icon": "trex.png",
+    "tags": "game",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"trex.app.js","url":"trex.js"},
       {"name":"trex.img","url":"trex-icon.js","evaluate":true},
       {"name":"trex.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"trex.score", "storageFile": true}
-    ]
+    "data": [{"name":"trex.score","storageFile":true}]
   },
-  { "id": "astroid",
+  {
+    "id": "astroid",
     "name": "Asteroids!",
-    "icon": "asteroids.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Retro asteroids game",
-    "tags": "game,b2",
-    "allow_emulator":true,
+    "icon": "asteroids.png",
+    "tags": "game",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"astroid.app.js","url":"asteroids.js"},
       {"name":"astroid.img","url":"asteroids-icon.js","evaluate":true}
     ]
   },
-  { "id": "clickms",
+  {
+    "id": "clickms",
     "name": "Click Master",
-    "icon": "click-master.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
+    "icon": "click-master.png",
     "tags": "game",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"clickms.app.js","url":"click-master.js"},
       {"name":"clickms.img","url":"click-master-icon.js","evaluate":true}
     ]
   },
-  { "id": "horsey",
+  {
+    "id": "horsey",
     "name": "Horse Race!",
-    "icon": "horse-race.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
+    "icon": "horse-race.png",
     "tags": "game",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"horsey.app.js","url":"horse-race.js"},
       {"name":"horsey.img","url":"horse-race-icon.js","evaluate":true}
     ]
   },
-  { "id": "compass",
+  {
+    "id": "compass",
     "name": "Compass",
-    "icon": "compass.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Simple compass that points North",
+    "icon": "compass.png",
     "tags": "tool,outdoors",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"compass.app.js","url":"compass.js"},
       {"name":"compass.img","url":"compass-icon.js","evaluate":true}
     ]
   },
-  { "id": "gpstime",
+  {
+    "id": "gpstime",
     "name": "GPS Time",
-    "icon": "gpstime.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "Update the Bangle.js's clock based on the time from the GPS receiver",
+    "icon": "gpstime.png",
     "tags": "tool,gps",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"gpstime.app.js","url":"gpstime.js"},
       {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true}
     ]
   },
-  { "id": "openloc",
+  {
+    "id": "openloc",
     "name": "Open Location / Plus Codes",
     "shortName": "Open Location",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Convert your current GPS location to a series of characters",
+    "icon": "app.png",
     "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"openloc.app.js","url":"app.js"},
       {"name":"openloc.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "speedo",
+  {
+    "id": "speedo",
     "name": "Speedo",
-    "icon": "speedo.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "Show the current speed according to the GPS",
-    "tags": "tool,outdoors,gps,b2",
+    "icon": "speedo.png",
+    "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"speedo.app.js","url":"speedo.js"},
       {"name":"speedo.img","url":"speedo-icon.js","evaluate":true}
     ]
   },
-  { "id": "gpsrec",
+  {
+    "id": "gpsrec",
     "name": "GPS Recorder",
-    "icon": "app.png",
-    "version":"0.24",
-    "interface": "interface.html",
+    "version": "0.24",
     "description": "Application that allows you to record a GPS track. Can run in background",
+    "icon": "app.png",
     "tags": "tool,outdoors,gps,widget",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "interface": "interface.html",
     "storage": [
       {"name":"gpsrec.app.js","url":"app.js"},
       {"name":"gpsrec.img","url":"app-icon.js","evaluate":true},
       {"name":"gpsrec.wid.js","url":"widget.js"},
       {"name":"gpsrec.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"gpsrec.json"},
-      {"wildcard":".gpsrc?","storageFile": true}
-    ]
+    "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}]
   },
-  { "id": "gpsnav",
+  {
+    "id": "gpsnav",
     "name": "GPS Navigation",
-    "icon": "icon.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor",
+    "icon": "icon.png",
     "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
-    "interface":"waypoints.html",
+    "interface": "waypoints.html",
     "storage": [
       {"name":"gpsnav.app.js","url":"app.min.js"},
       {"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"waypoints.json","url":"waypoints.json"}
-    ]
+    "data": [{"name":"waypoints.json","url":"waypoints.json"}]
   },
-  { "id": "heart",
+  {
+    "id": "heart",
     "name": "Heart Rate Recorder",
-    "icon": "app.png",
-    "version":"0.06",
-    "interface": "interface.html",
+    "version": "0.06",
     "description": "Application that allows you to record your heart rate. Can run in background",
+    "icon": "app.png",
     "tags": "tool,health,widget",
+    "supports": ["BANGLEJS"],
+    "interface": "interface.html",
     "storage": [
       {"name":"heart.app.js","url":"app.js"},
       {"name":"heart.img","url":"app-icon.js","evaluate":true},
       {"name":"heart.wid.js","url":"widget.js"}
     ],
-    "data": [
-      {"name":"heart.json"},
-      {"wildcard":".heart?","storageFile": true}
-    ]
+    "data": [{"name":"heart.json"},{"wildcard":".heart?","storageFile":true}]
   },
-  { "id": "slevel",
+  {
+    "id": "slevel",
     "name": "Spirit Level",
-    "icon": "spiritlevel.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat",
+    "icon": "spiritlevel.png",
     "tags": "tool",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"slevel.app.js","url":"spiritlevel.js"},
       {"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true}
     ]
   },
-  { "id": "files",
+  {
+    "id": "files",
     "name": "App Manager",
-    "icon": "files.png",
-    "version":"0.07",
+    "version": "0.07",
     "description": "Show currently installed apps, free space, and allow their deletion from the watch",
+    "icon": "files.png",
     "tags": "tool,system,files",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"files.app.js","url":"files.js"},
       {"name":"files.img","url":"files-icon.js","evaluate":true}
     ]
   },
-  { "id": "weather",
+  {
+    "id": "weather",
     "name": "Weather",
-    "icon": "icon.png",
-    "version":"0.10",
+    "version": "0.10",
     "description": "Show Gadgetbridge weather report",
-    "readme": "readme.md",
+    "icon": "icon.png",
     "tags": "widget,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "readme.md",
     "storage": [
       {"name":"weather.app.js","url":"app.js"},
       {"name":"weather.wid.js","url":"widget.js"},
@@ -622,30 +679,32 @@
       {"name":"weather.img","url":"icon.js","evaluate":true},
       {"name":"weather.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name": "weather.json"}
-    ]
+    "data": [{"name":"weather.json"}]
   },
-  { "id": "chargeanim",
+  {
+    "id": "chargeanim",
     "name": "Charge Animation",
-    "icon": "icon.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.",
+    "icon": "icon.png",
     "tags": "battery",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"chargeanim.app.js","url":"app.js"},
       {"name":"chargeanim.boot.js","url":"boot.js"},
       {"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "bluetoothdock",
+  {
+    "id": "bluetoothdock",
     "name": "Bluetooth Dock",
-    "shortName":"Dock",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "Dock",
+    "version": "0.01",
     "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
+    "icon": "app.png",
     "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"bluetoothdock.app.js","url":"app.js"},
@@ -653,190 +712,214 @@
       {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widbat",
+  {
+    "id": "widbat",
     "name": "Battery Level Widget",
-    "icon": "widget.png",
-    "version":"0.08",
+    "version": "0.08",
     "description": "Show the current battery level and charging status in the top right of the clock",
-    "tags": "widget,battery,b2",
-    "type":"widget",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,battery",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"widbat.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "widlock",
+  {
+    "id": "widlock",
     "name": "Lock Widget",
-    "icon": "widget.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
-    "tags": "widget,lock,b2",
-    "type":"widget",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,lock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"widlock.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "widbatpc",
+  {
+    "id": "widbatpc",
     "name": "Battery Level Widget (with percentage)",
     "shortName": "Battery Widget",
-    "icon": "widget.png",
-    "version":"0.13",
+    "version": "0.13",
     "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
-    "tags": "widget,battery,b2",
-    "type":"widget",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,battery",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
     "storage": [
       {"name":"widbatpc.wid.js","url":"widget.js"},
       {"name":"widbatpc.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"widbatpc.json"}
-    ]
+    "data": [{"name":"widbatpc.json"}]
   },
-  { "id": "widbatwarn",
+  {
+    "id": "widbatwarn",
     "name": "Battery Warning",
     "shortName": "Battery Warning",
-    "icon": "widget.png",
-    "readme": "README.md",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Show a warning when the battery runs low.",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "tool,battery",
-    "type":"widget",
-    "dependencies": { "notify":"type" },
+    "supports": ["BANGLEJS"],
+    "dependencies": {"notify":"type"},
+    "readme": "README.md",
     "storage": [
       {"name":"widbatwarn.wid.js","url":"widget.js"},
       {"name":"widbatwarn.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"widbatwarn.json"}
-    ]
+    "data": [{"name":"widbatwarn.json"}]
   },
-  { "id": "widbt",
+  {
+    "id": "widbt",
     "name": "Bluetooth Widget",
-    "icon": "widget.png",
-    "version":"0.06",
+    "version": "0.06",
     "description": "Show the current Bluetooth connection status in the top right of the clock",
-    "tags": "widget,bluetooth,b2",
-    "type":"widget",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,bluetooth",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"widbt.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "widchime",
+  {
+    "id": "widchime",
     "name": "Hour Chime",
-    "icon": "widget.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Buzz or beep on every whole hour.",
-    "tags": "widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widchime.wid.js","url":"widget.js"},
       {"name":"widchime.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"widchime.json"}
-    ]
+    "data": [{"name":"widchime.json"}]
   },
-  { "id": "widram",
+  {
+    "id": "widram",
     "name": "RAM Widget",
-    "shortName":"RAM Widget",
-    "icon": "widget.png",
-    "version":"0.01",
+    "shortName": "RAM Widget",
+    "version": "0.01",
     "description": "Display your Bangle's available RAM percentage in a widget",
-    "tags": "widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widram.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "hrm",
+  {
+    "id": "hrm",
     "name": "Heart Rate Monitor",
-    "icon": "heartrate.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "Measure your heart rate and see live sensor data",
+    "icon": "heartrate.png",
     "tags": "health",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"hrm.app.js","url":"heartrate.js"},
       {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true}
     ]
   },
-  { "id": "widhrm",
+  {
+    "id": "widhrm",
     "name": "Simple Heart Rate widget",
-    "icon": "widget.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.",
-    "tags": "health,widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "health,widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widhrm.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "stetho",
+  {
+    "id": "stetho",
     "name": "Stethoscope",
-    "icon": "stetho.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Hear your heart rate",
+    "icon": "stetho.png",
     "tags": "health",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"stetho.app.js","url":"stetho.js"},
       {"name":"stetho.img","url":"stetho-icon.js","evaluate":true}
     ]
   },
-  { "id": "swatch",
+  {
+    "id": "swatch",
     "name": "Stopwatch",
-    "icon": "stopwatch.png",
-    "version":"0.07",
-    "interface": "interface.html",
+    "version": "0.07",
     "description": "Simple stopwatch with Lap Time logging to a JSON file",
+    "icon": "stopwatch.png",
     "tags": "health",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "interface": "interface.html",
+    "allow_emulator": true,
     "storage": [
       {"name":"swatch.app.js","url":"stopwatch.js"},
       {"name":"swatch.img","url":"stopwatch-icon.js","evaluate":true}
     ]
   },
-  { "id": "hidmsic",
+  {
+    "id": "hidmsic",
     "name": "Bluetooth Music Controls",
     "shortName": "Music Control",
-    "icon": "hid-music.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!",
+    "icon": "hid-music.png",
     "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"hidmsic.app.js","url":"hid-music.js"},
       {"name":"hidmsic.img","url":"hid-music-icon.js","evaluate":true}
     ]
   },
-  { "id": "hidkbd",
+  {
+    "id": "hidkbd",
     "name": "Bluetooth Keyboard",
     "shortName": "Bluetooth Kbd",
-    "icon": "hid-keyboard.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps",
+    "icon": "hid-keyboard.png",
     "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"hidkbd.app.js","url":"hid-keyboard.js"},
       {"name":"hidkbd.img","url":"hid-keyboard-icon.js","evaluate":true}
     ]
   },
-  { "id": "hidbkbd",
+  {
+    "id": "hidbkbd",
     "name": "Binary Bluetooth Keyboard",
     "shortName": "Binary BT Kbd",
-    "icon": "hid-binary-keyboard.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want",
+    "icon": "hid-binary-keyboard.png",
     "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"hidbkbd.app.js","url":"hid-binary-keyboard.js"},
       {"name":"hidbkbd.img","url":"hid-binary-keyboard-icon.js","evaluate":true}
     ]
   },
-  { "id": "animals",
+  {
+    "id": "animals",
     "name": "Animals Game",
-    "icon": "animals.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Simple toddler's game - displays a different number of animals each time the screen is pressed",
+    "icon": "animals.png",
     "tags": "game",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"animals.app.js","url":"animals.js"},
       {"name":"animals.img","url":"animals-icon.js","evaluate":true},
@@ -850,36 +933,43 @@
       {"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true}
     ]
   },
-  { "id": "qrcode",
+  {
+    "id": "qrcode",
     "name": "Custom QR Code",
-    "icon": "app.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Use this to upload a customised QR code to Bangle.js",
-    "tags": "qrcode,b2",
-    "custom": "custom.html", "customConnect":true,
+    "icon": "app.png",
+    "tags": "qrcode",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "custom": "custom.html",
+    "customConnect": true,
     "storage": [
       {"name":"qrcode.app.js"},
       {"name":"qrcode.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "beer",
+  {
+    "id": "beer",
     "name": "Beer Compass",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
     "custom": "custom.html",
     "storage": [
       {"name":"beer.app.js"},
       {"name":"beer.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "route",
+  {
+    "id": "route",
     "name": "Route Viewer",
-    "icon": "app.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Upload a KML file of a route, and have your watch display a map with how far around it you are",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
     "custom": "custom.html",
     "storage": [
       {"name":"route.app.js"},
@@ -889,10 +979,11 @@
   {
     "id": "ncstart",
     "name": "NCEU Startup",
-    "icon": "start.png",
-    "version":"0.06",
+    "version": "0.06",
     "description": "NodeConfEU 2019 'First Start' Sequence",
+    "icon": "start.png",
     "tags": "start,welcome",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"ncstart.app.js","url":"start.js"},
       {"name":"ncstart.boot.js","url":"boot.js"},
@@ -904,104 +995,118 @@
       {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true},
       {"name":"nc-tf.img","url":"start-tf.js","evaluate":true}
     ],
-    "data": [
-      {"name":"ncstart.json"}
-    ]
+    "data": [{"name":"ncstart.json"}]
   },
-  { "id": "ncfrun",
+  {
+    "id": "ncfrun",
     "name": "NCEU 5K Fun Run",
-    "icon": "nceu-funrun.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it",
+    "icon": "nceu-funrun.png",
     "tags": "health",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"ncfrun.app.js","url":"nceu-funrun.js"},
       {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true}
     ]
   },
-  { "id": "widnceu",
+  {
+    "id": "widnceu",
     "name": "NCEU Logo Widget",
-    "icon": "widget.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Show the NodeConf EU logo in the top left",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widnceu.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "sclock",
+  {
+    "id": "sclock",
     "name": "Simple Clock",
-    "icon": "clock-simple.png",
-    "version":"0.06",
+    "version": "0.06",
     "description": "A Simple Digital Clock",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-simple.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"sclock.app.js","url":"clock-simple.js"},
       {"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true}
     ]
   },
-  { "id": "s7clk",
+  {
+    "id": "s7clk",
     "name": "Simple 7 segment Clock",
-    "icon": "icon.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "A simple 7 segment Clock with date",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "icon.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"s7clk.app.js","url":"app.js"},
       {"name":"s7clk.img","url":"icon.js","evaluate":true}
     ]
   },
-  { "id": "vibrclock",
+  {
+    "id": "vibrclock",
     "name": "Vibrate Clock",
-    "icon": "app.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.",
+    "icon": "app.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"vibrclock.app.js","url":"app.js"},
       {"name":"vibrclock.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "svclock",
+  {
+    "id": "svclock",
     "name": "Simple V-Clock",
-    "icon": "vclock-simple.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Modification of Simple Clock 0.04 to use Vectorfont",
+    "icon": "vclock-simple.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"svclock.app.js","url":"vclock-simple.js"},
       {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true}
     ]
   },
-  { "id": "dclock",
+  {
+    "id": "dclock",
     "name": "Dev Clock",
-    "icon": "clock-dev.png",
-    "version":"0.10",
+    "version": "0.10",
     "description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-dev.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"dclock.app.js","url":"clock-dev.js"},
       {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true}
     ]
   },
-  { "id": "gesture",
+  {
+    "id": "gesture",
     "name": "Gesture Test",
-    "icon": "gesture.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "BETA! Uploads a basic Tensorflow Gesture model, and then outputs each gesture as a message",
+    "icon": "gesture.png",
+    "type": "app",
     "tags": "gesture,ai",
-    "type":"app",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"gesture.app.js","url":"gesture.js"},
       {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
@@ -1009,39 +1114,45 @@
       {"name":"gesture.img","url":"gesture-icon.js","evaluate":true}
     ]
   },
-  { "id": "pparrot",
+  {
+    "id": "pparrot",
     "name": "Party Parrot",
-    "icon": "party-parrot.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Party with a parrot on your wrist",
+    "icon": "party-parrot.png",
+    "type": "app",
     "tags": "party,parrot,lol",
-    "type":"app",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"pparrot.app.js","url":"party-parrot.js"},
       {"name":"pparrot.img","url":"party-parrot-icon.js","evaluate":true}
     ]
   },
-  { "id": "hrings",
+  {
+    "id": "hrings",
     "name": "Hypno Rings",
-    "icon": "hypno-rings.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Experiment with trippy rings, press buttons for change",
+    "icon": "hypno-rings.png",
+    "type": "app",
     "tags": "rings,hypnosis,psychadelic",
-    "type":"app",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"hrings.app.js","url":"hypno-rings.js"},
       {"name":"hrings.img","url":"hypno-rings-icon.js","evaluate":true}
     ]
   },
-  { "id": "morse",
+  {
+    "id": "morse",
     "name": "Morse Code",
-    "icon": "morse-code.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!",
+    "icon": "morse-code.png",
+    "type": "app",
     "tags": "morse,sound,visual,input",
-    "type":"app",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"morse.app.js","url":"morse-code.js"},
       {"name":"morse.img","url":"morse-code-icon.js","evaluate":true}
@@ -1050,97 +1161,112 @@
   {
     "id": "blescan",
     "name": "BLE Scanner",
-    "icon": "blescan.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Scan for advertising BLE devices",
-    "tags" : "bluetooth",
-    "storage" : [
+    "icon": "blescan.png",
+    "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
+    "storage": [
       {"name":"blescan.app.js","url":"blescan.js"},
-      {"name":"blescan.img","url":"blescan-icon.js", "evaluate":true}
+      {"name":"blescan.img","url":"blescan-icon.js","evaluate":true}
     ]
   },
-  { "id": "mmonday",
-  "name": "Manic Monday Tone",
-  "icon": "manic-monday-icon.png",
-  "version":"0.02",
-  "description": "The Bangles make a comeback",
-  "tags": "sound",
-  "storage": [
-    {"name":"mmonday.app.js","url":"manic-monday.js"},
-    {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true}
-  ]
-  },
-  { "id": "jbells",
-    "name": "Jingle Bells",
-    "icon": "jbells.png",
-    "version":"0.01",
-    "description": "Play Jingle Bells",
+  {
+    "id": "mmonday",
+    "name": "Manic Monday Tone",
+    "version": "0.02",
+    "description": "The Bangles make a comeback",
+    "icon": "manic-monday-icon.png",
     "tags": "sound",
-    "type":"app",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"mmonday.app.js","url":"manic-monday.js"},
+      {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "jbells",
+    "name": "Jingle Bells",
+    "version": "0.01",
+    "description": "Play Jingle Bells",
+    "icon": "jbells.png",
+    "type": "app",
+    "tags": "sound",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"jbells.app.js","url":"jbells.js"},
       {"name":"jbells.img","url":"jbells-icon.js","evaluate":true}
     ]
   },
-  { "id": "scolor",
+  {
+    "id": "scolor",
     "name": "Show Color",
-    "icon": "show-color.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Display all available Colors and Names",
+    "icon": "show-color.png",
+    "type": "app",
     "tags": "tool",
-    "type":"app",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"scolor.app.js","url":"show-color.js"},
       {"name":"scolor.img","url":"show-color-icon.js","evaluate":true}
     ]
   },
-  { "id": "miclock",
+  {
+    "id": "miclock",
     "name": "Mixed Clock",
-    "icon": "clock-mixed.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "A mix of analog and digital Clock",
+    "icon": "clock-mixed.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"miclock.app.js","url":"clock-mixed.js"},
       {"name":"miclock.img","url":"clock-mixed-icon.js","evaluate":true}
     ]
   },
-  { "id": "bclock",
+  {
+    "id": "bclock",
     "name": "Binary Clock",
-    "icon": "clock-binary.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "A simple binary clock watch face",
+    "icon": "clock-binary.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"bclock.app.js","url":"clock-binary.js"},
       {"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true}
     ]
   },
-  { "id": "clotris",
+  {
+    "id": "clotris",
     "name": "Clock-Tris",
-    "icon": "clock-tris.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "A fully functional clone of a classic game of falling blocks",
+    "icon": "clock-tris.png",
     "tags": "game",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"clotris.app.js","url":"clock-tris.js"},
       {"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true},
       {"name":".trishig","url":"clock-tris-high"}
     ]
   },
-  { "id": "flappy",
+  {
+    "id": "flappy",
     "name": "Flappy Bird",
-    "icon": "app.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "A Flappy Bird game clone",
-    "tags": "game,b2",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "tags": "game",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"flappy.app.js","url":"app.js"},
       {"name":"flappy.img","url":"app-icon.js","evaluate":true}
@@ -1149,137 +1275,159 @@
   {
     "id": "gpsinfo",
     "name": "GPS Info",
-    "icon": "gps-info.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "An application that displays information about altitude, lat/lon, satellites and time",
-    "tags": "gps,b2",
+    "icon": "gps-info.png",
     "type": "app",
+    "tags": "gps",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
-      {"name":"gpsinfo.app.js","url": "gps-info.js"},
-      {"name":"gpsinfo.img","url": "gps-info-icon.js","evaluate": true}
+      {"name":"gpsinfo.app.js","url":"gps-info.js"},
+      {"name":"gpsinfo.img","url":"gps-info-icon.js","evaluate":true}
     ]
   },
-  { "id": "assistedgps",
+  {
+    "id": "assistedgps",
     "name": "Assisted GPS Update (AGPS)",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
-    "custom": "custom.html",
-    "tags": "tool,outdoors,agps,bno2",
+    "icon": "app.png",
     "type": "RAM",
-    "storage": [ ]
+    "tags": "tool,outdoors,agps",
+    "supports": ["BANGLEJS"],
+    "custom": "custom.html",
+    "storage": [
+      
+    ]
   },
   {
     "id": "pomodo",
-    "name":"Pomodoro",
-    "icon":"pomodoro.png",
-    "version":"0.01",
+    "name": "Pomodoro",
+    "version": "0.01",
     "description": "A simple pomodoro timer.",
-    "tags": "pomodoro,cooking,tools",
+    "icon": "pomodoro.png",
     "type": "app",
-    "allow_emulator":true,
+    "tags": "pomodoro,cooking,tools",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
-      {"name":"pomodo.app.js","url": "pomodoro.js"},
-      {"name":"pomodo.img","url": "pomodoro-icon.js","evaluate": true}
+      {"name":"pomodo.app.js","url":"pomodoro.js"},
+      {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true}
     ]
   },
-  { "id": "blobclk",
+  {
+    "id": "blobclk",
     "name": "Large Digit Blob Clock",
-    "shortName" : "Blob Clock",
-    "icon": "clock-blob.png",
-    "version":"0.06",
+    "shortName": "Blob Clock",
+    "version": "0.06",
     "description": "A clock with big digits",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-blob.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"blobclk.app.js","url":"clock-blob.js"},
       {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true}
     ]
   },
-  { "id": "boldclk",
+  {
+    "id": "boldclk",
     "name": "Bold Clock",
-    "icon": "bold_clock.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "Simple, readable and practical clock",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "bold_clock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"boldclk.app.js","url":"bold_clock.js"},
       {"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true}
     ]
   },
-  { "id": "widclk",
+  {
+    "id": "widclk",
     "name": "Digital clock widget",
-    "icon": "widget.png",
-    "version":"0.06",
+    "version": "0.06",
     "description": "A simple digital clock widget",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,clock",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widclk.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "widpedom",
+  {
+    "id": "widpedom",
     "name": "Pedometer widget",
-    "icon": "widget.png",
-    "version":"0.19",
+    "version": "0.19",
     "description": "Daily pedometer widget",
-    "tags": "widget,b2",
-    "type":"widget",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"widpedom.wid.js","url":"widget.js"},
       {"name":"widpedom.settings.js","url":"settings.js"}
     ]
   },
-  { "id": "berlinc",
+  {
+    "id": "berlinc",
     "name": "Berlin Clock",
-    "icon": "berlin-clock.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
+    "icon": "berlin-clock.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"berlinc.app.js","url":"berlin-clock.js"},
       {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true}
     ]
   },
-  { "id": "ctrclk",
+  {
+    "id": "ctrclk",
     "name": "Centerclock",
-    "icon": "app.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Watch-centered digital 24h clock with date in dd.mm.yyyy format.",
-    "tags": "clock,bno2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"ctrclk.app.js","url":"app.js"},
       {"name":"ctrclk.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "demoapp",
+  {
+    "id": "demoapp",
     "name": "Demo Loop",
-    "icon": "app.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information",
-    "tags": "bno2",
-    "type":"app",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "type": "app",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"demoapp.app.js","url":"app.js"},
       {"name":"demoapp.img","url":"app-icon.js","evaluate":true}
     ],
-    "sortorder" : -9
+    "sortorder": -9
   },
-  { "id": "flagrse",
+  {
+    "id": "flagrse",
     "name": "Espruino Flag Raiser",
-    "icon": "app.png",
-    "version":"0.01",
-    "readme": "README.md",
+    "version": "0.01",
     "description": "App to send a command to another Espruino to cause it to raise a flag",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"flagrse.app.js","url":"app.js"},
       {"name":"flagrse.img","url":"app-icon.js","evaluate":true}
@@ -1288,64 +1436,73 @@
   {
     "id": "pipboy",
     "name": "Pipboy",
-    "icon": "app.png",
     "version": "0.04",
     "description": "Pipboy themed clock",
-    "tags": "clock,bno2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"pipboy.app.js","url":"app.js"},
       {"name":"pipboy.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "torch",
+  {
+    "id": "torch",
     "name": "Torch",
-    "shortName":"Torch",
-    "icon": "app.png",
-    "version":"0.02",
+    "shortName": "Torch",
+    "version": "0.02",
     "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets",
+    "icon": "app.png",
     "tags": "tool,torch",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"torch.app.js","url":"app.js"},
       {"name":"torch.wid.js","url":"widget.js"},
       {"name":"torch.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "rtorch",
+  {
+    "id": "rtorch",
     "name": "Red Torch",
-    "shortName":"RedTorch",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "RedTorch",
+    "version": "0.01",
     "description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets",
+    "icon": "app.png",
     "tags": "tool,torch",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"rtorch.app.js","url":"app.js"},
       {"name":"rtorch.wid.js","url":"widget.js"},
       {"name":"rtorch.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "wohrm",
+  {
+    "id": "wohrm",
     "name": "Workout HRM",
-    "icon": "app.png",
-    "version":"0.08",
-    "readme": "README.md",
+    "version": "0.08",
     "description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
-    "tags": "hrm,workout",
+    "icon": "app.png",
     "type": "app",
-    "allow_emulator":true,
+    "tags": "hrm,workout",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"wohrm.app.js","url":"app.js"},
       {"name":"wohrm.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widid",
+  {
+    "id": "widid",
     "name": "Bluetooth ID Widget",
-    "icon": "widget.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,address,mac",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widid.wid.js","url":"widget.js"}
     ]
@@ -1353,113 +1510,130 @@
   {
     "id": "grocery",
     "name": "Grocery",
-    "icon": "grocery.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
-    "tags": "tool,outdoors,shopping,list",
+    "icon": "grocery.png",
     "type": "app",
-    "custom":"grocery.html",
+    "tags": "tool,outdoors,shopping,list",
+    "supports": ["BANGLEJS"],
+    "custom": "grocery.html",
     "storage": [
-      {"name":"grocery.app.js", "url":"app.js"},
+      {"name":"grocery.app.js","url":"app.js"},
       {"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
     ]
   },
-  { "id": "marioclock",
+  {
+    "id": "marioclock",
     "name": "Mario Clock",
-    "icon": "marioclock.png",
-    "version":"0.15",
+    "version": "0.15",
     "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
-    "tags": "clock,mario,retro",
+    "icon": "marioclock.png",
     "type": "clock",
-    "allow_emulator":false,
+    "tags": "clock,mario,retro",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "allow_emulator": false,
     "storage": [
       {"name":"marioclock.app.js","url":"marioclock-app.js"},
       {"name":"marioclock.img","url":"marioclock-icon.js","evaluate":true}
     ]
   },
-  { "id": "cliock",
+  {
+    "id": "cliock",
     "name": "Commandline-Clock",
-    "shortName":"CLI-Clock",
-    "icon": "app.png",
-    "version":"0.14",
+    "shortName": "CLI-Clock",
+    "version": "0.14",
     "description": "Simple CLI-Styled Clock",
-    "tags": "clock,cli,command,bash,shell,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock,cli,command,bash,shell",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"cliock.app.js","url":"app.js"},
       {"name":"cliock.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widver",
+  {
+    "id": "widver",
     "name": "Firmware Version Widget",
-    "icon": "widget.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Display the version of the installed firmware in the top widget section.",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,tool,system",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widver.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "barclock",
+  {
+    "id": "barclock",
     "name": "Bar Clock",
-    "icon": "clock-bar.png",
-    "version":"0.08",
+    "version": "0.08",
     "description": "A simple digital clock showing seconds as a bar",
+    "icon": "clock-bar.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
-    "allow_emulator":true,
+    "allow_emulator": true,
     "storage": [
       {"name":"barclock.app.js","url":"clock-bar.js"},
       {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
     ]
   },
-  { "id": "dotclock",
+  {
+    "id": "dotclock",
     "name": "Dot Clock",
-    "icon": "clock-dot.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "A Minimal Dot Analog Clock",
-    "tags": "clock,b2",
-    "type":"clock",
-    "allow_emulator":true,
+    "icon": "clock-dot.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
     "storage": [
       {"name":"dotclock.app.js","url":"clock-dot.js"},
       {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true}
     ]
   },
-  { "id": "widtbat",
+  {
+    "id": "widtbat",
     "name": "Tiny Battery Widget",
-    "icon": "widget.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Tiny blueish battery widget, vibs and changes level color when charging",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,tool,system",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widtbat.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "chrono",
+  {
+    "id": "chrono",
     "name": "Chrono",
-    "shortName":"Chrono",
-    "icon": "chrono.png",
-    "version":"0.01",
+    "shortName": "Chrono",
+    "version": "0.01",
     "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.",
+    "icon": "chrono.png",
     "tags": "tool",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"chrono.app.js","url":"chrono.js"},
       {"name":"chrono.img","url":"chrono-icon.js","evaluate":true}
     ]
   },
-  { "id": "astrocalc",
+  {
+    "id": "astrocalc",
     "name": "Astrocalc",
-    "icon": "astrocalc.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
+    "icon": "astrocalc.png",
     "tags": "app,sun,moon,cycles,tool,outdoors",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"astrocalc.app.js","url":"astrocalc-app.js"},
       {"name":"suncalc.js","url":"suncalc.js"},
@@ -1474,108 +1648,121 @@
       {"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true}
     ]
   },
-  { "id": "widhwt",
+  {
+    "id": "widhwt",
     "name": "Hand Wash Timer",
-    "icon": "widget.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Swipe your wrist over the watch face to start your personal Bangle.js hand wash timer for 35 sec. Start washing after the short buzz and stop after the long buzz.",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,tool",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widhwt.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "toucher",
+  {
+    "id": "toucher",
     "name": "Touch Launcher",
-    "shortName":"Toucher",
-    "icon": "app.png",
-    "version":"0.07",
+    "shortName": "Toucher",
+    "version": "0.07",
     "description": "Touch enable left to right launcher.",
-    "tags": "tool,system,launcher,b2",
-    "type":"launch",
+    "icon": "app.png",
+    "type": "launch",
+    "tags": "tool,system,launcher",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
-    "data": [
-      {"name":"toucher.json"}
-    ],
     "storage": [
       {"name":"toucher.app.js","url":"app.js"},
       {"name":"toucher.settings.js","url":"settings.js"}
     ],
-    "sortorder" : -10
+    "data": [{"name":"toucher.json"}],
+    "sortorder": -10
   },
   {
     "id": "balltastic",
     "name": "Balltastic",
-    "icon": "app.png",
     "version": "0.02",
     "description": "Simple but fun ball eats dots game.",
-    "tags": "game,fun",
+    "icon": "app.png",
     "type": "app",
+    "tags": "game,fun",
+    "supports": ["BANGLEJS"],
     "storage": [
-        {"name":"balltastic.app.js","url":"app.js"},
-        {"name":"balltastic.img","url":"app-icon.js","evaluate":true}
-      ]
+      {"name":"balltastic.app.js","url":"app.js"},
+      {"name":"balltastic.img","url":"app-icon.js","evaluate":true}
+    ]
   },
   {
     "id": "rpgdice",
     "name": "RPG dice",
-    "icon": "rpgdice.png",
     "version": "0.02",
     "description": "Simple RPG dice rolling app.",
-    "tags": "game,fun",
+    "icon": "rpgdice.png",
     "type": "app",
+    "tags": "game,fun",
+    "supports": ["BANGLEJS"],
     "allow_emulator": true,
     "storage": [
-      {"name":"rpgdice.app.js","url": "app.js"},
-      {"name":"rpgdice.img","url": "app-icon.js","evaluate":true}
+      {"name":"rpgdice.app.js","url":"app.js"},
+      {"name":"rpgdice.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widmp",
+  {
+    "id": "widmp",
     "name": "Moon Phase Widget",
-    "icon": "widget.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget,tools",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widmp.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "minionclk",
+  {
+    "id": "minionclk",
     "name": "Minion clock",
-    "icon": "minionclk.png",
     "version": "0.05",
     "description": "Minion themed clock.",
-    "tags": "clock,minion",
+    "icon": "minionclk.png",
     "type": "clock",
+    "tags": "clock,minion",
+    "supports": ["BANGLEJS"],
     "allow_emulator": true,
     "storage": [
       {"name":"minionclk.app.js","url":"app.js"},
       {"name":"minionclk.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "openstmap",
+  {
+    "id": "openstmap",
     "name": "OpenStreetMap",
-    "shortName":"OpenStMap",
-    "icon": "app.png",
-    "version":"0.09",
+    "shortName": "OpenStMap",
+    "version": "0.09",
     "description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
-    "tags": "outdoors,gps,b2",
-    "custom": "custom.html", "customConnect":true,
+    "icon": "app.png",
+    "tags": "outdoors,gps",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "custom": "custom.html",
+    "customConnect": true,
     "storage": [
       {"name":"openstmap","url":"openstmap.js"},
       {"name":"openstmap.app.js","url":"app.js"},
       {"name":"openstmap.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "activepedom",
+  {
+    "id": "activepedom",
     "name": "Active Pedometer",
-    "shortName":"Active Pedometer",
-    "icon": "app.png",
-    "version":"0.09",
+    "shortName": "Active Pedometer",
+    "version": "0.09",
     "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
+    "icon": "app.png",
     "tags": "outdoors,widget",
-     "readme": "README.md",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"activepedom.wid.js","url":"widget.js"},
       {"name":"activepedom.settings.js","url":"settings.js"},
@@ -1583,136 +1770,154 @@
       {"name":"activepedom.app.js","url":"app.js"}
     ]
   },
-  { "id": "chronowid",
+  {
+    "id": "chronowid",
     "name": "Chrono Widget",
-    "shortName":"Chrono Widget",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Chrono Widget",
+    "version": "0.03",
     "description": "Chronometer (timer) which runs as widget.",
-    "tags": "tool,widget,b2",
-     "readme": "README.md",
+    "icon": "app.png",
+    "tags": "tool,widget",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "readme": "README.md",
     "storage": [
       {"name":"chronowid.wid.js","url":"widget.js"},
       {"name":"chronowid.app.js","url":"app.js"},
       {"name":"chronowid.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "tabata",
+  {
+    "id": "tabata",
     "name": "Tabata",
     "shortName": "Tabata - Control High-Intensity Interval Training",
-    "icon": "tabata.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Control high-intensity interval training (according to tabata: https://en.wikipedia.org/wiki/Tabata_method).",
+    "icon": "tabata.png",
     "tags": "workout,health",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"tabata.app.js","url":"tabata.js"},
       {"name":"tabata.img","url":"tabata-icon.js","evaluate":true}
     ]
   },
-  { "id": "custom",
+  {
+    "id": "custom",
     "name": "Custom Boot Code ",
-    "icon": "custom.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Add code you want to run at boot time",
-    "tags": "tool,system",
+    "icon": "custom.png",
     "type": "bootloader",
-    "custom":"custom.html",
+    "tags": "tool,system",
+    "supports": ["BANGLEJS"],
+    "custom": "custom.html",
     "storage": [
-       {"name":"custom"}
+      {"name":"custom"}
     ]
   },
-  { "id": "devstopwatch",
-  "name": "Dev Stopwatch",
-  "shortName":"Dev Stopwatch",
-  "icon": "app.png",
-  "version":"0.03",
-  "description": "Stopwatch with 5 laps supported (cyclically replaced)",
-  "tags": "stopwatch,chrono,timer,chronometer,b2",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"devstopwatch.app.js","url":"app.js"},
-    {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true}
-  ]
-  },
-  { "id": "batchart",
-    "name": "Battery Chart",
-    "shortName":"Battery Chart",
+  {
+    "id": "devstopwatch",
+    "name": "Dev Stopwatch",
+    "shortName": "Dev Stopwatch",
+    "version": "0.03",
+    "description": "Stopwatch with 5 laps supported (cyclically replaced)",
     "icon": "app.png",
-    "version":"0.10",
-    "readme": "README.md",
+    "tags": "stopwatch,chrono,timer,chronometer",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"devstopwatch.app.js","url":"app.js"},
+      {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "batchart",
+    "name": "Battery Chart",
+    "shortName": "Battery Chart",
+    "version": "0.10",
     "description": "A widget and an app for recording and visualizing battery percentage over time.",
+    "icon": "app.png",
     "tags": "app,widget,battery,time,record,chart,tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"batchart.wid.js","url":"widget.js"},
       {"name":"batchart.app.js","url":"app.js"},
       {"name":"batchart.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "nato",
+  {
+    "id": "nato",
     "name": "NATO Alphabet",
-    "shortName" : "NATOAlphabet",
-    "icon": "nato.png",
-    "version":"0.01",
-    "type": "app",
+    "shortName": "NATOAlphabet",
+    "version": "0.01",
     "description": "Learn the NATO Phonetic alphabet plus some numbers.",
+    "icon": "nato.png",
+    "type": "app",
     "tags": "app,learn,visual",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"nato.app.js","url":"nato.js"},
       {"name":"nato.img","url":"nato-icon.js","evaluate":true}
     ]
   },
-  { "id": "numerals",
+  {
+    "id": "numerals",
     "name": "Numerals Clock",
     "shortName": "Numerals Clock",
-    "icon": "numerals.png",
-    "version":"0.09",
+    "version": "0.09",
     "description": "A simple big numerals clock",
+    "icon": "numerals.png",
+    "type": "clock",
     "tags": "numerals,clock",
-	"type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"numerals.app.js","url":"numerals.app.js"},
       {"name":"numerals.img","url":"numerals-icon.js","evaluate":true},
       {"name":"numerals.settings.js","url":"numerals.settings.js"}
     ],
-    "data":[
-      {"name":"numerals.json"}
-    ]
+    "data": [{"name":"numerals.json"}]
   },
-  { "id": "bledetect",
+  {
+    "id": "bledetect",
     "name": "BLE Detector",
-    "shortName":"BLE Detector",
-    "icon": "bledetect.png",
-    "version":"0.03",
+    "shortName": "BLE Detector",
+    "version": "0.03",
     "description": "Detect BLE devices and show some informations.",
+    "icon": "bledetect.png",
     "tags": "app,bluetooth,tool",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"bledetect.app.js","url":"bledetect.js"},
       {"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
     ]
   },
-  { "id": "snake",
+  {
+    "id": "snake",
     "name": "Snake",
-    "shortName":"Snake",
-    "icon": "snake.png",
-    "version":"0.02",
+    "shortName": "Snake",
+    "version": "0.02",
     "description": "The classic snake game. Eat apples and don't bite your tail.",
+    "icon": "snake.png",
     "tags": "game,fun",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"snake.app.js","url":"snake.js"},
       {"name":"snake.img","url":"snake-icon.js","evaluate":true}
     ]
   },
-    { "id": "calculator",
+  {
+    "id": "calculator",
     "name": "Calculator",
-    "shortName":"Calculator",
-    "icon": "calculator.png",
-    "version":"0.04",
+    "shortName": "Calculator",
+    "version": "0.04",
     "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
-    "tags": "app,tool,b2",
+    "icon": "calculator.png",
+    "tags": "app,tool",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "storage": [
       {"name":"calculator.app.js","url":"app.js"},
       {"name":"calculator.img","url":"calculator-icon.js","evaluate":true}
@@ -1722,54 +1927,49 @@
     "id": "dane",
     "name": "Digital Assistant, not EDITH",
     "shortName": "DANE",
-    "icon": "app.png",
     "version": "0.16",
     "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/",
-    "tags": "clock",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "allow_emulator": true,
     "storage": [
-      {
-        "name": "dane.app.js",
-        "url": "app.js"
-      },
-      {
-        "name": "dane.img",
-        "url": "app-icon.js",
-        "evaluate": true
-      }
+      {"name":"dane.app.js","url":"app.js"},
+      {"name":"dane.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "dane_tcr",
+  {
+    "id": "dane_tcr",
     "name": "DANE Touch Launcher",
-    "shortName":"DANE Toucher",
-    "icon": "app.png",
-    "version":"0.07",
+    "shortName": "DANE Toucher",
+    "version": "0.07",
     "description": "Touch enable left to right launcher in the style of the DANE Watchface",
+    "icon": "app.png",
+    "type": "launch",
     "tags": "tool,system,launcher",
-    "type":"launch",
-    "data": [
-      {"name":"dane_tcr.json"}
-    ],
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"dane_tcr.app.js","url":"app.js"},
       {"name":"dane_tcr.settings.js","url":"settings.js"}
     ],
-    "sortorder" : -10
+    "data": [{"name":"dane_tcr.json"}],
+    "sortorder": -10
   },
   {
     "id": "buffgym",
     "name": "BuffGym",
-    "icon": "buffgym.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "BuffGym is the famous 5x5 workout program for the BangleJS",
-    "tags": "tool,outdoors,gym,exercise",
+    "icon": "buffgym.png",
     "type": "app",
+    "tags": "tool,outdoors,gym,exercise",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "interface": "buffgym.html",
     "allow_emulator": false,
-    "readme": "README.md",
     "storage": [
-      {"name":"buffgym.app.js", "url": "buffgym.app.js"},
+      {"name":"buffgym.app.js","url":"buffgym.app.js"},
       {"name":"buffgym-set.js","url":"buffgym-set.js"},
       {"name":"buffgym-exercise.js","url":"buffgym-exercise.js"},
       {"name":"buffgym-workout.js","url":"buffgym-workout.js"},
@@ -1783,82 +1983,76 @@
     "id": "banglerun",
     "name": "BangleRun",
     "shortName": "BangleRun",
-    "icon": "banglerun.png",
     "version": "0.10",
-    "interface": "interface.html",
     "description": "An app for running sessions. Displays info and logs your run for later viewing.",
+    "icon": "banglerun.png",
     "tags": "run,running,fitness,outdoors",
+    "supports": ["BANGLEJS"],
+    "interface": "interface.html",
     "allow_emulator": false,
     "storage": [
-      {
-        "name": "banglerun.app.js",
-        "url": "app.js"
-      },
-      {
-        "name": "banglerun.img",
-        "url": "app-icon.js",
-        "evaluate": true
-      }
+      {"name":"banglerun.app.js","url":"app.js"},
+      {"name":"banglerun.img","url":"app-icon.js","evaluate":true}
     ]
   },
   {
     "id": "metronome",
     "name": "Metronome",
-    "icon": "metronome_icon.png",
     "version": "0.06",
-    "readme": "README.md",
     "description": "Makes the watch blinking and vibrating with a given rate",
+    "icon": "metronome_icon.png",
     "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-      {
-        "name": "metronome.app.js",
-        "url": "metronome.js"
-      },
-      {
-        "name": "metronome.img",
-        "url": "metronome-icon.js",
-        "evaluate": true
-      },
+      {"name":"metronome.app.js","url":"metronome.js"},
+      {"name":"metronome.img","url":"metronome-icon.js","evaluate":true},
       {"name":"metronome.settings.js","url":"settings.js"}
     ]
   },
-  { "id": "blackjack",
+  {
+    "id": "blackjack",
     "name": "Black Jack game",
-    "shortName":"Black Jack game",
-    "icon": "blackjack.png",
-    "version":"0.02",
+    "shortName": "Black Jack game",
+    "version": "0.02",
     "description": "Simple implementation of card game Black Jack",
+    "icon": "blackjack.png",
     "tags": "game",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"blackjack.app.js","url":"blackjack.app.js"},
       {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true}
     ]
   },
-  { "id": "hidcam",
+  {
+    "id": "hidcam",
     "name": "Camera shutter",
-    "shortName":"Cam shutter",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Cam shutter",
+    "version": "0.03",
     "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle",
-    "readme": "README.md",
+    "icon": "app.png",
     "tags": "bluetooth,tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
-        {"name":"hidcam.app.js","url":"app.js"},
-        {"name":"hidcam.img","url":"app-icon.js","evaluate":true}
+      {"name":"hidcam.app.js","url":"app.js"},
+      {"name":"hidcam.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "swlclk",
+  {
+    "id": "swlclk",
     "name": "SWL Clock / Short Wave Listner Clock",
     "shortName": "SWL Clock",
-    "icon": "swlclk.png",
-    "version":"0.02",
+    "version": "0.02",
     "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies",
+    "icon": "swlclk.png",
+    "type": "clock",
     "tags": "tool,clock",
-    "type":"clock",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
-    "allow_emulator":true,
+    "allow_emulator": true,
     "storage": [
       {"name":"swlclk.app.js","url":"app.js"},
       {"name":"swlclk.img","url":"app-icon.js","evaluate":true}
@@ -1868,11 +2062,12 @@
     "id": "rclock",
     "name": "Round clock with seconds,  minutes and date",
     "shortName": "Round Clock",
-    "icon": "app.png",
     "version": "0.06",
     "description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
-    "tags": "clock",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"rclock.app.js","url":"rclock.app.js"},
       {"name":"rclock.img","url":"app-icon.js","evaluate":true}
@@ -1882,35 +2077,40 @@
     "id": "fclock",
     "name": "fclock",
     "shortName": "F Clock",
-    "icon": "app.png",
     "version": "0.02",
     "description": "Simple design of a digital clock",
-    "tags": "clock",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"fclock.app.js","url":"fclock.app.js"},
       {"name":"fclock.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "hamloc",
+  {
+    "id": "hamloc",
     "name": "QTH Locator / Maidenhead Locator System",
     "shortName": "QTH Locator",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators",
+    "icon": "app.png",
     "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"hamloc.app.js","url":"app.js"},
       {"name":"hamloc.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "osmpoi",
+  {
+    "id": "osmpoi",
     "name": "POI Compass",
-    "icon": "app.png",
-    "version":"0.03",
+    "version": "0.03",
     "description": "Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.",
+    "icon": "app.png",
     "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "custom": "custom.html",
     "storage": [
@@ -1918,64 +2118,63 @@
       {"name":"osmpoi.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "pong",
+  {
+    "id": "pong",
     "name": "Pong",
     "shortName": "Pong",
-    "icon": "pong.png",
     "version": "0.03",
     "description": "A clone of the Atari game Pong",
-    "tags": "game",
+    "icon": "pong.png",
     "type": "app",
-    "allow_emulator": true,
+    "tags": "game",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"pong.app.js","url":"app.js"},
       {"name":"pong.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "ballmaze",
+  {
+    "id": "ballmaze",
     "name": "Ball Maze",
-    "icon": "icon.png",
     "version": "0.02",
     "description": "Navigate a ball through a maze by tilting your watch.",
-    "readme": "README.md",
-    "tags": "game",
+    "icon": "icon.png",
     "type": "app",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
-      {"name": "ballmaze.app.js","url":"app.js"},
-      {"name": "ballmaze.img","url":"icon.js","evaluate": true}
+      {"name":"ballmaze.app.js","url":"app.js"},
+      {"name":"ballmaze.img","url":"icon.js","evaluate":true}
     ],
-    "data": [
-      {"name": "ballmaze.json"}
-    ]
+    "data": [{"name":"ballmaze.json"}]
   },
-  { "id": "calendar",
+  {
+    "id": "calendar",
     "name": "Calendar",
-    "icon": "calendar.png",
     "version": "0.02",
     "description": "Simple calendar",
-    "tags": "calendar,b2",
+    "icon": "calendar.png",
+    "tags": "calendar",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-      {
-        "name": "calendar.app.js",
-        "url": "calendar.js"
-      },
-      {
-        "name": "calendar.img",
-        "url": "calendar-icon.js",
-        "evaluate": true
-      }
+      {"name":"calendar.app.js","url":"calendar.js"},
+      {"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
     ]
   },
-  { "id": "hidjoystick",
+  {
+    "id": "hidjoystick",
     "name": "Bluetooth Joystick",
     "shortName": "Joystick",
-    "icon": "app.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.",
+    "icon": "app.png",
     "tags": "bluetooth",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"hidjoystick.app.js","url":"app.js"},
       {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true}
@@ -1984,30 +2183,31 @@
   {
     "id": "largeclock",
     "name": "Large Clock",
-    "icon": "largeclock.png",
     "version": "0.10",
     "description": "A readable and informational digital watch, with date, seconds and moon phase",
-    "readme": "README.md",
-    "tags": "clock",
+    "icon": "largeclock.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-      {"name": "largeclock.app.js", "url": "largeclock.js"},
-      {"name": "largeclock.img", "url": "largeclock-icon.js", "evaluate": true},
-      {"name": "largeclock.settings.js", "url": "settings.js"}
+      {"name":"largeclock.app.js","url":"largeclock.js"},
+      {"name":"largeclock.img","url":"largeclock-icon.js","evaluate":true},
+      {"name":"largeclock.settings.js","url":"settings.js"}
     ],
-    "data": [
-      {"name":"largeclock.json"}
-    ]
+    "data": [{"name":"largeclock.json"}]
   },
-  { "id": "smtswch",
+  {
+    "id": "smtswch",
     "name": "Smart Switch",
-    "shortName":"Smart Switch",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "Smart Switch",
+    "version": "0.01",
     "description": "Using EspruinoHub, control your smart devices on and off via Bluetooth Low Energy!",
-    "tags": "bluetooth,btle,smart,switch",
+    "icon": "app.png",
     "type": "app",
+    "tags": "bluetooth,btle,smart,switch",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"smtswch.app.js","url":"app.js"},
@@ -2018,13 +2218,15 @@
       {"name":"switch-off.img","url":"switch-off.js","evaluate":true}
     ]
   },
-  { "id": "miplant",
+  {
+    "id": "miplant",
     "name": "Xiaomi Plant Sensor",
-    "shortName":"Mi Plant",
-    "icon": "app.png",
-    "version":"0.02",
+    "shortName": "Mi Plant",
+    "version": "0.02",
     "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors",
+    "icon": "app.png",
     "tags": "xiaomi,mi,plant,ble,bluetooth",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"miplant.app.js","url":"app.js"},
       {"name":"miplant.img","url":"app-icon.js","evaluate":true}
@@ -2033,76 +2235,63 @@
   {
     "id": "simpletimer",
     "name": "Timer",
-    "icon": "app.png",
     "version": "0.07",
     "description": "Simple timer, useful when playing board games or cooking",
+    "icon": "app.png",
     "tags": "timer",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-      {
-        "name": "simpletimer.app.js",
-        "url": "app.js"
-      },
-      {
-        "name": ".tfnames",
-        "url": "gesture-tfnames.js",
-        "evaluate": true
-      },
-      {
-        "name": ".tfmodel",
-        "url": "gesture-tfmodel.js",
-        "evaluate": true
-      },
-      {
-        "name": "simpletimer.img",
-        "url": "app-icon.js",
-        "evaluate": true
-      }
+      {"name":"simpletimer.app.js","url":"app.js"},
+      {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
+      {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true},
+      {"name":"simpletimer.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {
-        "name": "simpletimer.json"
-      }
-    ]
+    "data": [{"name":"simpletimer.json"}]
   },
   {
     "id": "beebclock",
     "name": "Beeb Clock",
-    "icon": "beebclock.png",
-    "version":"0.05",
+    "version": "0.05",
     "description": "Clock face that may be coincidentally familiar to BBC viewers",
-    "tags": "clock",
+    "icon": "beebclock.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "allow_emulator": true,
     "storage": [
-        {"name":"beebclock.app.js","url":"beebclock.js"},
-        {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true}
+      {"name":"beebclock.app.js","url":"beebclock.js"},
+      {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true}
     ]
   },
-  { "id": "findphone",
+  {
+    "id": "findphone",
     "name": "Find Phone",
-    "shortName":"Find Phone",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Find Phone",
+    "version": "0.03",
     "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳  Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
+    "icon": "app.png",
     "tags": "tool,android",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-        {"name":"findphone.app.js","url":"app.js"},
-        {"name":"findphone.img","url":"app-icon.js","evaluate":true}
+      {"name":"findphone.app.js","url":"app.js"},
+      {"name":"findphone.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "getup",
+  {
+    "id": "getup",
     "name": "Get Up",
-    "shortName":"Get Up",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "Get Up",
+    "version": "0.01",
     "description": "Reminds you to getup every x minutes. Sitting to long is dangerous!",
+    "icon": "app.png",
     "tags": "tools,health",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
-    "allow_emulator":true,
+    "allow_emulator": true,
     "storage": [
       {"name":"getup.app.js","url":"app.js"},
       {"name":"getup.settings.js","url":"settings.js"},
@@ -2113,43 +2302,46 @@
     "id": "gallifr",
     "name": "Time Traveller's Chronometer",
     "shortName": "Time Travel Clock",
-    "icon": "gallifr.png",
     "version": "0.02",
     "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.",
-    "tags": "clock,b2",
-    "readme": "README.md",
+    "icon": "gallifr.png",
     "type": "clock",
-    "allow_emulator":true,
-    "storage": [
-      { "name": "gallifr.app.js", "url": "app.js" },
-      { "name": "gallifr.img", "url": "app-icon.js", "evaluate": true },
-      { "name": "gallifr.settings.js", "url": "settings.js" }
-    ],
-    "data": [
-      {"name":"gallifr.json"}
-    ]
-  },
-  { "id": "rndmclk",
-    "name": "Random Clock Loader",
-    "icon": "rndmclk.png",
-    "version":"0.03",
-    "description": "Load a different clock whenever the LCD is switched on.",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"gallifr.app.js","url":"app.js"},
+      {"name":"gallifr.img","url":"app-icon.js","evaluate":true},
+      {"name":"gallifr.settings.js","url":"settings.js"}
+    ],
+    "data": [{"name":"gallifr.json"}]
+  },
+  {
+    "id": "rndmclk",
+    "name": "Random Clock Loader",
+    "version": "0.03",
+    "description": "Load a different clock whenever the LCD is switched on.",
+    "icon": "rndmclk.png",
+    "type": "widget",
     "tags": "widget,clock",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"rndmclk.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "dotmatrixclock",
+  {
+    "id": "dotmatrixclock",
     "name": "Dotmatrix Clock",
-    "icon": "dotmatrixclock.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "A clear white-on-blue dotmatrix simulated clock",
-    "tags": "clock,dotmatrix,retro",
+    "icon": "dotmatrixclock.png",
     "type": "clock",
-    "allow_emulator":true,
+    "tags": "clock,dotmatrix,retro",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"dotmatrixclock.app.js","url":"app.js"},
       {"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true}
@@ -2159,63 +2351,71 @@
     "id": "jbm8b",
     "name": "Magic 8 Ball",
     "shortName": "Magic 8 Ball",
-    "icon": "app.png",
-    "description": "A simple fortune telling app",
-    "tags": "game",
     "version": "0.03",
+    "description": "A simple fortune telling app",
+    "icon": "app.png",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
     "storage": [
-      { "name": "jbm8b.app.js", "url": "app.js" },
-      { "name": "jbm8b.img", "url": "app-icon.js",  "evaluate": true  }
-  ]
+      {"name":"jbm8b.app.js","url":"app.js"},
+      {"name":"jbm8b.img","url":"app-icon.js","evaluate":true}
+    ]
   },
   {
     "id": "jbm8b_IT",
     "name": "Magic 8 Ball Italiano",
     "shortName": "Magic 8 Ball IT",
-    "icon": "app.png",
+    "version": "0.01",
     "description": "La palla predice il futuro",
+    "icon": "app.png",
     "tags": "game",
-    "version": "0.01",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
-      { "name": "jbm8b_IT.app.js", "url": "app.js" },
-      { "name": "jbm8b_IT.img", "url": "app-icon.js",  "evaluate": true  }
-  ]
-  },
-  { "id": "BLEcontroller",
-    "name": "BLE Customisable Controller with Joystick",
-    "shortName": "BLE Controller",
-    "icon": "BLEcontroller.png",
-    "version": "0.01",
-    "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.",
-    "tags": "tool,bluetooth",
-    "readme": "README.md",
-    "allow_emulator":false,
-    "storage": [
-      { "name": "BLEcontroller.app.js", "url": "app.js" },
-      { "name": "BLEcontroller.img", "url": "app-icon.js", "evaluate": true }
+      {"name":"jbm8b_IT.app.js","url":"app.js"},
+      {"name":"jbm8b_IT.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widviz",
+  {
+    "id": "BLEcontroller",
+    "name": "BLE Customisable Controller with Joystick",
+    "shortName": "BLE Controller",
+    "version": "0.01",
+    "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.",
+    "icon": "BLEcontroller.png",
+    "tags": "tool,bluetooth",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": false,
+    "storage": [
+      {"name":"BLEcontroller.app.js","url":"app.js"},
+      {"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "widviz",
     "name": "Widget Visibility Widget",
-    "shortName":"Viz Widget",
-    "icon": "eye.png",
-    "version":"0.02",
+    "shortName": "Viz Widget",
+    "version": "0.02",
     "description": "Swipe left to hide top bar widgets, swipe right to redisplay.",
-    "tags": "widget",
+    "icon": "eye.png",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widviz.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "binclock",
+  {
+    "id": "binclock",
     "name": "Binary Clock",
-    "shortName":"Binary Clock",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Binary Clock",
+    "version": "0.03",
     "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.",
-    "tags": "clock,binary",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock,binary",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"binclock.app.js","url":"app.js"},
       {"name":"binclock.img","url":"app-icon.js","evaluate":true}
@@ -2224,25 +2424,28 @@
   {
     "id": "pizzatimer",
     "name": "Pizza Timer",
-    "shortName":"Pizza Timer",
-    "icon": "pizza.png",
-    "version":"0.01",
+    "shortName": "Pizza Timer",
+    "version": "0.01",
     "description": "A timer app for when you cook Pizza. Some say it can also time other things",
+    "icon": "pizza.png",
     "tags": "timer,tool,pizza",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"pizzatimer.app.js","url":"app.js"},
       {"name":"pizzatimer.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "animclk",
+  {
+    "id": "animclk",
     "name": "Animated Clock",
-    "shortName":"Anim Clock",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Anim Clock",
+    "version": "0.03",
     "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art",
-    "tags": "clock,animated,bno2",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock,animated",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"animclk.app.js","url":"app.js"},
       {"name":"animclk.pixels1","url":"animclk.pixels1"},
@@ -2251,14 +2454,16 @@
       {"name":"animclk.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "analogimgclk",
+  {
+    "id": "analogimgclk",
     "name": "Analog Clock (Image background)",
-    "shortName":"Analog Clock",
-    "icon": "app.png",
-    "version":"0.03",
+    "shortName": "Analog Clock",
+    "version": "0.03",
     "description": "An analog clock with an image background",
-    "tags": "clock,bno2",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"analogimgclk.app.js","url":"app.js"},
       {"name":"analogimgclk.bg.img","url":"bg.img"},
@@ -2268,116 +2473,131 @@
   {
     "id": "verticalface",
     "name": "Vertical watch face",
-    "shortName":"Vertical Face",
-    "icon": "app.png",
-    "version":"0.09",
+    "shortName": "Vertical Face",
+    "version": "0.09",
     "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1",
+    "icon": "app.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"verticalface.app.js","url":"app.js"},
       {"name":"verticalface.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "sleepphasealarm",
+  {
+    "id": "sleepphasealarm",
     "name": "SleepPhaseAlarm",
-    "shortName":"SleepPhaseAlarm",
-    "icon": "app.png",
-    "version":"0.02",
+    "shortName": "SleepPhaseAlarm",
+    "version": "0.02",
     "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
+    "icon": "app.png",
     "tags": "alarm",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"sleepphasealarm.app.js","url":"app.js"},
       {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "life",
+  {
+    "id": "life",
     "name": "Game of Life",
-    "icon": "life.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "Conway's Game of Life - 16x16 board",
+    "icon": "life.png",
     "tags": "game",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"life.app.js","url":"life.min.js"},
       {"name":"life.img","url":"life-icon.js","evaluate":true}
     ]
   },
-   { "id": "magnav",
+  {
+    "id": "magnav",
     "name": "Navigation Compass",
-    "icon": "magnav.png",
-    "version":"0.04",
+    "version": "0.04",
     "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.",
-    "readme": "README.md",
+    "icon": "magnav.png",
     "tags": "tool,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"magnav.app.js","url":"magnav.min.js"},
       {"name":"magnav.img","url":"magnav-icon.js","evaluate":true}
     ],
-    "data":[{"name":"magnav.json"}]
+    "data": [{"name":"magnav.json"}]
   },
-  { "id": "gpspoilog",
+  {
+    "id": "gpspoilog",
     "name": "GPS POI Logger",
-    "shortName":"GPS POI Log",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "GPS POI Log",
+    "version": "0.01",
     "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial",
+    "icon": "app.png",
     "tags": "outdoors",
+    "supports": ["BANGLEJS"],
     "interface": "interface.html",
     "storage": [
       {"name":"gpspoilog.app.js","url":"app.js"},
       {"name":"gpspoilog.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "miclock2",
+  {
+    "id": "miclock2",
     "name": "Mixed Clock 2",
-    "icon": "clock-mixed.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.",
+    "icon": "clock-mixed.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
     "storage": [
       {"name":"miclock2.app.js","url":"clock-mixed.js"},
       {"name":"miclock2.img","url":"clock-mixed-icon.js","evaluate":true}
     ]
   },
-  { "id": "1button",
+  {
+    "id": "1button",
     "name": "One-Button-Tracker",
-    "icon": "widget.png",
-    "version":"0.01",
-    "interface": "interface.html",
+    "version": "0.01",
     "description": "A widget that turns BTN1 into a tracker, records time of button press/release.",
-    "tags": "tool,quantifiedself,widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "tool,quantifiedself,widget",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "interface": "interface.html",
     "storage": [
       {"name":"1button.wid.js","url":"widget.js"}
     ],
-    "data": [
-      {"name":"one_button_presses.csv","storageFile": true}
-    ]
+    "data": [{"name":"one_button_presses.csv","storageFile":true}]
   },
-  { "id": "gpsautotime",
+  {
+    "id": "gpsautotime",
     "name": "GPS auto time",
-    "shortName":"GPS auto time",
-    "icon": "widget.png",
-    "version":"0.01",
+    "shortName": "GPS auto time",
+    "version": "0.01",
     "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.",
-    "tags": "widget,gps",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "widget,gps",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"gpsautotime.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "espruinoctrl",
+  {
+    "id": "espruinoctrl",
     "name": "Espruino Control",
-    "shortName":"Espruino Ctrl",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "Espruino Ctrl",
+    "version": "0.01",
     "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "custom": "custom.html",
     "storage": [
@@ -2385,15 +2605,17 @@
       {"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "multiclock",
+  {
+    "id": "multiclock",
     "name": "Multi Clock",
-    "icon": "multiclock.png",
-    "version":"0.13",
+    "version": "0.13",
     "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3",
-    "readme": "README.md",
+    "icon": "multiclock.png",
+    "type": "clock",
     "tags": "clock",
-    "type":"clock",
-    "allow_emulator":true,
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
     "storage": [
       {"name":"multiclock.app.js","url":"clock.js"},
       {"name":"big.face.js","url":"big.js"},
@@ -2405,152 +2627,157 @@
       {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true}
     ]
   },
-   { "id": "widancs",
+  {
+    "id": "widancs",
     "name": "Apple Notification Widget",
-    "shortName":"ANCS Widget",
-    "icon": "widget.png",
-    "version":"0.07",
+    "shortName": "ANCS Widget",
+    "version": "0.07",
     "description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps",
-    "readme": "README.md",
-    "tags": "widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "storage": [
       {"name":"widancs.wid.js","url":"ancs.min.js"},
       {"name":"widancs.settings.js","url":"settings.js"}
     ]
   },
-  { "id": "accelrec",
+  {
+    "id": "accelrec",
     "name": "Acceleration Recorder",
-    "shortName":"Accel Rec",
-    "icon": "app.png",
-    "version":"0.02",
-    "interface": "interface.html",
+    "shortName": "Accel Rec",
+    "version": "0.02",
     "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "interface": "interface.html",
     "storage": [
       {"name":"accelrec.app.js","url":"app.js"},
       {"name":"accelrec.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {"wildcard":"accelrec.?.csv" }
-    ]
+    "data": [{"wildcard":"accelrec.?.csv"}]
   },
-  { "id": "accellog",
+  {
+    "id": "accellog",
     "name": "Acceleration Logger",
-    "shortName":"Accel Log",
-    "icon": "app.png",
-    "version":"0.03",
-    "interface": "interface.html",
+    "shortName": "Accel Log",
+    "version": "0.03",
     "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
-    "tags": "outdoor,b2",
+    "icon": "app.png",
+    "tags": "outdoor",
+    "supports": ["BANGLEJS","BANGLEJS2"],
     "readme": "README.md",
+    "interface": "interface.html",
     "storage": [
       {"name":"accellog.app.js","url":"app.js"},
       {"name":"accellog.img","url":"app-icon.js","evaluate":true}
     ],
-    "data": [
-      {"wildcard":"accellog.?.csv" }
-    ]
+    "data": [{"wildcard":"accellog.?.csv"}]
   },
   {
     "id": "cprassist",
-    "name":"CPR Assist",
-    "icon":"cprassist-icon.png",
+    "name": "CPR Assist",
     "version": "0.01",
-    "readme": "README.md",
     "description": "Provides assistance while performing a CPR",
+    "icon": "cprassist-icon.png",
     "tags": "tool,firstaid",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
     "allow_emulator": true,
     "storage": [
-      {
-        "name": "cprassist.app.js",
-        "url": "cprassist.js"
-      },
-      {
-        "name": "cprassist.img",
-        "url": "cprassist-icon.js",
-        "evaluate": true
-      },
-      {
-        "name": "cprassist.settings.js",
-        "url": "settings.js"
-      }
+      {"name":"cprassist.app.js","url":"cprassist.js"},
+      {"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true},
+      {"name":"cprassist.settings.js","url":"settings.js"}
     ]
   },
-  { "id": "osgridref",
+  {
+    "id": "osgridref",
     "name": "Ordnance Survey Grid Reference",
-    "shortName":"OS Grid ref",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "OS Grid ref",
+    "version": "0.01",
     "description": "Displays the UK Ordnance Survey grid reference of your current GPS location. Useful when in the United Kingdom with an Ordnance Survey map",
+    "icon": "app.png",
     "tags": "outdoors,gps",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"osgridref.app.js","url":"app.js"},
       {"name":"osgridref.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "openseizure",
+  {
+    "id": "openseizure",
     "name": "OpenSeizureDetector Widget",
-    "shortName":"Short Name",
-    "icon": "widget.png",
-    "version":"0.01",
+    "shortName": "Short Name",
+    "version": "0.01",
     "description": "[BETA!] A widget to work alongside [OpenSeizureDetector](https://www.openseizuredetector.org.uk/)",
-    "tags": "widget",
+    "icon": "widget.png",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"openseizure.wid.js","url":"widget.js"}
     ]
   },
-  {"id": "counter",
-  "name": "Counter",
-  "icon": "counter_icon.png",
-  "version": "0.03",
-  "description": "Simple counter",
-  "tags": "tool",
-  "allow_emulator": true,
-  "storage": [
-    {"name": "counter.app.js", "url": "counter.js"},
-    {"name": "counter.img", "url": "counter-icon.js", "evaluate": true}
-   ]
+  {
+    "id": "counter",
+    "name": "Counter",
+    "version": "0.03",
+    "description": "Simple counter",
+    "icon": "counter_icon.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"counter.app.js","url":"counter.js"},
+      {"name":"counter.img","url":"counter-icon.js","evaluate":true}
+    ]
   },
-  { "id": "bootgattbat",
+  {
+    "id": "bootgattbat",
     "name": "BLE GATT Battery Service",
-    "shortName":"BLE Battery Service",
-    "icon": "bluetooth.png",
-    "version":"0.01",
+    "shortName": "BLE Battery Service",
+    "version": "0.01",
     "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
-    "tags": "battery,ble,bluetooth,gatt",
+    "icon": "bluetooth.png",
     "type": "bootloader",
+    "tags": "battery,ble,bluetooth,gatt",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"gattbat.boot.js","url":"boot.js"}
     ]
   },
-  { "id": "viewstl",
-  "name": "STL file viewer",
-  "shortName":"ViewSTL",
-  "icon": "icons8-octahedron-48.png",
-  "version":"0.02",
-  "description": "This app allows you to view STL 3D models on your watch",
-  "tags": "tool",
-  "readme": "README.md",
-  "storage": [
-    {"name":"viewstl.app.js","url":"viewstl.min.js"},
-    {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true},
-    {"name":"tetra.stl","url":"tetra.stl"},
-    {"name":"cube.stl","url":"cube.stl"},
-    {"name":"icosa.stl","url":"icosa.stl"}
-   ]
+  {
+    "id": "viewstl",
+    "name": "STL file viewer",
+    "shortName": "ViewSTL",
+    "version": "0.02",
+    "description": "This app allows you to view STL 3D models on your watch",
+    "icon": "icons8-octahedron-48.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"viewstl.app.js","url":"viewstl.min.js"},
+      {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true},
+      {"name":"tetra.stl","url":"tetra.stl"},
+      {"name":"cube.stl","url":"cube.stl"},
+      {"name":"icosa.stl","url":"icosa.stl"}
+    ]
   },
-  { "id": "cscsensor",
+  {
+    "id": "cscsensor",
     "name": "Cycling speed sensor",
-    "shortName":"CSCSensor",
-    "icon": "icons8-cycling-48.png",
-    "version":"0.05",
+    "shortName": "CSCSensor",
+    "version": "0.05",
     "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
+    "icon": "icons8-cycling-48.png",
     "tags": "outdoors,exercise,ble,bluetooth",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"cscsensor.app.js","url":"cscsensor.app.js"},
@@ -2558,70 +2785,78 @@
       {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true}
     ]
   },
-  { "id": "fileman",
+  {
+    "id": "fileman",
     "name": "File manager",
-    "shortName":"FileManager",
-    "icon": "icons8-filing-cabinet-48.png",
-    "version":"0.03",
+    "shortName": "FileManager",
+    "version": "0.03",
     "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files",
+    "icon": "icons8-filing-cabinet-48.png",
     "tags": "tools",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"fileman.app.js","url":"fileman.app.js"},
       {"name":"fileman.img","url":"fileman-icon.js","evaluate":true}
     ]
   },
- { "id": "worldclock",
+  {
+    "id": "worldclock",
     "name": "World Clock - 4 time zones",
-    "shortName":"World Clock",
-    "icon": "app.png",
-    "version":"0.04",
+    "shortName": "World Clock",
+    "version": "0.04",
     "description": "Current time zone plus up to four others",
+    "icon": "app.png",
+    "type": "clock",
     "tags": "clock",
-    "type" : "clock",
-    "custom": "custom.html",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
+    "custom": "custom.html",
     "storage": [
       {"name":"worldclock.app.js","url":"app.js"},
       {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
     ],
-    "data": [
-      {"name":"worldclock.settings.json"}
+    "data": [{"name":"worldclock.settings.json"}]
+  },
+  {
+    "id": "digiclock",
+    "name": "Digital Clock Face",
+    "shortName": "Digi Clock",
+    "version": "0.02",
+    "description": "A simple digital clock with the time, day, month, and year",
+    "icon": "digiclock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"digiclock.app.js","url":"digiclock.js"},
+      {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true}
     ]
- },
-{ "id": "digiclock",
-  "name": "Digital Clock Face",
-  "shortName":"Digi Clock",
-  "icon": "digiclock.png",
-  "version":"0.02",
-  "description": "A simple digital clock with the time, day, month, and year",
-  "tags": "clock,bno2",
-  "type" : "clock",
-  "storage": [
-    {"name":"digiclock.app.js","url":"digiclock.js"},
-    {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true}
- ]
-},
-  { "id": "dsdrelay",
+  },
+  {
+    "id": "dsdrelay",
     "name": "DSD BLE Relay controller",
-    "shortName":"DSDRelay",
-    "icon": "icons8-relay-48.png",
-    "version":"0.01",
+    "shortName": "DSDRelay",
+    "version": "0.01",
     "description": "Control BLE relay board from the watch",
+    "icon": "icons8-relay-48.png",
     "tags": "ble,bluetooth",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"},
       {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true}
     ]
   },
-  { "id": "mandel",
+  {
+    "id": "mandel",
     "name": "Mandelbrot",
-    "shortName":"Mandel",
-    "icon": "mandel.png",
-    "version":"0.01",
+    "shortName": "Mandel",
+    "version": "0.01",
     "description": "Draw a zoomable Mandelbrot set",
+    "icon": "mandel.png",
     "tags": "game",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"mandel.app.js","url":"mandel.min.js"},
@@ -2631,997 +2866,1100 @@
   {
     "id": "petrock",
     "name": "Pet rock",
-    "icon": "petrock.png",
     "version": "0.02",
     "description": "A virtual pet rock with wobbly eyes",
-    "tags": "game",
+    "icon": "petrock.png",
     "type": "app",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
     "storage": [
-      {"name": "petrock.app.js", "url": "app.js"},
-      {"name": "petrock.img", "url": "app-icon.js", "evaluate": true}
+      {"name":"petrock.app.js","url":"app.js"},
+      {"name":"petrock.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "smartibot",
+  {
+    "id": "smartibot",
     "name": "Smartibot controller",
-    "shortName":"Smartibot",
-    "icon": "app.png",
-    "version":"0.01",
+    "shortName": "Smartibot",
+    "version": "0.01",
     "description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js",
+    "icon": "app.png",
     "tags": "",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"smartibot.app.js","url":"app.js"},
       {"name":"smartibot.img","url":"app-icon.js","evaluate":true}
     ]
   },
-  { "id": "widncr",
+  {
+    "id": "widncr",
     "name": "NCR Logo Widget",
-    "icon": "widget.png",
-    "version":"0.01",
+    "version": "0.01",
     "description": "Show the NodeConf Remote logo in the top left",
+    "icon": "widget.png",
+    "type": "widget",
     "tags": "widget",
-    "type":"widget",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"widncr.wid.js","url":"widget.js"}
     ]
   },
-  { "id": "ncrclk",
+  {
+    "id": "ncrclk",
     "name": "NCR Clock",
-    "shortName":"NCR Clock",
-    "icon": "app.png",
-    "version":"0.02",
+    "shortName": "NCR Clock",
+    "version": "0.02",
     "description": "NodeConf Remote clock",
-    "tags": "clock",
+    "icon": "app.png",
     "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
     "storage": [
       {"name":"ncrclk.app.js","url":"app.js"},
       {"name":"ncrclk.img","url":"app-icon.js","evaluate":true}
     ]
   },
-{ "id": "isoclock",
-  "name": "ISO Compliant Clock Face",
-  "shortName":"ISO Clock",
-  "icon": "isoclock.png",
-  "version":"0.02",
-  "description": "Tweaked fork of digiclock for ISO date and time",
-  "tags": "clock",
-  "type" : "clock",
-  "storage": [
-    {"name":"isoclock.app.js","url":"isoclock.js"},
-    {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true}
- ]
-},
-{ "id": "gpstimeserver",
-  "name": "GPS Time Server",
-  "icon": "widget.png",
-  "version":"0.01",
-  "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.",
-  "tags": "widget",
-  "type": "widget",
-  "readme": "README.md",
-  "storage": [
-    {"name":"gpstimeserver.wid.js","url":"widget.js"}
-  ]
-},
-{ "id": "tilthydro",
-  "name": "Tilt Hydrometer Display",
-  "shortName":"Tilt Hydro",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)",
-  "tags": "tools,bluetooth",
-  "storage": [
-    {"name":"tilthydro.app.js","url":"app.js"},
-    {"name":"tilthydro.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "supmariodark",
-  "name": "Super mario clock night mode",
-  "shortName":"supmariodark",
-  "icon": "supmariodark.png",
-  "version":"0.01",
-  "description": "Super mario clock in night mode",
-  "tags": "clock",
-  "type" : "clock",
-  "storage": [
-    {"name":"supmariodark.app.js","url":"supmariodark.js"},
-    {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true},
-    {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"},
-    {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"},
-    {"name":"banner-up.img","url":"banner-up.js","evaluate":true},
-    {"name":"banner-down.img","url":"banner-down.js","evaluate":true},
-    {"name":"brick2.img","url":"brick2.js","evaluate":true},
-    {"name":"enemy.img","url":"enemy.js","evaluate":true},
-    {"name":"flower.img","url":"flower.js","evaluate":true},
-    {"name":"flower_b.img","url":"flower_b.js","evaluate":true},
-    {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true},
-    {"name":"pipe.img","url":"pipe.js","evaluate":true}
-  ]
-},
-{ "id": "gmeter",
-  "name": "G-Meter",
-  "shortName":"G-Meter",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "Simple G-Meter",
-  "tags": "",
-  "storage": [
-    {"name":"gmeter.app.js","url":"app.js"},
-    {"name":"gmeter.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "dtlaunch",
-  "name": "Desktop Launcher",
-  "icon": "icon.png",
-  "version":"0.04",
-  "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.",
-  "readme": "README.md",
-  "tags": "tool,system,launcher",
-  "type":"launch",
-  "storage": [
-    {"name":"dtlaunch.app.js","url":"app.js"},
-    {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "HRV",
-  "name": "Heart Rate Variability monitor",
-  "shortName":"HRV monitor",
-  "icon": "hrv.png",
-  "version":"0.04",
-  "description": "Heart Rate Variability monitor, see Readme for more info",
-  "tags": "",
-  "readme": "README.md",
-  "storage": [
-    {"name":"HRV.app.js","url":"app.js"},
-    {"name":"HRV.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "hardalarm",
-  "name": "Hard Alarm",
-  "shortName":"HardAlarm",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "Make sure you wake up! Count to the right number to turn off the alarm",
-  "tags": "tool,alarm,widget",
-  "storage": [
-    {"name":"hardalarm.app.js","url":"app.js"},
-    {"name":"hardalarm.boot.js","url":"boot.js"},
-    {"name":"hardalarm.js","url":"hardalarm.js"},
-    {"name":"hardalarm.img","url":"app-icon.js","evaluate":true},
-    {"name":"hardalarm.wid.js","url":"widget.js"}
-  ],
-  "data": [
-    {"name":"hardalarm.json"}
-  ]
-},
-{ "id": "edisonsball",
-  "name": "Edison's Ball",
-  "shortName":"Edison's Ball",
-  "icon": "app-icon.png",
-  "version":"0.01",
-  "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness",
-  "tags": "",
-  "readme": "README.md",
-  "storage": [
-    {"name":"edisonsball.app.js","url":"app.js"},
-    {"name":"edisonsball.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "hrrawexp",
-  "name": "HRM Data Exporter",
-  "shortName":"HRM Data Exporter",
-  "icon": "app-icon.png",
-  "version":"0.01",
-  "description": "export raw hrm signal data to a csv file",
-  "tags": "",
-  "readme": "README.md",
-  "interface": "interface.html",
-  "storage": [
-    {"name":"hrrawexp.app.js","url":"app.js"},
-    {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "breath",
-  "name": "Breathing App",
-  "shortName":"Breathing App",
-  "icon": "app-icon.png",
-  "version":"0.01",
-  "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR",
-  "tags": "tools,health",
-  "readme": "README.md",
-  "storage": [
-    {"name":"breath.app.js","url":"app.js"},
-    {"name":"breath.img","url":"app-icon.js","evaluate":true}
-  ],
-  "data": [
-    {"name":"breath.settings.json","url":"settings.json"}
-  ]
-},
-{ "id": "lazyclock",
-  "name": "Lazy Clock",
-  "icon": "lazyclock.png",
-  "version":"0.03",
-  "readme": "README.md",
-  "description": "Tells the time, roughly",
-  "tags": "clock",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"lazyclock.app.js","url":"lazyclock-app.js"},
-    {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true}
-  ]
-},
-{ "id": "astral",
-  "name": "Astral Clock",
-  "icon": "app-icon.png",
-  "version":"0.03",
-  "readme": "README.md",
-  "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
-  "tags": "clock",
-  "type":"clock",
-  "storage": [
-    {"name":"astral.app.js","url":"app.js"},
-    {"name":"astral.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "alpinenav",
-  "name": "Alpine Nav",
-  "icon": "app-icon.png",
-  "version":"0.01",
-  "readme": "README.md",
-  "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
-  "tags": "outdoors,gps",
-  "storage": [
-    {"name":"alpinenav.app.js","url":"app.js"},
-    {"name":"alpinenav.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "lifeclk",
-  "name": "Game of Life Clock",
-  "shortName":"Conway's Clock",
-  "icon": "app.png",
-  "version":"0.06",
-  "description": "Modification and clockification of Conway's Game of Life",
-  "tags": "clock",
-  "type" : "clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"lifeclk.app.js","url":"app.min.js"},
-    {"name":"lifeclk.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "speedalt",
-  "name": "GPS Adventure Sports",
-  "shortName":"GPS Adv Sport",
-  "icon": "app.png",
-  "version":"1.02",
-  "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
-  "tags": "tool,outdoors",
-  "type":"app",
-  "allow_emulator":true,
-  "readme": "README.md",
-  "storage": [
-    {"name":"speedalt.app.js","url":"app.js"},
-    {"name":"speedalt.img","url":"app-icon.js","evaluate":true},
-    {"name":"speedalt.settings.js","url":"settings.js"}
-  ],
- "data": [
-      {"name":"speedalt.json"}
+  {
+    "id": "isoclock",
+    "name": "ISO Compliant Clock Face",
+    "shortName": "ISO Clock",
+    "version": "0.02",
+    "description": "Tweaked fork of digiclock for ISO date and time",
+    "icon": "isoclock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"isoclock.app.js","url":"isoclock.js"},
+      {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true}
     ]
-},
-{ "id": "de-stress",
-  "name": "De-Stress",
-  "shortName":"De-Stress",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "Simple haptic heartbeat",
-  "storage": [
-    {"name":"de-stress.app.js","url":"app.js"},
-    {"name":"de-stress.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "mclockplus",
-  "name": "Morph Clock+",
-  "shortName":"Morph Clock+",
-  "icon": "mclockplus.png",
-  "version":"0.02",
-  "description": "Morphing Clock with more readable seconds and date and additional stopwatch",
-  "tags": "clock",
-  "type": "clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"mclockplus.app.js","url":"mclockplus.app.js"},
-    {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true}
-  ]
-},
-{ "id": "intervals",
-  "name": "Intervals App",
-  "shortName":"Intervals",
-  "icon": "intervals.png",
-  "version":"0.01",
-  "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.",
-  "tags": "",
-  "storage": [
-    {"name":"intervals.app.js","url":"intervals.app.js"},
-    {"name":"intervals.img","url":"intervals-icon.js","evaluate":true}
-  ]
-},
-{ "id": "planetarium",
-  "name": "Planetarium",
-  "shortName":"Planetarium",
-  "icon": "planetarium.png",
-  "readme": "README.md",
-  "version":"0.03",
-  "description": "Planetarium showing up to 500 stars using the watch location and time",
-  "tags": "",
-  "storage": [
-    {"name":"planetarium.app.js","url":"planetarium.app.js"},
-    {"name":"planetarium.data.csv","url":"planetarium.data.csv"},
-    {"name":"planetarium.const.csv","url":"planetarium.const.csv"},
-    {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"},
-    {"name":"planetarium.settings.js","url":"settings.js"},
-    {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true}
-  ],
-  "data":[
-    {"name":"planetarium.json"}
-  ]
-},
-{ "id": "tapelauncher",
-  "name": "Tape Launcher",
-  "icon": "icon.png",
-  "version":"0.02",
-  "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons",
-  "readme": "README.md",
-  "tags": "tool,system,launcher",
-  "type":"launch",
-  "storage": [
-    {"name":"tapelauncher.app.js","url":"app.js"},
-    {"name":"tapelauncher.img","url":"icon.js","evaluate":true}
-  ]
-},
-{ "id": "oblique",
-  "name": "Oblique Strategies",
-  "icon": "eno.png",
-  "version": "0.01",
-  "description": "Oblique Strategies for creativity. Copied from Brian Eno.",
-  "tags": "tool",
-  "storage": [
-    {"name":"oblique.app.js","url":"app.js"},
-    {"name":"oblique.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "testuserinput",
-  "name": "Test User Input",
-  "shortName":"Test User Input",
-  "icon": "app.png",
-  "version":"0.06",
-  "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.",
-  "readme": "README.md",
-  "tags": "input,interface,buttons,touch,UI",
-  "storage": [
-    {"name":"testuserinput.app.js","url":"app.js"},
-    {"name":"testuserinput.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "gpssetup",
-  "name": "GPS Setup",
-  "shortName":"GPS Setup",
-  "icon": "gpssetup.png",
-  "version":"0.02",
-  "description": "Configure the GPS power options and store them in the GPS nvram",
-  "tags": "gps,tools,outdoors,bno2",
-  "readme": "README.md",
-  "storage": [
-    {"name":"gpssetup","url":"gpssetup.js"},
-    {"name":"gpssetup.settings.js","url":"settings.js"},
-    {"name":"gpssetup.app.js","url":"app.js"},
-    {"name":"gpssetup.img","url":"icon.js","evaluate":true}
-  ],
-  "data": [
-    {"name":"gpssetup.settings.json","url":"settings.json"}
-  ]
-},
-{ "id": "walkersclock",
-  "name": "Walkers Clock",
-  "shortName":"Walkers Clock",
-  "icon": "walkersclock48.png",
-  "version":"0.04",
-  "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference",
-  "type":"clock",
-  "tags": "clock, gps, tools, outdoors",
-  "readme": "README.md",
-  "storage": [
-    {"name":"walkersclock.app.js","url":"app.js"},
-    {"name":"walkersclock.img","url":"icon.js","evaluate":true}
-  ]
-},
-{ "id": "widgps",
-  "name": "GPS Widget",
-  "icon": "widget.png",
-  "version":"0.02",
-  "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later",
-  "tags": "widget,gps",
-  "type":"widget",
-  "readme": "README.md",
-  "storage": [
-    {"name":"widgps.wid.js","url":"widget.js"}
-  ]
-},
-{ "id": "widhrt",
-  "name": "HRM Widget",
-  "icon": "widget.png",
-  "version":"0.02",
-  "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later",
-  "tags": "widget, hrm",
-  "type":"widget",
-  "readme": "README.md",
-  "storage": [
-    {"name":"widhrt.wid.js","url":"widget.js"}
-  ]
-},
-{ "id": "countdowntimer",
-  "name" : "Countdown Timer",
-  "icon": "countdowntimer.png",
-  "version": "0.01",
-  "description": "A simple countdown timer with a focus on usability",
-  "tags": "timer, tool",
-  "readme": "README.md",
-  "storage": [
-    {"name": "countdowntimer.app.js", "url": "countdowntimer.js"},
-    {"name": "countdowntimer.img", "url": "countdowntimer-icon.js", "evaluate": true}
-  ]
-},
-{ "id": "helloworld",
-  "name": "hello, world!",
-  "shortName":"hello world",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "A cross cultural hello world!/hola mundo! app with colors and languages",
-  "readme": "README.md",
-  "tags": "input,interface,buttons,touch",
-  "storage": [
-    {"name":"helloworld.app.js","url":"app.js"},
-    {"name":"helloworld.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "widcom",
-  "name": "Compass Widget",
-  "icon": "widget.png",
-  "version":"0.01",
-  "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later",
-  "tags": "widget, compass",
-  "type":"widget",
-  "readme": "README.md",
-  "storage": [
-    {"name":"widcom.wid.js","url":"widget.js"}
-  ]
-},
-{ "id": "arrow",
-  "name": "Arrow Compass",
-  "icon": "arrow.png",
-  "type":"app",
-  "version":"0.04",
-  "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
-  "tags": "tool,outdoors",
-  "readme": "README.md",
-  "storage": [
-    {"name":"arrow.app.js","url":"app.js"},
-    {"name":"arrow.img","url":"icon.js","evaluate":true}
-  ]
-},
-{ "id": "waypointer",
-  "name": "Way Pointer",
-  "icon": "waypointer.png",
-  "version":"0.01",
-  "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
-  "tags": "tool,outdoors,gps",
-  "readme": "README.md",
-  "interface":"waypoints.html",
-  "storage": [
-    {"name":"waypointer.app.js","url":"app.js"},
-    {"name":"waypointer.img","url":"icon.js","evaluate":true}
-  ],
-  "data": [
-    {"name":"waypoints.json","url":"waypoints.json"}
-  ]
-},
-{ "id": "color_catalog",
-  "name": "Colors Catalog",
-  "shortName":"Colors Catalog",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
-  "readme": "README.md",
-  "tags": "Color,input,buttons,touch,UI,bno2",
-  "storage": [
-    {"name":"color_catalog.app.js","url":"app.js"},
-    {"name":"color_catalog.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "UI4swatch",
-  "name": "UI 4 swatch",
-  "shortName":"UI 4 swatch",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
-  "readme": "README.md",
-  "tags": "Color, input,buttons,touch,UI",
-  "storage": [
-    {"name":"UI4swatch.app.js","url":"app.js"},
-    {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "simplest",
-  "name": "Simplest Clock",
-  "icon": "simplest.png",
-  "version":"0.02",
-  "description": "The simplest working clock, acts as a tutorial piece",
-  "tags": "clock",
-  "type":"clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"simplest.app.js","url":"app.js"},
-    {"name":"simplest.img","url":"icon.js","evaluate":true}
-  ]
-},
-{ "id": "stepo",
-  "name": "Stepometer Clock",
-  "icon": "stepo.png",
-  "version":"0.03",
-  "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed",
-  "tags": "clock",
-  "type":"clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"stepo.app.js","url":"app.js"},
-    {"name":"stepo.img","url":"icon.js","evaluate":true}
-  ]
-},
-{ "id": "gbmusic",
-  "name": "Gadgetbridge Music Controls",
-  "shortName":"Music Controls",
-  "icon": "icon.png",
-  "version":"0.05",
-  "description": "Control the music on your Gadgetbridge-connected phone",
-  "tags": "tools,bluetooth,gadgetbridge,music",
-  "type":"app",
-  "allow_emulator": false,
-  "readme": "README.md",
-  "storage": [
-    {"name":"gbmusic.app.js","url":"app.js"},
-    {"name":"gbmusic.settings.js","url":"settings.js"},
-    {"name":"gbmusic.wid.js","url":"widget.js"},
-    {"name":"gbmusic.img","url":"icon.js","evaluate":true}
-  ],
-  "data": [
-    {"name":"gbmusic.json"},
-    {"name":"gbmusic.load.json"}
-  ]
-},
-{
-  "id": "battleship",
-  "name":"Battleship",
-  "icon":"battleship-icon.png",
-  "version": "0.01",
-  "readme": "README.md",
-  "description": "The classic game of battleship",
-  "tags": "game",
-  "allow_emulator": true,
-  "storage": [
-    {
-      "name": "battleship.app.js",
-      "url": "battleship.js"
-    },
-    {
-      "name": "battleship.img",
-      "url": "battleship-icon.js",
-      "evaluate": true
-    }
-  ]
-},
-{ "id": "kitchen",
-  "name": "Kitchen Combo",
-  "icon": "kitchen.png",
-  "version":"0.13",
-  "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
-  "tags": "tool,outdoors,gps",
-  "type":"clock",
-  "readme": "README.md",
-  "interface":"waypoints.html",
-  "storage": [
-    {"name":"kitchen.app.js","url":"kitchen.app.js"},
-    {"name":"stepo2.kit.js","url":"stepo2.kit.js"},
-    {"name":"swatch.kit.js","url":"swatch.kit.js"},
-    {"name":"gps.kit.js","url":"gps.kit.js"},
-    {"name":"compass.kit.js","url":"compass.kit.js"},
-    {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
-  ],
-  "data": [
-      {"name":"waypoints.json","url":"waypoints.json"}
-  ]
-},
-{ "id": "banglebridge",
-    "name": "BangleBridge",
-    "shortName":"BangleBridge",
+  },
+  {
+    "id": "gpstimeserver",
+    "name": "GPS Time Server",
+    "version": "0.01",
+    "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.",
     "icon": "widget.png",
-    "version":"0.01",
-    "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
-    "tags": "widget",
     "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"gpstimeserver.wid.js","url":"widget.js"}
+    ]
+  },
+  {
+    "id": "tilthydro",
+    "name": "Tilt Hydrometer Display",
+    "shortName": "Tilt Hydro",
+    "version": "0.01",
+    "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)",
+    "icon": "app.png",
+    "tags": "tools,bluetooth",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"tilthydro.app.js","url":"app.js"},
+      {"name":"tilthydro.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "supmariodark",
+    "name": "Super mario clock night mode",
+    "shortName": "supmariodark",
+    "version": "0.01",
+    "description": "Super mario clock in night mode",
+    "icon": "supmariodark.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"supmariodark.app.js","url":"supmariodark.js"},
+      {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true},
+      {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"},
+      {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"},
+      {"name":"banner-up.img","url":"banner-up.js","evaluate":true},
+      {"name":"banner-down.img","url":"banner-down.js","evaluate":true},
+      {"name":"brick2.img","url":"brick2.js","evaluate":true},
+      {"name":"enemy.img","url":"enemy.js","evaluate":true},
+      {"name":"flower.img","url":"flower.js","evaluate":true},
+      {"name":"flower_b.img","url":"flower_b.js","evaluate":true},
+      {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true},
+      {"name":"pipe.img","url":"pipe.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "gmeter",
+    "name": "G-Meter",
+    "shortName": "G-Meter",
+    "version": "0.01",
+    "description": "Simple G-Meter",
+    "icon": "app.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"gmeter.app.js","url":"app.js"},
+      {"name":"gmeter.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "dtlaunch",
+    "name": "Desktop Launcher",
+    "version": "0.04",
+    "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.",
+    "icon": "icon.png",
+    "type": "launch",
+    "tags": "tool,system,launcher",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"dtlaunch.app.js","url":"app.js"},
+      {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "HRV",
+    "name": "Heart Rate Variability monitor",
+    "shortName": "HRV monitor",
+    "version": "0.04",
+    "description": "Heart Rate Variability monitor, see Readme for more info",
+    "icon": "hrv.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"HRV.app.js","url":"app.js"},
+      {"name":"HRV.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "hardalarm",
+    "name": "Hard Alarm",
+    "shortName": "HardAlarm",
+    "version": "0.02",
+    "description": "Make sure you wake up! Count to the right number to turn off the alarm",
+    "icon": "app.png",
+    "tags": "tool,alarm,widget",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"hardalarm.app.js","url":"app.js"},
+      {"name":"hardalarm.boot.js","url":"boot.js"},
+      {"name":"hardalarm.js","url":"hardalarm.js"},
+      {"name":"hardalarm.img","url":"app-icon.js","evaluate":true},
+      {"name":"hardalarm.wid.js","url":"widget.js"}
+    ],
+    "data": [{"name":"hardalarm.json"}]
+  },
+  {
+    "id": "edisonsball",
+    "name": "Edison's Ball",
+    "shortName": "Edison's Ball",
+    "version": "0.01",
+    "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness",
+    "icon": "app-icon.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"edisonsball.app.js","url":"app.js"},
+      {"name":"edisonsball.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "hrrawexp",
+    "name": "HRM Data Exporter",
+    "shortName": "HRM Data Exporter",
+    "version": "0.01",
+    "description": "export raw hrm signal data to a csv file",
+    "icon": "app-icon.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "interface": "interface.html",
+    "storage": [
+      {"name":"hrrawexp.app.js","url":"app.js"},
+      {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "breath",
+    "name": "Breathing App",
+    "shortName": "Breathing App",
+    "version": "0.01",
+    "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR",
+    "icon": "app-icon.png",
+    "tags": "tools,health",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"breath.app.js","url":"app.js"},
+      {"name":"breath.img","url":"app-icon.js","evaluate":true}
+    ],
+    "data": [{"name":"breath.settings.json","url":"settings.json"}]
+  },
+  {
+    "id": "lazyclock",
+    "name": "Lazy Clock",
+    "version": "0.03",
+    "description": "Tells the time, roughly",
+    "icon": "lazyclock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"lazyclock.app.js","url":"lazyclock-app.js"},
+      {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "astral",
+    "name": "Astral Clock",
+    "version": "0.03",
+    "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
+    "icon": "app-icon.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"astral.app.js","url":"app.js"},
+      {"name":"astral.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "alpinenav",
+    "name": "Alpine Nav",
+    "version": "0.01",
+    "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
+    "icon": "app-icon.png",
+    "tags": "outdoors,gps",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"alpinenav.app.js","url":"app.js"},
+      {"name":"alpinenav.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "lifeclk",
+    "name": "Game of Life Clock",
+    "shortName": "Conway's Clock",
+    "version": "0.06",
+    "description": "Modification and clockification of Conway's Game of Life",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"lifeclk.app.js","url":"app.min.js"},
+      {"name":"lifeclk.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "speedalt",
+    "name": "GPS Adventure Sports",
+    "shortName": "GPS Adv Sport",
+    "version": "1.02",
+    "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
+    "icon": "app.png",
+    "type": "app",
+    "tags": "tool,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"speedalt.app.js","url":"app.js"},
+      {"name":"speedalt.img","url":"app-icon.js","evaluate":true},
+      {"name":"speedalt.settings.js","url":"settings.js"}
+    ],
+    "data": [{"name":"speedalt.json"}]
+  },
+  {
+    "id": "de-stress",
+    "name": "De-Stress",
+    "shortName": "De-Stress",
+    "version": "0.02",
+    "description": "Simple haptic heartbeat",
+    "icon": "app.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"de-stress.app.js","url":"app.js"},
+      {"name":"de-stress.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "mclockplus",
+    "name": "Morph Clock+",
+    "shortName": "Morph Clock+",
+    "version": "0.02",
+    "description": "Morphing Clock with more readable seconds and date and additional stopwatch",
+    "icon": "mclockplus.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"mclockplus.app.js","url":"mclockplus.app.js"},
+      {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "intervals",
+    "name": "Intervals App",
+    "shortName": "Intervals",
+    "version": "0.01",
+    "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.",
+    "icon": "intervals.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"intervals.app.js","url":"intervals.app.js"},
+      {"name":"intervals.img","url":"intervals-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "planetarium",
+    "name": "Planetarium",
+    "shortName": "Planetarium",
+    "version": "0.03",
+    "description": "Planetarium showing up to 500 stars using the watch location and time",
+    "icon": "planetarium.png",
+    "tags": "",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"planetarium.app.js","url":"planetarium.app.js"},
+      {"name":"planetarium.data.csv","url":"planetarium.data.csv"},
+      {"name":"planetarium.const.csv","url":"planetarium.const.csv"},
+      {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"},
+      {"name":"planetarium.settings.js","url":"settings.js"},
+      {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true}
+    ],
+    "data": [{"name":"planetarium.json"}]
+  },
+  {
+    "id": "tapelauncher",
+    "name": "Tape Launcher",
+    "version": "0.02",
+    "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons",
+    "icon": "icon.png",
+    "type": "launch",
+    "tags": "tool,system,launcher",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"tapelauncher.app.js","url":"app.js"},
+      {"name":"tapelauncher.img","url":"icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "oblique",
+    "name": "Oblique Strategies",
+    "version": "0.01",
+    "description": "Oblique Strategies for creativity. Copied from Brian Eno.",
+    "icon": "eno.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"oblique.app.js","url":"app.js"},
+      {"name":"oblique.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "testuserinput",
+    "name": "Test User Input",
+    "shortName": "Test User Input",
+    "version": "0.06",
+    "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.",
+    "icon": "app.png",
+    "tags": "input,interface,buttons,touch,UI",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"testuserinput.app.js","url":"app.js"},
+      {"name":"testuserinput.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "gpssetup",
+    "name": "GPS Setup",
+    "shortName": "GPS Setup",
+    "version": "0.02",
+    "description": "Configure the GPS power options and store them in the GPS nvram",
+    "icon": "gpssetup.png",
+    "tags": "gps,tools,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"gpssetup","url":"gpssetup.js"},
+      {"name":"gpssetup.settings.js","url":"settings.js"},
+      {"name":"gpssetup.app.js","url":"app.js"},
+      {"name":"gpssetup.img","url":"icon.js","evaluate":true}
+    ],
+    "data": [{"name":"gpssetup.settings.json","url":"settings.json"}]
+  },
+  {
+    "id": "walkersclock",
+    "name": "Walkers Clock",
+    "shortName": "Walkers Clock",
+    "version": "0.04",
+    "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference",
+    "icon": "walkersclock48.png",
+    "type": "clock",
+    "tags": "clock,gps,tools,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"walkersclock.app.js","url":"app.js"},
+      {"name":"walkersclock.img","url":"icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "widgps",
+    "name": "GPS Widget",
+    "version": "0.02",
+    "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,gps",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"widgps.wid.js","url":"widget.js"}
+    ]
+  },
+  {
+    "id": "widhrt",
+    "name": "HRM Widget",
+    "version": "0.02",
+    "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,hrm",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"widhrt.wid.js","url":"widget.js"}
+    ]
+  },
+  {
+    "id": "countdowntimer",
+    "name": "Countdown Timer",
+    "version": "0.01",
+    "description": "A simple countdown timer with a focus on usability",
+    "icon": "countdowntimer.png",
+    "tags": "timer,tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"countdowntimer.app.js","url":"countdowntimer.js"},
+      {"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "helloworld",
+    "name": "hello, world!",
+    "shortName": "hello world",
+    "version": "0.02",
+    "description": "A cross cultural hello world!/hola mundo! app with colors and languages",
+    "icon": "app.png",
+    "tags": "input,interface,buttons,touch",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"helloworld.app.js","url":"app.js"},
+      {"name":"helloworld.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "widcom",
+    "name": "Compass Widget",
+    "version": "0.01",
+    "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget,compass",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"widcom.wid.js","url":"widget.js"}
+    ]
+  },
+  {
+    "id": "arrow",
+    "name": "Arrow Compass",
+    "version": "0.04",
+    "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
+    "icon": "arrow.png",
+    "type": "app",
+    "tags": "tool,outdoors",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"arrow.app.js","url":"app.js"},
+      {"name":"arrow.img","url":"icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "waypointer",
+    "name": "Way Pointer",
+    "version": "0.01",
+    "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
+    "icon": "waypointer.png",
+    "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "interface": "waypoints.html",
+    "storage": [
+      {"name":"waypointer.app.js","url":"app.js"},
+      {"name":"waypointer.img","url":"icon.js","evaluate":true}
+    ],
+    "data": [{"name":"waypoints.json","url":"waypoints.json"}]
+  },
+  {
+    "id": "color_catalog",
+    "name": "Colors Catalog",
+    "shortName": "Colors Catalog",
+    "version": "0.01",
+    "description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
+    "icon": "app.png",
+    "tags": "Color,input,buttons,touch,UI",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"color_catalog.app.js","url":"app.js"},
+      {"name":"color_catalog.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "UI4swatch",
+    "name": "UI 4 swatch",
+    "shortName": "UI 4 swatch",
+    "version": "0.01",
+    "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
+    "icon": "app.png",
+    "tags": "Color,input,buttons,touch,UI",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"UI4swatch.app.js","url":"app.js"},
+      {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "simplest",
+    "name": "Simplest Clock",
+    "version": "0.02",
+    "description": "The simplest working clock, acts as a tutorial piece",
+    "icon": "simplest.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"simplest.app.js","url":"app.js"},
+      {"name":"simplest.img","url":"icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "stepo",
+    "name": "Stepometer Clock",
+    "version": "0.03",
+    "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed",
+    "icon": "stepo.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"stepo.app.js","url":"app.js"},
+      {"name":"stepo.img","url":"icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "gbmusic",
+    "name": "Gadgetbridge Music Controls",
+    "shortName": "Music Controls",
+    "version": "0.05",
+    "description": "Control the music on your Gadgetbridge-connected phone",
+    "icon": "icon.png",
+    "type": "app",
+    "tags": "tools,bluetooth,gadgetbridge,music",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": false,
+    "storage": [
+      {"name":"gbmusic.app.js","url":"app.js"},
+      {"name":"gbmusic.settings.js","url":"settings.js"},
+      {"name":"gbmusic.wid.js","url":"widget.js"},
+      {"name":"gbmusic.img","url":"icon.js","evaluate":true}
+    ],
+    "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}]
+  },
+  {
+    "id": "battleship",
+    "name": "Battleship",
+    "version": "0.01",
+    "description": "The classic game of battleship",
+    "icon": "battleship-icon.png",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"battleship.app.js","url":"battleship.js"},
+      {"name":"battleship.img","url":"battleship-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "kitchen",
+    "name": "Kitchen Combo",
+    "version": "0.13",
+    "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
+    "icon": "kitchen.png",
+    "type": "clock",
+    "tags": "tool,outdoors,gps",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "interface": "waypoints.html",
+    "storage": [
+      {"name":"kitchen.app.js","url":"kitchen.app.js"},
+      {"name":"stepo2.kit.js","url":"stepo2.kit.js"},
+      {"name":"swatch.kit.js","url":"swatch.kit.js"},
+      {"name":"gps.kit.js","url":"gps.kit.js"},
+      {"name":"compass.kit.js","url":"compass.kit.js"},
+      {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
+    ],
+    "data": [{"name":"waypoints.json","url":"waypoints.json"}]
+  },
+  {
+    "id": "banglebridge",
+    "name": "BangleBridge",
+    "shortName": "BangleBridge",
+    "version": "0.01",
+    "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
+    "icon": "widget.png",
+    "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
     "readme": "README.md",
     "storage": [
       {"name":"banglebridge.wid.js","url":"widget.js"},
-	    {"name":"banglebridge.watch.img","url":"watch.img"},
-	    {"name":"banglebridge.heart.img","url":"heart.img"}
+      {"name":"banglebridge.watch.img","url":"watch.img"},
+      {"name":"banglebridge.heart.img","url":"heart.img"}
     ]
- },
-{ "id": "qmsched",
-  "name": "Quiet Mode Schedule and Widget",
-  "shortName":"Quiet Mode",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "Automatically turn Quiet Mode on or off at set times",
-  "readme": "README.md",
-  "tags": "tool,widget",
-  "storage": [
-    {"name":"qmsched","url":"lib.js"},
-    {"name":"qmsched.app.js","url":"app.js"},
-    {"name":"qmsched.boot.js","url":"boot.js"},
-    {"name":"qmsched.img","url":"icon.js","evaluate":true},
-    {"name":"qmsched.wid.js","url":"widget.js"}
-  ],
-  "data": [
-    {"name":"qmsched.json"}
-  ]
-},
-{
-  "id": "hourstrike",
-  "name": "Hour Strike",
-  "shortName": "Hour Strike",
-  "icon": "app-icon.png",
-  "version": "0.08",
-  "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
-  "tags": "tool,alarm",
-  "readme": "README.md",
-  "storage": [
-    {"name":"hourstrike.app.js","url":"app.js"},
-    {"name":"hourstrike.boot.js","url":"boot.js"},
-    {"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
-    {"name":"hourstrike.json","url":"hourstrike.json"}
-  ]
-},
-{ "id": "whereworld",
-  "name": "Where in the World?",
-  "shortName" : "Where World",
-  "icon": "app.png",
-  "version": "0.01",
-  "description": "Shows your current location on the world map",
-  "tags": "gps",
-  "storage": [
-    {"name":"whereworld.app.js","url":"app.js"},
-    {"name":"whereworld.img","url":"app-icon.js","evaluate":true},
-    {"name":"whereworld.worldmap","url":"worldmap"}
-  ]
-},
-{
-  "id": "omnitrix",
-  "name":"Omnitrix",
-  "icon":"omnitrix.png",
-  "version": "0.01",
-  "readme": "README.md",
-  "description": "An Omnitrix Showpiece",
-  "tags": "game",
-  "storage": [
-    {"name":"omnitrix.app.js","url":"omnitrix.app.js"},
-    {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true}
-  ]
-},
-{ "id": "batclock",
-  "name": "Bat Clock",
-  "shortName":"Bat Clock",
-  "icon": "bat-clock.png",
-  "version":"0.02",
-  "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
-  "tags": "clock",
-  "type": "clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"batclock.app.js","url":"bat-clock.app.js"},
-    {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true}
-  ]
-},
-{ "id":"doztime",
-  "name":"Dozenal Time",
-  "shortName":"Dozenal Time",
-  "icon":"app.png",
-  "version":"0.04",
-  "description":"A dozenal Holocene calendar and dozenal diurnal clock",
-  "tags":"clock",
-  "type":"clock",
-  "allow_emulator":true,
-  "readme": "README.md",
-  "storage": [
-    {"name":"doztime.app.js","url":"app.js"},
-    {"name":"doztime.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id":"gbtwist",
-  "name":"Gadgetbridge Twist Control",
-  "shortName":"Twist Control",
-  "icon":"app.png",
-  "version":"0.01",
-  "description":"Shake your wrist to control your music app via Gadgetbridge",
-  "tags":"tools,bluetooth,gadgetbridge,music",
-  "type":"app",
-  "allow_emulator":false,
-  "readme": "README.md",
-  "storage": [
-    {"name":"gbtwist.app.js","url":"app.js"},
-    {"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "thermom",
-  "name": "Thermometer",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "Displays the current temperature, updated every 20 seconds",
-  "tags": "tool",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"thermom.app.js","url":"app.js"},
-    {"name":"thermom.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "mysticdock",
-  "name": "Mystic Dock",
-  "icon": "mystic-dock.png",
-  "version":"1.00",
-  "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.",
-  "tags": "dock",
-  "type":"dock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"mysticdock.app.js","url":"mystic-dock-app.js"},
-    {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"},
-    {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"},
-    {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true}
-  ]
-},
-{ "id": "mysticclock",
-  "name": "Mystic Clock",
-  "icon": "mystic-clock.png",
-  "version":"1.01",
-  "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.",
-  "tags": "clock",
-  "type":"clock",
-  "readme": "README.md",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"mysticclock.app.js","url":"mystic-clock-app.js"},
-    {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"},
-    {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true}
-  ]
-},
-{ "id": "hcclock",
-  "name": "Hi-Contrast Clock",
-  "icon": "hcclock-icon.png",
-  "version":"0.01",
-  "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.",
-  "tags": "clock",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"hcclock.app.js","url":"hcclock.app.js"},
-    {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true}
-  ]
-},
-{ "id": "thermomF",
-  "name": "Fahrenheit Temp",
-  "icon": "thermf.png",
-  "version":"0.01",
-  "description": "A modification of the Thermometer App to display temprature in Fahrenheit",
-  "tags": "tool",
-  "storage": [
-    {"name":"thermomF.app.js","url":"app.js"},
-    {"name":"thermomF.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "nixie",
-  "name": "Nixie Clock",
-  "shortName":"Nixie",
-  "icon": "nixie.png",
-  "version":"0.01",
-  "description": "A nixie tube clock for both Bangle 1 and 2.",
-  "tags": "clock",
-  "type":"clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"nixie.app.js","url":"app.js"},
-    {"name":"nixie.img","url":"app-icon.js","evaluate":true},
-    {"name":"m_vatch.js","url":"m_vatch.js"}
-  ]
-},
-{ "id": "carcrazy",
-  "name": "Car Crazy",
-  "shortName":"Car Crazy",
-  "icon": "carcrash.png",
-  "version":"0.03",
-  "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
-  "tags": "game",
-  "readme": "README.md",
-  "storage": [
-    {"name":"carcrazy.app.js","url":"app.js"},
-    {"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
-    {"name":"carcrazy.settings.js","url":"settings.js"}
-  ],
-  "data": [
-    {"name":"app.json"}
-  ]
-},
-{ "id": "shortcuts",
-  "name": "Shortcuts",
-  "shortName":"Shortcuts",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "Quickly load your favourite apps from (almost) any watch face.",
-  "tags": "tool",
-  "type": "bootloader",
-  "readme": "README.md",
-  "storage": [
-    {"name":"shortcuts.boot.js","url":"boot.js"},
-    {"name":"shortcuts.settings.js","url":"settings.js"}
-  ],
-  "data": [
-    {"name":"shortcuts.json"}
-  ]
-},
-{ "id": "vectorclock",
-  "name": "Vector Clock",
-  "icon": "app.png",
-  "version": "0.02",
-  "description": "A digital clock that uses the built-in vector font.",
-  "tags": "clock",
-  "type": "clock",
-  "allow_emulator": true,
-  "storage": [
-    {"name":"vectorclock.app.js","url":"app.js"},
-    {"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "fd6fdetect",
-  "name": "fd6fdetect",
-  "shortName":"fd6fdetect",
-  "icon": "app.png",
-  "version":"0.1",
-  "description": "Allows you to see 0xFD6F beacons near you.",
-  "tags": "tool",
-  "storage": [
-    {"name":"fd6fdetect.app.js","url":"app.js"},
-    {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "choozi",
-  "name": "Choozi",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "Choose people or things at random using Bangle.js.",
-  "tags": "tool",
-  "readme": "README.md",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"choozi.app.js","url":"app.js"},
-    {"name":"choozi.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "widclkbttm",
-  "name": "Digital clock (Bottom) widget",
-  "shortName":"Digital clock Bottom Widget",
-  "icon": "widclkbttm.png",
-  "version":"0.03",
-  "description": "Displays time in the bottom area.",
-  "readme": "README.md",
-  "tags": "widget",
-  "type": "widget",
-  "storage": [
-    {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"}
-  ]
-},
-{ "id": "pastel",
-  "name": "Pastel Clock",
-  "shortName": "Pastel",
-  "icon": "pastel.png",
-  "version":"0.05",
-  "description": "A Configurable clock with custom fonts and background",
-  "tags": "clock,b2",
-  "type":"clock",
-  "readme": "README.md",
-  "storage": [
-    {"name":"pastel.app.js","url":"pastel.app.js"},
-    {"name":"pastel.img","url":"pastel.icon.js","evaluate":true},
-    {"name":"pastel.settings.js","url":"pastel.settings.js"}
-  ],
-  "data": [
-    {"name":"pastel.json"}
-  ]
-},
-{ "id": "antonclk",
-  "name": "Anton Clock",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "A simple clock using the bold Anton font.",
-  "tags":"clock,b2",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"antonclk.app.js","url":"app.js"},
-    {"name":"antonclk.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "waveclk",
-  "name": "Wave Clock",
-  "icon": "app.png",
-  "version":"0.02",
-  "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
-  "tags":"clock,b2",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"waveclk.app.js","url":"app.js"},
-    {"name":"waveclk.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "floralclk",
-  "name": "Floral Clock",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
-  "tags":"clock,b2",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"floralclk.app.js","url":"app.js"},
-    {"name":"floralclk.img","url":"app-icon.js","evaluate":true}
-  ]
-},
-{ "id": "score",
-  "name": "Score Tracker",
-  "icon": "score.app.png",
-  "version":"0.01",
-  "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.",
-  "readme": "README.md",
-  "tags": "b2",
-  "type": "app",
-  "storage": [
-    {"name":"score.app.js","url":"score.app.js"},
-    {"name":"score.settings.js","url":"score.settings.js"},
-    {"name":"score.presets.json","url":"score.presets.json"},
-    {"name":"score.img","url":"score.app-icon.js","evaluate":true}
-  ],
-  "data": [
-    {"name":"score.json"}
-  ]
-},
-{ "id": "menusmall",
-  "name": "Small Menus",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "Replace Bangle.js 2's menus with a version that contains smaller text",
-  "tags": "b2,bno1,system",
-  "type": "boot",
-  "storage": [
-    {"name":"menusmall.boot.js","url":"boot.js"}
-  ]
-},
-{ "id": "ffcniftya",
-  "name": "Nifty-A Clock",
-  "icon": "app.png",
-  "version":"0.01",
-  "description": "A nifty clock with time and date",
-  "tags":"clock,b2",
-  "type":"clock",
-  "allow_emulator":true,
-  "storage": [
-    {"name":"ffcniftya.app.js","url":"app.js"},
-    {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true}
-  ]
-}
+  },
+  {
+    "id": "qmsched",
+    "name": "Quiet Mode Schedule and Widget",
+    "shortName": "Quiet Mode",
+    "version": "0.02",
+    "description": "Automatically turn Quiet Mode on or off at set times",
+    "icon": "app.png",
+    "tags": "tool,widget",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"qmsched","url":"lib.js"},
+      {"name":"qmsched.app.js","url":"app.js"},
+      {"name":"qmsched.boot.js","url":"boot.js"},
+      {"name":"qmsched.img","url":"icon.js","evaluate":true},
+      {"name":"qmsched.wid.js","url":"widget.js"}
+    ],
+    "data": [{"name":"qmsched.json"}]
+  },
+  {
+    "id": "hourstrike",
+    "name": "Hour Strike",
+    "shortName": "Hour Strike",
+    "version": "0.08",
+    "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
+    "icon": "app-icon.png",
+    "tags": "tool,alarm",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"hourstrike.app.js","url":"app.js"},
+      {"name":"hourstrike.boot.js","url":"boot.js"},
+      {"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
+      {"name":"hourstrike.json","url":"hourstrike.json"}
+    ]
+  },
+  {
+    "id": "whereworld",
+    "name": "Where in the World?",
+    "shortName": "Where World",
+    "version": "0.01",
+    "description": "Shows your current location on the world map",
+    "icon": "app.png",
+    "tags": "gps",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"whereworld.app.js","url":"app.js"},
+      {"name":"whereworld.img","url":"app-icon.js","evaluate":true},
+      {"name":"whereworld.worldmap","url":"worldmap"}
+    ]
+  },
+  {
+    "id": "omnitrix",
+    "name": "Omnitrix",
+    "version": "0.01",
+    "description": "An Omnitrix Showpiece",
+    "icon": "omnitrix.png",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"omnitrix.app.js","url":"omnitrix.app.js"},
+      {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "batclock",
+    "name": "Bat Clock",
+    "shortName": "Bat Clock",
+    "version": "0.02",
+    "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
+    "icon": "bat-clock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"batclock.app.js","url":"bat-clock.app.js"},
+      {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "doztime",
+    "name": "Dozenal Time",
+    "shortName": "Dozenal Time",
+    "version": "0.04",
+    "description": "A dozenal Holocene calendar and dozenal diurnal clock",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"doztime.app.js","url":"app.js"},
+      {"name":"doztime.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "gbtwist",
+    "name": "Gadgetbridge Twist Control",
+    "shortName": "Twist Control",
+    "version": "0.01",
+    "description": "Shake your wrist to control your music app via Gadgetbridge",
+    "icon": "app.png",
+    "type": "app",
+    "tags": "tools,bluetooth,gadgetbridge,music",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": false,
+    "storage": [
+      {"name":"gbtwist.app.js","url":"app.js"},
+      {"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "thermom",
+    "name": "Thermometer",
+    "version": "0.02",
+    "description": "Displays the current temperature, updated every 20 seconds",
+    "icon": "app.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"thermom.app.js","url":"app.js"},
+      {"name":"thermom.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "mysticdock",
+    "name": "Mystic Dock",
+    "version": "1.00",
+    "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.",
+    "icon": "mystic-dock.png",
+    "type": "dock",
+    "tags": "dock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"mysticdock.app.js","url":"mystic-dock-app.js"},
+      {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"},
+      {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"},
+      {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "mysticclock",
+    "name": "Mystic Clock",
+    "version": "1.01",
+    "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.",
+    "icon": "mystic-clock.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"mysticclock.app.js","url":"mystic-clock-app.js"},
+      {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"},
+      {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "hcclock",
+    "name": "Hi-Contrast Clock",
+    "version": "0.01",
+    "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.",
+    "icon": "hcclock-icon.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"hcclock.app.js","url":"hcclock.app.js"},
+      {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "thermomF",
+    "name": "Fahrenheit Temp",
+    "version": "0.01",
+    "description": "A modification of the Thermometer App to display temprature in Fahrenheit",
+    "icon": "thermf.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"thermomF.app.js","url":"app.js"},
+      {"name":"thermomF.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "nixie",
+    "name": "Nixie Clock",
+    "shortName": "Nixie",
+    "version": "0.01",
+    "description": "A nixie tube clock for both Bangle 1 and 2.",
+    "icon": "nixie.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"nixie.app.js","url":"app.js"},
+      {"name":"nixie.img","url":"app-icon.js","evaluate":true},
+      {"name":"m_vatch.js","url":"m_vatch.js"}
+    ]
+  },
+  {
+    "id": "carcrazy",
+    "name": "Car Crazy",
+    "shortName": "Car Crazy",
+    "version": "0.03",
+    "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
+    "icon": "carcrash.png",
+    "tags": "game",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"carcrazy.app.js","url":"app.js"},
+      {"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
+      {"name":"carcrazy.settings.js","url":"settings.js"}
+    ],
+    "data": [{"name":"app.json"}]
+  },
+  {
+    "id": "shortcuts",
+    "name": "Shortcuts",
+    "shortName": "Shortcuts",
+    "version": "0.01",
+    "description": "Quickly load your favourite apps from (almost) any watch face.",
+    "icon": "app.png",
+    "type": "bootloader",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"shortcuts.boot.js","url":"boot.js"},
+      {"name":"shortcuts.settings.js","url":"settings.js"}
+    ],
+    "data": [{"name":"shortcuts.json"}]
+  },
+  {
+    "id": "vectorclock",
+    "name": "Vector Clock",
+    "version": "0.02",
+    "description": "A digital clock that uses the built-in vector font.",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"vectorclock.app.js","url":"app.js"},
+      {"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "fd6fdetect",
+    "name": "fd6fdetect",
+    "shortName": "fd6fdetect",
+    "version": "0.1",
+    "description": "Allows you to see 0xFD6F beacons near you.",
+    "icon": "app.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "storage": [
+      {"name":"fd6fdetect.app.js","url":"app.js"},
+      {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "choozi",
+    "name": "Choozi",
+    "version": "0.01",
+    "description": "Choose people or things at random using Bangle.js.",
+    "icon": "app.png",
+    "tags": "tool",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "allow_emulator": true,
+    "storage": [
+      {"name":"choozi.app.js","url":"app.js"},
+      {"name":"choozi.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "widclkbttm",
+    "name": "Digital clock (Bottom) widget",
+    "shortName": "Digital clock Bottom Widget",
+    "version": "0.03",
+    "description": "Displays time in the bottom area.",
+    "icon": "widclkbttm.png",
+    "type": "widget",
+    "tags": "widget",
+    "supports": ["BANGLEJS"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"}
+    ]
+  },
+  {
+    "id": "pastel",
+    "name": "Pastel Clock",
+    "shortName": "Pastel",
+    "version": "0.05",
+    "description": "A Configurable clock with custom fonts and background",
+    "icon": "pastel.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"pastel.app.js","url":"pastel.app.js"},
+      {"name":"pastel.img","url":"pastel.icon.js","evaluate":true},
+      {"name":"pastel.settings.js","url":"pastel.settings.js"}
+    ],
+    "data": [{"name":"pastel.json"}]
+  },
+  {
+    "id": "antonclk",
+    "name": "Anton Clock",
+    "version": "0.02",
+    "description": "A simple clock using the bold Anton font.",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"antonclk.app.js","url":"app.js"},
+      {"name":"antonclk.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "waveclk",
+    "name": "Wave Clock",
+    "version": "0.02",
+    "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"waveclk.app.js","url":"app.js"},
+      {"name":"waveclk.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "floralclk",
+    "name": "Floral Clock",
+    "version": "0.01",
+    "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"floralclk.app.js","url":"app.js"},
+      {"name":"floralclk.img","url":"app-icon.js","evaluate":true}
+    ]
+  },
+  {
+    "id": "score",
+    "name": "Score Tracker",
+    "version": "0.01",
+    "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.",
+    "icon": "score.app.png",
+    "type": "app",
+    "tags": "",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "readme": "README.md",
+    "storage": [
+      {"name":"score.app.js","url":"score.app.js"},
+      {"name":"score.settings.js","url":"score.settings.js"},
+      {"name":"score.presets.json","url":"score.presets.json"},
+      {"name":"score.img","url":"score.app-icon.js","evaluate":true}
+    ],
+    "data": [{"name":"score.json"}]
+  },
+  {
+    "id": "menusmall",
+    "name": "Small Menus",
+    "version": "0.01",
+    "description": "Replace Bangle.js 2's menus with a version that contains smaller text",
+    "icon": "app.png",
+    "type": "boot",
+    "tags": "system",
+    "supports": ["BANGLEJS2"],
+    "storage": [
+      {"name":"menusmall.boot.js","url":"boot.js"}
+    ]
+  },
+  {
+    "id": "ffcniftya",
+    "name": "Nifty-A Clock",
+    "version": "0.01",
+    "description": "A nifty clock with time and date",
+    "icon": "app.png",
+    "type": "clock",
+    "tags": "clock",
+    "supports": ["BANGLEJS","BANGLEJS2"],
+    "allow_emulator": true,
+    "storage": [
+      {"name":"ffcniftya.app.js","url":"app.js"},
+      {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true}
+    ]
+  }
 ]
diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/add_to_apps.json
index 1585ab73d..cc28e1e93 100644
--- a/apps/_example_app/add_to_apps.json
+++ b/apps/_example_app/add_to_apps.json
@@ -2,13 +2,14 @@
 { "id": "7chname",
   "name": "My app's human readable name",
   "shortName":"Short Name",
-  "icon": "app.png",
   "version":"0.01",
   "description": "A detailed description of my great app",
+  "icon": "app.png",
   "tags": "",
+  "supports" : ["BANGLEJS2"],  
   "readme": "README.md",
   "storage": [
     {"name":"7chname.app.js","url":"app.js"},
     {"name":"7chname.img","url":"app-icon.js","evaluate":true}
   ]
-}
\ No newline at end of file
+}
diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/add_to_apps.json
index 527c698a0..b55adce9d 100644
--- a/apps/_example_widget/add_to_apps.json
+++ b/apps/_example_widget/add_to_apps.json
@@ -2,11 +2,12 @@
 { "id": "7chname",
   "name": "My widget's human readable name",
   "shortName":"Short Name",
-  "icon": "widget.png",
   "version":"0.01",
   "description": "A detailed description of my great widget",
-  "tags": "widget",
+  "icon": "widget.png",  
   "type": "widget",
+  "tags": "widget",
+  "supports" : ["BANGLEJS2"],
   "readme": "README.md",
   "storage": [
     {"name":"7chname.wid.js","url":"widget.js"}
diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js
new file mode 100644
index 000000000..6908591a5
--- /dev/null
+++ b/bin/create_app_supports_field.js
@@ -0,0 +1,83 @@
+#!/usr/bin/nodejs
+/* Quick hack to add proper 'supports' field to apps.json
+*/
+
+var fs = require("fs");
+
+var BASEDIR = __dirname+"/../";
+
+var appsFile, apps;
+try {
+  appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
+} catch (e) {
+  ERROR("apps.json not found");
+}
+try{
+  apps = JSON.parse(appsFile);
+} catch (e) {
+  console.log(e);
+  var m = e.toString().match(/in JSON at position (\d+)/);
+  if (m) {
+    var char = parseInt(m[1]);
+    console.log("===============================================");
+    console.log("LINE "+appsFile.substr(0,char).split("\n").length);
+    console.log("===============================================");
+    console.log(appsFile.substr(char-10, 20));
+    console.log("===============================================");
+  }
+  console.log(m);
+  ERROR("apps.json not valid JSON");
+
+}
+
+apps = apps.map((app,appIdx) => {
+  var tags = [];
+  if (app.tags) tags = app.tags.split(",").map(t=>t.trim());
+  var supportsB1 = true;
+  var supportsB2 = false;
+  if (tags.includes("b2")) {
+    tags = tags.filter(x=>x!="b2");
+    supportsB2 = true;
+  }
+  if (tags.includes("bno2")) {
+    tags = tags.filter(x=>x!="bno2");
+    supportsB2 = false;
+  }
+  if (tags.includes("bno1")) {
+    tags = tags.filter(x=>x!="bno1");
+    supportsB1 = false;
+  }
+  app.tags = tags.join(",");
+  app.supports = [];
+  if (supportsB1) app.supports.push("BANGLEJS");
+  if (supportsB2) app.supports.push("BANGLEJS2");
+  return app;
+});
+
+var KEY_ORDER = [
+  "id","name","shortName","version","description","icon","type","tags","supports",
+  "dependencies", "readme", "custom", "customConnect", "interface",
+  "allow_emulator", "storage", "data", "sortorder"
+];
+
+var JS = JSON.stringify;
+var json = "[\n  "+apps.map(app=>{
+  var keys = KEY_ORDER.filter(k=>k in app);
+  Object.keys(app).forEach(k=>{
+    if (!KEY_ORDER.includes(k))
+      throw new Error(`Key named ${k} not known!`);
+  });
+
+
+  return "{\n    "+keys.map(k=>{
+    var js = JS(app[k]);
+    if (k=="storage")
+      js = "[\n      "+app.storage.map(s=>JS(s)).join(",\n      ")+"\n    ]";
+    return JS(k)+": "+js;
+  }).join(",\n    ")+"\n  }";
+}).join(",\n  ")+"\n]\n";
+
+//console.log(json);
+
+console.log("new apps.json written");
+fs.writeFileSync(BASEDIR+"apps.json", json);
diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js
index b98aa9ef3..dbce9c855 100755
--- a/bin/sanitycheck.js
+++ b/bin/sanitycheck.js
@@ -51,7 +51,8 @@ try{
 
 const APP_KEYS = [
   'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
-  'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator',
+  'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data',
+  'supports', 'allow_emulator', 
   'dependencies'
 ];
 const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
@@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => {
   if (!app.name) ERROR(`App ${app.id} has no name`);
   var isApp = !app.type || app.type=="app";
   if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
+  if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
+  else {
+    app.supports.forEach(dev => {
+      if (!["BANGLEJS","BANGLEJS2"].includes(dev))
+        ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`);
+    });
+  }
+
   if (!app.version) WARN(`App ${app.id} has no version`);
   else {
     if (!fs.existsSync(appDir+"ChangeLog")) {

From d1935c3860aecd5de7c7e2b6e11b1fe4d5919b0a Mon Sep 17 00:00:00 2001
From: Gordon Williams 
Date: Wed, 20 Oct 2021 15:20:25 +0100
Subject: [PATCH 075/325] Add filtering for Bangle.js 1 and 2

---
 core         |   2 +-
 css/main.css |   3 ++
 index.html   |  11 +++++
 loader.js    | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 126 insertions(+), 1 deletion(-)

diff --git a/core b/core
index 0fd608f08..bc5b1284f 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
+Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479
diff --git a/css/main.css b/css/main.css
index 0dbe8da14..90b4ff280 100644
--- a/css/main.css
+++ b/css/main.css
@@ -23,6 +23,9 @@
 .filter-nav {
   display: inline-block;
 }
+.device-nav {
+  display: inline-block;
+}
 .sort-nav {
   float: right;
 }
diff --git a/index.html b/index.html
index a5ae7bff0..0185f1bae 100644
--- a/index.html
+++ b/index.html
@@ -60,6 +60,17 @@
 
     
+
diff --git a/loader.js b/loader.js index 6528ffc98..90c1c5d96 100644 --- a/loader.js +++ b/loader.js @@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") { var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this +// We're only interested in Bangles +DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); + +// Set up source code URL (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); @@ -21,6 +25,7 @@ var RECOMMENDED_VERSION = "2v10"; Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); +// When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); @@ -33,4 +38,110 @@ function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId == "BANGLEJS2") { Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + + // check against features shown? + filterAppsForDevice(deviceId); + /* if we'd saved a device ID but this device is different, ensure + we ask again next time */ + var savedDeviceId = getSavedDeviceId(); + if (savedDeviceId!==undefined && savedDeviceId!=deviceId) + setSavedDeviceId(undefined); } + +var originalAppJSON = undefined; +function filterAppsForDevice(deviceId) { + if (originalAppJSON===undefined) + originalAppJSON = appJSON; + + var device = DEVICEINFO.find(d=>d.id==deviceId); + // set the device dropdown + document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps"; + + if (!device) { + if (deviceId!==undefined) + showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning"); + appJSON = originalAppJSON; + } else { + // Now filter apps + appJSON = originalAppJSON.filter(app => { + var supported = ["BANGLEJS"]; + if (!app.supports) { + console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`); + return false; + } + if (app.supports.includes(deviceId)) return true; + //console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`); + return false; + }); + } + refreshLibrary(); +} + +// If 'remember' was checked in the window below, this is the device +function getSavedDeviceId() { + let deviceId = localStorage.getItem("deviceId"); + if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId)) + return deviceId; + return undefined; +} + +function setSavedDeviceId(deviceId) { + localStorage.setItem("deviceId", deviceId); +} + +// At boot, show a window to choose which type of device you have... +window.addEventListener('load', (event) => { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + return filterAppsForDevice(deviceId); + + var html = `
+ ${DEVICEINFO.map(d=>` +
+
+
+
${d.name}
+ +
+
+ ${d.name} +
+
+
`).join("\n")} +
+
+
+ +
+
+
`; + showPrompt("Which Bangle.js?",html,{},false); + htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { + button.addEventListener("click",event => { + let rememberDevice = document.getElementById("remember_device").checked; + + let button = event.currentTarget; + let deviceId = button.getAttribute("deviceid"); + hidePrompt(); + console.log("Chosen device", deviceId); + setSavedDeviceId(rememberDevice ? deviceId : undefined); + filterAppsForDevice(deviceId); + }); + }); +}); + +// Hook onto device chooser dropdown +window.addEventListener('load', (event) => { + htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { + button.addEventListener("click", event => { + var a = event.target; + var deviceId = a.getAttribute("dt")||undefined; + filterAppsForDevice(deviceId); // also sets the device dropdown + setSavedDeviceId(undefined); // ask at startup next time + document.querySelector(".devicetype-nav span").innerText = a.innerText; + }); + }); +}); From a89545e02a89871591955cd4dfcc479884d45ff7 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 18:35:03 +0100 Subject: [PATCH 076/325] Stopwatch: touch based stopwatch for Bangle 2 --- apps.json | 13 ++ apps/stopwatch/ChangeLog | 1 + apps/stopwatch/README.md | 20 +++ apps/stopwatch/pause-24.png | Bin 0 -> 161 bytes apps/stopwatch/pause-24a.png | Bin 0 -> 123 bytes apps/stopwatch/play-24.png | Bin 0 -> 297 bytes apps/stopwatch/stop-24.png | Bin 0 -> 177 bytes apps/stopwatch/stop-24a.png | Bin 0 -> 192 bytes apps/stopwatch/stopwatch.app.js | 222 +++++++++++++++++++++++++++++++ apps/stopwatch/stopwatch.icon.js | 1 + apps/stopwatch/stopwatch.png | Bin 0 -> 1566 bytes 11 files changed, 257 insertions(+) create mode 100644 apps/stopwatch/ChangeLog create mode 100644 apps/stopwatch/README.md create mode 100644 apps/stopwatch/pause-24.png create mode 100644 apps/stopwatch/pause-24a.png create mode 100644 apps/stopwatch/play-24.png create mode 100644 apps/stopwatch/stop-24.png create mode 100644 apps/stopwatch/stop-24a.png create mode 100644 apps/stopwatch/stopwatch.app.js create mode 100644 apps/stopwatch/stopwatch.icon.js create mode 100644 apps/stopwatch/stopwatch.png diff --git a/apps.json b/apps.json index 269e9577e..84e95330c 100644 --- a/apps.json +++ b/apps.json @@ -3612,5 +3612,18 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "stopwatch", + "name": "Stopwatch", + "shortName":"Stopwatch", + "icon": "stopwatch.png", + "version":"0.01", + "description": "A touch controlled stopwatch for Bangle 2", + "readme": "README.md", + "tags": "tool, b2", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] } ] diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/stopwatch/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md new file mode 100644 index 000000000..1924cc343 --- /dev/null +++ b/apps/stopwatch/README.md @@ -0,0 +1,20 @@ +# Stopwatch + +A touch screen based stop watch for Bangle 2 + +## Screenshots + +## Features + +* Feature A +* Feature B + +## Future features + +I'm keen to complete this project with + +* Ability to dismiss the app and leave it running in the background +* A small widget to show the elapsed time on the curren active clock +* Laptimes, with a way to view all the laptimes on a scrollable screen + + diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3d8feaa578ddf2868ca2ebfe6c1016b89076b6 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt^(o-U3d z9-YYv609r?%6x@>Y;0_-CL6c&@bIi(kYfnsC7S$X2lDQBZoM(BvGJk3)I0Y{?~g0x zcb`>IOUhLd?};+VI=F&$u~<)(1Fu1X=sArBW(KQ1!Ho<&4aPtl7(8A5T-G@yGywo& C|1Q-4 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..7838ef640b48c569532e978aa4ea89960d1b0533 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt@zo-U3d z9-YYv60C>i3?5DV?eCzgSjh$gYC*9sS`u3mt~3R5yXi{si1joHGB8XvWs;ff*UPx#<4Ht8R7gv;*0D;&KoADtFVAWvTu`u3ff&Q zddffoheq6(ovFkBk;%?%8p+ip=j#J@@r2_VcpvbIcRXMd*JGgHfc4CCOxm~l7>bnu zk@<#w%y2gZDFwRBN1S4cml9|w5Sc5yVh=}uA^!o^Gw-p1+qR8Ayso-U3d z9-YYv609mr{A_G($9Z^o{+~S3zaZ@aOTUJJ%uS&=49hxV8Wzs)WIEizWU{@XmBX~9 zsYh`BlMDQd0=eB9SdTnm-t%&ffW$Aymb`<>8oqgfy*X@Q4-Ps+&UM&uNRk!kj6GuR V;!)cRW&mwu@O1TaS?83{1OQ$|I{5$q literal 0 HcmV?d00001 diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..e89ddae054cbda36b088abab6577f064049dbdf9 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_oo-U3d z9-VKedh<0X@VLyE^E>)`zQZ1Ny$-idk)JA?UU=`)n(L`3>2NZIRp$ZMX(l(L6@vEJ zySe!q8&BV{IJVT%o@zpKGUG`7qs_<9thpacs;)sXzG!+_r}04vQBZNXTE1 nxpdjV=apyUC!A4#dXRD61xvr9nF3)z2QYZL`njxgN@xNA(C= 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 + if (this.img != undefined) { + let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale + let ix = this.x + ((this.w - iw) /2); + let iy = this.y + ((this.h - iw) /2); + log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})"); + g.drawImage(this.img, ix, iy, {scale: iconScale}); + } + g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h); +}; + + +var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img); +var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img); +var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img); + +bigPlayPauseBtn.setImage(play_img); +smallPlayPauseBtn.setImage(play_img); +resetBtn.setImage(pause_img); + + +Bangle.on('touch', function(button, xy) { + // not running, and reset + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and hit play + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and press reset + if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + + // must be running + if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +// above not working, hence using next 2 lines +g.setColor("#000"); +g.fillRect(0,0,w,h); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +Bangle.setUI("clock"); // Show launcher when button pressed diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js new file mode 100644 index 000000000..867b95bd2 --- /dev/null +++ b/apps/stopwatch/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")); diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 From 4ccc61822fcd39a4ba676b5dad68d99baff4a376 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 18:38:52 +0100 Subject: [PATCH 077/325] Stopwatch: touch based stopwatch for Bangle 2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 84e95330c..1f1937daf 100644 --- a/apps.json +++ b/apps.json @@ -3614,7 +3614,7 @@ ] }, { "id": "stopwatch", - "name": "Stopwatch", + "name": "Stopwatch Touch", "shortName":"Stopwatch", "icon": "stopwatch.png", "version":"0.01", From 572dac6889658182e98bbcd25d1ea649624b1cb5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 19:13:29 +0100 Subject: [PATCH 078/325] Widbatpc: setting to hide widget, apps.json fix for stopwatch touch --- apps.json | 4 ++-- apps/widbatpc/ChangeLog | 1 + apps/widbatpc/settings.js | 9 ++++++++- apps/widbatpc/widget.js | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 1f1937daf..8beaee6f1 100644 --- a/apps.json +++ b/apps.json @@ -668,7 +668,7 @@ "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", "icon": "widget.png", - "version":"0.13", + "version":"0.14", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "tags": "widget,battery,b2", "type":"widget", @@ -3622,7 +3622,7 @@ "readme": "README.md", "tags": "tool, b2", "storage": [ - {"name":"stopwatch.app.js","url":"stopwatch.js"}, + {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} ] } diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index 09e4fabf4..b8e594fc4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -10,3 +10,4 @@ 0.11: Don't overwrite existing settings on app update 0.12: Fixed for Bangle 2 0.13: Fillbar setting added, see README +0.14: Added setting to completely hide the widget diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index b7a5db9e6..55588238d 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -13,6 +13,7 @@ 'fillbar': false, 'charger': true, 'hideifmorethan': 100, + 'hidewidget': false, } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -31,7 +32,8 @@ } } - const onOffFormat = b => (b ? 'on' : 'off') + const onOffFormat = b => (b ? 'On' : 'Off') + const yesNoFormat = b => (b ? 'Yes' : 'No') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -68,6 +70,11 @@ format: x => x+"%", onchange: save('hideifmorethan'), }, + 'Hide Widget': { + value: s.hidewidget, + format: yesNoFormat, + onchange: save('hidewidget'), + }, } E.showMenu(menu) }) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index caecf8ae4..574c22f6c 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -76,6 +76,7 @@ function draw() { // if hidden, don't draw if (!WIDGETS["batpc"].width) return; + if (setting('hidewidget')) return; // else... var s = 39; var x = this.x, y = this.y; From 8d439968ba71c98b818d9ecbb543ef643dfb15f0 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 19:28:09 +0100 Subject: [PATCH 079/325] Stopwatch Touch: update short name --- apps.json | 2 +- apps/stopwatch/stopwatch.app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 8beaee6f1..54b15d970 100644 --- a/apps.json +++ b/apps.json @@ -3615,7 +3615,7 @@ }, { "id": "stopwatch", "name": "Stopwatch Touch", - "shortName":"Stopwatch", + "shortName":"Stopwatch Touch", "icon": "stopwatch.png", "version":"0.01", "description": "A touch controlled stopwatch for Bangle 2", diff --git a/apps/stopwatch/stopwatch.app.js b/apps/stopwatch/stopwatch.app.js index 80fa7902e..1d6791b57 100644 --- a/apps/stopwatch/stopwatch.app.js +++ b/apps/stopwatch/stopwatch.app.js @@ -30,7 +30,7 @@ function timeToText(t) { if (hrs === 0) text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; else - text = (""+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; + text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); //log_debug(text); return text; From fc3ce860091b89230b507d1eb070864fec0a58cb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 20:28:28 +0100 Subject: [PATCH 080/325] misc tweaks for layout/gps time/bootloader --- apps.json | 6 +-- apps/boot/ChangeLog | 3 ++ apps/boot/bootupdate.js | 5 +- apps/gpstime/ChangeLog | 3 +- apps/gpstime/gpstime.js | 117 +++++++++++++++++++++------------------- modules/Layout.js | 23 ++++---- 6 files changed, 86 insertions(+), 71 deletions(-) diff --git a/apps.json b/apps.json index ac3911a71..7afcd9cb5 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.31", + "version": "0.32", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -550,11 +550,11 @@ { "id": "gpstime", "name": "GPS Time", - "version": "0.04", + "version": "0.05", "description": "Update the Bangle.js's clock based on the time from the GPS receiver", "icon": "gpstime.png", "tags": "tool,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"gpstime.app.js","url":"gpstime.js"}, {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true} diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 6cdf1b0e5..4ce4dbe65 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -31,3 +31,6 @@ Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js 0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now 0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics +0.32: Fix single quote error in g.wrapString polyfill + improve g.stringMetrics polyfill + diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 7210ae731..a5ec01fa4 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -143,13 +143,14 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi } if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.stringMetrics=function(txt) { - return {width:this.stringWidth(txt), height:this.getFontHeight()}; + txt = txt.toString().split("\\n"); + return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; };\n`; } if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.wrapString=function(str, maxWidth) { var lines = []; - for (var unwrappedLine of str.split("\n")) { + for (var unwrappedLine of str.split("\\n")) { var words = unwrappedLine.split(" "); var line = words.shift(); for (var word of words) { diff --git a/apps/gpstime/ChangeLog b/apps/gpstime/ChangeLog index a3bd6351e..4d9bbc8a2 100644 --- a/apps/gpstime/ChangeLog +++ b/apps/gpstime/ChangeLog @@ -1,2 +1,3 @@ 0.03: Fix time output on new firmwares when no GPS time set (fix #104) -0.04: Fix shown UTC time zone sign \ No newline at end of file +0.04: Fix shown UTC time zone sign +0.05: Use new 'layout library for Bangle2, fix #764 by adding a back button diff --git a/apps/gpstime/gpstime.js b/apps/gpstime/gpstime.js index a061d2e23..8c80953fa 100644 --- a/apps/gpstime/gpstime.js +++ b/apps/gpstime/gpstime.js @@ -1,68 +1,75 @@ -var img = require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=")); +function satelliteImage() { + return require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AGnE4F1wvsF34wgFldcLdyMYsoACF1WJF4YxPFzOtF4wxNFzAvKSiIvU1ovIGAkJAAQucF5QxCFwYwbF4QwLrwvjYIVfrwABrtdq9Wqwvkq4oCAAtXmYvi1teE4NXrphCrxoCGAbvdSIoAHNQNeFzQvGeRQvCsowrYYNfF8YwHZQQFCF8QwGF4owjeYovBroHEMERhEF8IwNrtWryYFF8YwCq4vhGBeJF5AwaxIwKwVXFwwvandfMJeJF8M6nZiLGQIvdstfGAVlGBZkCxJeZJQIwCGIRjMFzYACGIc6r/+FsIvGGIYABEzYvPGQYvusovkAH4A/AH4A/ACo=")); +} + +var fix; Bangle.setLCDPower(1); Bangle.setLCDTimeout(0); +var Layout = require("Layout"); +Bangle.setGPSPower(1, "app"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +E.showMessage("Loading..."); // avoid showing rubbish on screen -g.clear(); - -var fix; -Bangle.setGPSPower(1); -Bangle.on('GPS',function(f) { - fix = f; - g.reset(1); - g.setFont("6x8",2); - g.setFontAlign(0,0); - g.clearRect(90,30,239,90); - if (fix.fix) { - g.drawString("GPS",170,40); - g.drawString("Acquired",170,60); +function setGPSTime() { + if (fix.time!==undefined) { + setTime(fix.time.getTime()/1000); + E.showMessage("System time set", {img:require("heatshrink").decompress(atob("lEo4UBvvv///vEFBYNVAAWq1QFDBAgKGrQJD0oJDtQJD1IICqwGBFoIDByocDwAJBgQeDtWoJwcqDwWq0EAgfAgEKHoQcCBIQeBGAQaBBIQzBytaEwQJDlWlrQmBBIkK0tqBI+ptRNCBIcCBKhECBIh6CAgUL8AJHl/4BI8+3gJRl/8GJH/BI8Ah6MDLIZQB+BjGAAIoBBI84BIaVCAAaVBVIYJEWYLkEXobRDAAbRBcoYACcoT5DEwYJCtQoElWpBINaDwYcB0oJBGQIzCAYIwBDwQGBAAIcCDwYACDgQACBIYIEBQYFDA="))}); } else { - g.drawString("Waiting for",170,40); - g.drawString("GPS Fix",170,60); + E.showMessage("No GPS time to set"); } - g.setFont("6x8"); - g.drawString(fix.satellites+" satellites",170,80); - g.clearRect(0,100,239,239); - var t = ["","","","---",""]; - if (fix.time!==undefined) + Bangle.removeListener('GPS',onGPS); + setTimeout(function() { + fix = undefined; + layout.forgetLazyState(); // redraw all next time + Bangle.on('GPS',onGPS); + }, 2000); +} + +var layout = new Layout( { + type:"v", c: [ + {type:"h", c:[ + {type:"img", src:satelliteImage }, + { type:"v", fillx:1, c: [ + {type:"txt", font:"6x8:2", label:"Waiting\nfor GPS", id:"status" }, + {type:"txt", font:"6x8", label:"---", id:"sat" }, + ]}, + ]}, + {type:"txt", fillx:1, filly:1, font:"6x8:2", label:"---", id:"gpstime" } + ]},{lazy:true, btns: [ + { label : "Set", cb : setGPSTime}, + { label : "Back", cb : ()=>load() } + ]}); + + +function onGPS(f) { + if (fix===undefined) { + g.clear(); + Bangle.drawWidgets(); + } + fix = f; + if (fix.fix) { + layout.status.label = "GPS\nAcquired"; + } else { + layout.status.label = "Waiting\nfor GPS"; + } + layout.sat.label = fix.satellites+" satellites"; + + var t = ["","---",""]; + if (fix.time!==undefined) { t = fix.time.toString().split(" "); - /* - [ - "Sun", - "Nov", - "10", - "2019", - "15:55:35", - "GMT+0100" - ] - */ - //g.setFont("6x8",2); - //g.drawString(t[0],120,110); // day - g.setFont("6x8",3); - g.drawString(t[1]+" "+t[2],120,135); // date - g.setFont("6x8",2); - g.drawString(t[3],120,160); // year - g.setFont("6x8",3); - g.drawString(t[4],120,185); // time - if (fix.time) { - // timezone var tz = (new Date()).getTimezoneOffset()/-60; if (tz==0) tz="UTC"; else if (tz>0) tz="UTC+"+tz; else tz="UTC"+tz; - g.setFont("6x8",2); - g.drawString(tz,120,210); // gmt - g.setFontAlign(0,0,3); - g.drawString("Set",230,120); - g.setFontAlign(0,0); - } -}); -setInterval(function() { - g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2}); -},100); -setWatch(function() { - if (fix.time!==undefined) - setTime(fix.time.getTime()/1000); -}, BTN2, {repeat:true}); + t = [t[1]+" "+t[2],t[3],t[4],t[5],tz]; + } + + layout.gpstime.label = t.join("\n"); + layout.render(); +} + +Bangle.on('GPS',onGPS); diff --git a/modules/Layout.js b/modules/Layout.js index 03aa6249b..319f6901e 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -169,14 +169,23 @@ function Layout(layout, options) { Bangle.on('touch',Bangle.touchHandler); } - // add IDs + // recurse over layout doing some fixing up if needed var ll = this; - function idRecurser(l) { + function recurser(l) { + // add IDs if (l.id) ll[l.id] = l; + // fix type up if (!l.type) l.type=""; - if (l.c) l.c.forEach(idRecurser); + // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally + // fix fonts for pre-2v11 firmware + if (l.font && l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + if (l.c) l.c.forEach(recurser); } - idRecurser(layout); + recurser(this._l); this.updateNeeded = true; } @@ -352,12 +361,6 @@ Layout.prototype.update = function() { "txt" : function(l) { if (l.font.endsWith("%")) l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.wrap) { l._h = l._w = 0; } else { From 1cc7674aa7f990f88644e78d9d19cd981ea34324 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 20:48:38 +0100 Subject: [PATCH 081/325] Fix issue where re-running bootupdate could disable existing polyfills --- apps/boot/ChangeLog | 2 +- apps/boot/bootupdate.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 4ce4dbe65..59d9e4c65 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -33,4 +33,4 @@ 0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics 0.32: Fix single quote error in g.wrapString polyfill improve g.stringMetrics polyfill - + Fix issue where re-running bootupdate could disable existing polyfills diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index a5ec01fa4..269a80831 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -81,9 +81,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`; if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; // Pre-2v10 firmwares without a theme/setUI +delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.theme) { boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`; } +delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it boot += `Bangle.setUI=function(mode, cb) { if (Bangle.btnWatches) { @@ -131,6 +133,7 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.imageMetrics=function(src) { if (src[0]) return {width:src[0],height:src[1]}; @@ -141,12 +144,14 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; };\n`; } +delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.stringMetrics=function(txt) { txt = txt.toString().split("\\n"); return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; };\n`; } +delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.wrapString=function(str, maxWidth) { var lines = []; From 7d99cde28dd587de1f0140eaedf65efca48bc89b Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 21:13:40 +0100 Subject: [PATCH 082/325] Stopwatch Touch: README and screenshots --- apps/stopwatch/A.jpg | Bin 0 -> 77328 bytes apps/stopwatch/B.jpg | Bin 0 -> 69988 bytes apps/stopwatch/README.md | 21 +++++++++++++++++---- apps/stopwatch/screenshot1.png | Bin 0 -> 1783 bytes apps/stopwatch/screenshot2.png | Bin 0 -> 1765 bytes apps/stopwatch/screenshot3.png | Bin 0 -> 1938 bytes apps/stopwatch/stopwatch.app.js | 4 +--- 7 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 apps/stopwatch/A.jpg create mode 100644 apps/stopwatch/B.jpg create mode 100644 apps/stopwatch/screenshot1.png create mode 100644 apps/stopwatch/screenshot2.png create mode 100644 apps/stopwatch/screenshot3.png diff --git a/apps/stopwatch/A.jpg b/apps/stopwatch/A.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9155b9986e05f68312ad383e52490d45f8584542 GIT binary patch literal 77328 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

5%2T4Dz|IU;f>liup3Kanl0p2s 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 Z{{Z>S*8c$2^MCJuM&kbfW^T}<|Jf0-Ls0+# literal 0 HcmV?d00001 diff --git a/apps/stopwatch/B.jpg b/apps/stopwatch/B.jpg new file mode 100644 index 0000000000000000000000000000000000000000..639ff5d42b66c0d14fe451e0834cf174fb626050 GIT binary patch literal 69988 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 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md index 1924cc343..30a9306d1 100644 --- a/apps/stopwatch/README.md +++ b/apps/stopwatch/README.md @@ -1,20 +1,33 @@ -# Stopwatch +# Stopwatch Touch A touch screen based stop watch for Bangle 2 ## Screenshots +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) + ## Features -* Feature A -* Feature B +* Attractive UI design +* Will run up to 99 hours +* Shows 10th of seconds up to 1 hour +* Start / Pause button +* Reset button ## Future features I'm keen to complete this project with * Ability to dismiss the app and leave it running in the background -* A small widget to show the elapsed time on the curren active clock +* A small widget to show the elapsed time on the current active clock * Laptimes, with a way to view all the laptimes on a scrollable screen +## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't + +Which one is which ? + +![](A.jpg) +![](B.jpg) diff --git a/apps/stopwatch/screenshot1.png b/apps/stopwatch/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..6d94ce05c0f472e51b65eeba67e124de4b7f239a GIT binary patch literal 1783 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-ahlfo-Fwi(^Pd z+}qn1gQUHATmygn?|&x7sI{-}ZJ_t%o{+7dW`&)9S-WjE=Y;Ruw{Pz^uNRQ;dNB8Z z^K9YY{ESS(i6)Fp{p1~!ihxHwMs{eCbxGP!q{{F+Bi9GAC zwDJ$YVMPp+Sz`h7R3{$)<=R|kq2{ybIIAV z#{M|Yd9|K>_im)WlW*Misd4vReJkpk0+Pgj%D+cI=n`X&`I%TKcU>|F9=!+TaBSLuVICewX|Pq#Db6J6?Nl`gXZd8>VX z&$;&p`uYAozpc#w?|2d0g;W0oC+s$Ub8r7)NoC1J*?zUHKWbihF-q<%doRzp-TLsm zzt(dMCn;`v7S7VgvNLw_PkmSI^A+3bL*}H*?=d#A+07F6t?rv>&eNuwwa2c0wu!Fa zb>J$~ruWr>3ajP&L~ouy@nN&O>jKU6o=;U<`wp0wEHdA~|8is1#O@ar(;tgQvz)u5 zG?RJ#ouG+-!&%CX9WQdVkk6Kk-x&J9<7B$bkEtiLf2RY*^_jHiJg$;k|3|d3KRKe& zp^u@l_>PaMqQe6<*N%zG4i99^gMB#!c6bUVJ>d|rIDR%nn29AXs71n(iRE0O@zoZF z#@Ql@Zll@=1<421569i}Ziz673oCp$%34v=%g~tp$0y#W;efdKgO8?6EbBfR*BUZ1 z#m6)pzYYxXW9PpdW#OnPWR!~sMuU|7WlW7%neN@c|3_mVm!ZA?1DoCJPx?33|NAj_ zlGfR$l}!4|yQO!C&NvY|>-1-qI5#l?Aam8jN~ZQe+sGd;*!IQ0y1K~g=S!Q$)fvi6 z^>)oiLND<@=ZrZjC3+*r*%O0mWPOmSjtv28A{c-T>4_tvq zBSmlgE?mjTH2uTfzY5B`r(f^X*=f$S{9-MCSboB%X;KOWobeCY)bghOnP15iZn*B= zfg@{${@(}MX0ez_c)QoF??4N9YUF3^iM~+XYQg+^we*Y~=>l~-+rvAwlJ@&Wsz%IG zFW{UXpRxYY>T|+7d@j^$u2NcaMtFzQ`8Q92;Zn)U6aow-YViKBKlrQl{6c1w2Cyb$ N@O1TaS?83{1OV%-^rrv- literal 0 HcmV?d00001 diff --git a/apps/stopwatch/screenshot2.png b/apps/stopwatch/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..0baa733313973f63c117f606e7eaedf43cd46a21 GIT binary patch literal 1765 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 Date: Wed, 20 Oct 2021 22:55:33 +0100 Subject: [PATCH 083/325] fixed merge issue with apps.json --- apps.json | 408 ++---------------------------------------------------- 1 file changed, 15 insertions(+), 393 deletions(-) diff --git a/apps.json b/apps.json index 9a53a821e..3a19ba230 100644 --- a/apps.json +++ b/apps.json @@ -742,8 +742,7 @@ "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "icon": "widget.png", - "version":"0.14", + "version": "0.14", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "icon": "widget.png", "type": "widget", @@ -2946,396 +2945,6 @@ "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ -<<<<<<< HEAD - {"name":"banglebridge.wid.js","url":"widget.js"}, - {"name":"banglebridge.watch.img","url":"watch.img"}, - {"name":"banglebridge.heart.img","url":"heart.img"} - ] - }, -{ "id": "qmsched", - "name": "Quiet Mode Schedule and Widget", - "shortName":"Quiet Mode", - "icon": "app.png", - "version":"0.02", - "description": "Automatically turn Quiet Mode on or off at set times", - "readme": "README.md", - "tags": "tool,widget", - "storage": [ - {"name":"qmsched","url":"lib.js"}, - {"name":"qmsched.app.js","url":"app.js"}, - {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true}, - {"name":"qmsched.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"qmsched.json"} - ] -}, -{ - "id": "hourstrike", - "name": "Hour Strike", - "shortName": "Hour Strike", - "icon": "app-icon.png", - "version": "0.08", - "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", - "tags": "tool,alarm", - "readme": "README.md", - "storage": [ - {"name":"hourstrike.app.js","url":"app.js"}, - {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, - {"name":"hourstrike.json","url":"hourstrike.json"} - ] -}, -{ "id": "whereworld", - "name": "Where in the World?", - "shortName" : "Where World", - "icon": "app.png", - "version": "0.01", - "description": "Shows your current location on the world map", - "tags": "gps", - "storage": [ - {"name":"whereworld.app.js","url":"app.js"}, - {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, - {"name":"whereworld.worldmap","url":"worldmap"} - ] -}, -{ - "id": "omnitrix", - "name":"Omnitrix", - "icon":"omnitrix.png", - "version": "0.01", - "readme": "README.md", - "description": "An Omnitrix Showpiece", - "tags": "game", - "storage": [ - {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, - {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} - ] -}, -{ "id": "batclock", - "name": "Bat Clock", - "shortName":"Bat Clock", - "icon": "bat-clock.png", - "version":"0.02", - "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"batclock.app.js","url":"bat-clock.app.js"}, - {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} - ] -}, -{ "id":"doztime", - "name":"Dozenal Time", - "shortName":"Dozenal Time", - "icon":"app.png", - "version":"0.04", - "description":"A dozenal Holocene calendar and dozenal diurnal clock", - "tags":"clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"doztime.app.js","url":"app.js"}, - {"name":"doztime.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id":"gbtwist", - "name":"Gadgetbridge Twist Control", - "shortName":"Twist Control", - "icon":"app.png", - "version":"0.01", - "description":"Shake your wrist to control your music app via Gadgetbridge", - "tags":"tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator":false, - "readme": "README.md", - "storage": [ - {"name":"gbtwist.app.js","url":"app.js"}, - {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "thermom", - "name": "Thermometer", - "icon": "app.png", - "version":"0.02", - "description": "Displays the current temperature, updated every 20 seconds", - "tags": "tool", - "allow_emulator":true, - "storage": [ - {"name":"thermom.app.js","url":"app.js"}, - {"name":"thermom.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mysticdock", - "name": "Mystic Dock", - "icon": "mystic-dock.png", - "version":"1.00", - "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", - "tags": "dock", - "type":"dock", - "readme": "README.md", - "storage": [ - {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, - {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, - {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, - {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} - ] -}, -{ "id": "mysticclock", - "name": "Mystic Clock", - "icon": "mystic-clock.png", - "version":"1.01", - "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, - {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, - {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} - ] -}, -{ "id": "hcclock", - "name": "Hi-Contrast Clock", - "icon": "hcclock-icon.png", - "version":"0.01", - "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} - ] -}, -{ "id": "thermomF", - "name": "Fahrenheit Temp", - "icon": "thermf.png", - "version":"0.01", - "description": "A modification of the Thermometer App to display temprature in Fahrenheit", - "tags": "tool", - "storage": [ - {"name":"thermomF.app.js","url":"app.js"}, - {"name":"thermomF.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "nixie", - "name": "Nixie Clock", - "shortName":"Nixie", - "icon": "nixie.png", - "version":"0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"nixie.app.js","url":"app.js"}, - {"name":"nixie.img","url":"app-icon.js","evaluate":true}, - {"name":"m_vatch.js","url":"m_vatch.js"} - ] -}, -{ "id": "carcrazy", - "name": "Car Crazy", - "shortName":"Car Crazy", - "icon": "carcrash.png", - "version":"0.03", - "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", - "tags": "game", - "readme": "README.md", - "storage": [ - {"name":"carcrazy.app.js","url":"app.js"}, - {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, - {"name":"carcrazy.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"app.json"} - ] -}, -{ "id": "shortcuts", - "name": "Shortcuts", - "shortName":"Shortcuts", - "icon": "app.png", - "version":"0.01", - "description": "Quickly load your favourite apps from (almost) any watch face.", - "tags": "tool", - "type": "bootloader", - "readme": "README.md", - "storage": [ - {"name":"shortcuts.boot.js","url":"boot.js"}, - {"name":"shortcuts.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"shortcuts.json"} - ] -}, -{ "id": "vectorclock", - "name": "Vector Clock", - "icon": "app.png", - "version": "0.02", - "description": "A digital clock that uses the built-in vector font.", - "tags": "clock", - "type": "clock", - "allow_emulator": true, - "storage": [ - {"name":"vectorclock.app.js","url":"app.js"}, - {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "fd6fdetect", - "name": "fd6fdetect", - "shortName":"fd6fdetect", - "icon": "app.png", - "version":"0.1", - "description": "Allows you to see 0xFD6F beacons near you.", - "tags": "tool", - "storage": [ - {"name":"fd6fdetect.app.js","url":"app.js"}, - {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "choozi", - "name": "Choozi", - "icon": "app.png", - "version":"0.01", - "description": "Choose people or things at random using Bangle.js.", - "tags": "tool", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"choozi.app.js","url":"app.js"}, - {"name":"choozi.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widclkbttm", - "name": "Digital clock (Bottom) widget", - "shortName":"Digital clock Bottom Widget", - "icon": "widclkbttm.png", - "version":"0.03", - "description": "Displays time in the bottom area.", - "readme": "README.md", - "tags": "widget", - "type": "widget", - "storage": [ - {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} - ] -}, -{ "id": "pastel", - "name": "Pastel Clock", - "shortName": "Pastel", - "icon": "pastel.png", - "version":"0.05", - "description": "A Configurable clock with custom fonts and background", - "tags": "clock,b2", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, - {"name":"pastel.settings.js","url":"pastel.settings.js"} - ], - "data": [ - {"name":"pastel.json"} - ] -}, -{ "id": "antonclk", - "name": "Anton Clock", - "icon": "app.png", - "version":"0.02", - "description": "A simple clock using the bold Anton font.", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "waveclk", - "name": "Wave Clock", - "icon": "app.png", - "version":"0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"waveclk.app.js","url":"app.js"}, - {"name":"waveclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "floralclk", - "name": "Floral Clock", - "icon": "app.png", - "version":"0.01", - "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"floralclk.app.js","url":"app.js"}, - {"name":"floralclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "score", - "name": "Score Tracker", - "icon": "score.app.png", - "version":"0.01", - "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", - "readme": "README.md", - "tags": "b2", - "type": "app", - "storage": [ - {"name":"score.app.js","url":"score.app.js"}, - {"name":"score.settings.js","url":"score.settings.js"}, - {"name":"score.presets.json","url":"score.presets.json"}, - {"name":"score.img","url":"score.app-icon.js","evaluate":true} - ], - "data": [ - {"name":"score.json"} - ] -}, -{ "id": "menusmall", - "name": "Small Menus", - "icon": "app.png", - "version":"0.01", - "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "tags": "b2,bno1,system", - "type": "boot", - "storage": [ - {"name":"menusmall.boot.js","url":"boot.js"} - ] -}, -{ "id": "ffcniftya", - "name": "Nifty-A Clock", - "icon": "app.png", - "version":"0.01", - "description": "A nifty clock with time and date", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"ffcniftya.app.js","url":"app.js"}, - {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "stopwatch", - "name": "Stopwatch Touch", - "shortName":"Stopwatch Touch", - "icon": "stopwatch.png", - "version":"0.01", - "description": "A touch controlled stopwatch for Bangle 2", - "readme": "README.md", - "tags": "tool, b2", - "storage": [ - {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, - {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} - ] -} -======= {"name":"gpstimeserver.wid.js","url":"widget.js"} ] }, @@ -4352,6 +3961,19 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "stopwatch", + "name": "Stopwatch Touch", + "version": "0.01", + "description": "A touch based stop watch for Bangle JS 2", + "icon": "stopwatch.png", + "tags": "tools,app,b2", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] } ->>>>>>> upstream/master ] From 96685e531d915a4321b672535f2f1ba3d47f8f56 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 08:29:07 +0100 Subject: [PATCH 084/325] Rename launchers --- apps.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 7afcd9cb5..e098d466d 100644 --- a/apps.json +++ b/apps.json @@ -79,10 +79,10 @@ }, { "id": "launch", - "name": "Launcher (Default)", + "name": "Launcher (Bangle.js 1 default)", "shortName": "Launcher", "version": "0.07", - "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", + "description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", @@ -94,10 +94,10 @@ }, { "id": "launchb2", - "name": "Launcher (Bangle.js 2)", + "name": "Launcher (Bangle.js 2 default)", "shortName": "Launcher", "version": "0.03", - "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.", + "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", From d2d1f5b8cdc3e857323b3a37f2e1d51cb9813af0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 09:13:17 +0100 Subject: [PATCH 085/325] General clock update - fix svclock after last commit, ensure other clocks update on the minute (not 1-15 seconds later), add Bangle.js 2 and theme compatibility --- apps.json | 14 ++--- apps/berlinc/ChangeLog | 3 + apps/berlinc/berlin-clock.js | 66 ++++++++++++---------- apps/sclock/ChangeLog | 1 + apps/sclock/clock-simple.js | 33 ++++++++--- apps/svclock/ChangeLog | 3 +- apps/svclock/vclock-simple.js | 45 +++++++++------ apps/worldclock/ChangeLog | 2 + apps/worldclock/app.js | 101 +++++++++++++++++++--------------- 9 files changed, 160 insertions(+), 108 deletions(-) diff --git a/apps.json b/apps.json index e098d466d..d0fe261e4 100644 --- a/apps.json +++ b/apps.json @@ -1026,7 +1026,7 @@ { "id": "sclock", "name": "Simple Clock", - "version": "0.06", + "version": "0.07", "description": "A Simple Digital Clock", "icon": "clock-simple.png", "type": "clock", @@ -1071,12 +1071,12 @@ { "id": "svclock", "name": "Simple V-Clock", - "version": "0.03", + "version": "0.04", "description": "Modification of Simple Clock 0.04 to use Vectorfont", "icon": "vclock-simple.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, @@ -1376,12 +1376,12 @@ { "id": "berlinc", "name": "Berlin Clock", - "version": "0.04", + "version": "0.05", "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", "icon": "berlin-clock.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, @@ -2804,12 +2804,12 @@ "id": "worldclock", "name": "World Clock - 4 time zones", "shortName": "World Clock", - "version": "0.04", + "version": "0.05", "description": "Current time zone plus up to four others", "icon": "app.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/berlinc/ChangeLog b/apps/berlinc/ChangeLog index 65ed0505f..9e9c1a6aa 100644 --- a/apps/berlinc/ChangeLog +++ b/apps/berlinc/ChangeLog @@ -1,3 +1,6 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal. 0.04: Update to use Bangle.setUI instead of setWatch +0.05: Update *on* the minute rather than every 15 secs + Now show widgets + Make compatible with themes, and Bangle.js 2 diff --git a/apps/berlinc/berlin-clock.js b/apps/berlinc/berlin-clock.js index 144fa5ba7..0dd8ff8ee 100644 --- a/apps/berlinc/berlin-clock.js +++ b/apps/berlinc/berlin-clock.js @@ -1,7 +1,7 @@ // Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr // https://github.com/eska-muc/BangleApps const fields = [4, 4, 11, 4]; -const offset = 20; +const offset = 24; const width = g.getWidth() - 2 * offset; const height = g.getHeight() - 2 * offset; const rowHeight = height / 4; @@ -10,11 +10,23 @@ var show_date = false; var show_time = false; var yy = 0; -rowlights = []; -time_digit = []; +var rowlights = []; +var time_digit = []; -function drawBerlinClock() { - g.clear(); +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()); var now = new Date(); // show date below the clock @@ -24,8 +36,7 @@ function drawBerlinClock() { var day = now.getDate(); var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`; var strWidth = g.stringWidth(dateString); - g.setColor(1, 1, 1); - g.setFontAlign(-1,-1); + g.setColor(g.theme.fg).setFontAlign(-1,-1); g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4); } @@ -50,8 +61,7 @@ function drawBerlinClock() { x2 = (col + 1) * boxWidth + offset; y2 = (row + 1) * rowHeight + offset; - g.setColor(1, 1, 1); - g.drawRect(x1, y1, x2, y2); + g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2); if (col < rowlights[row]) { if (row === 2) { if (((col + 1) % 3) === 0) { @@ -65,46 +75,42 @@ function drawBerlinClock() { g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2); } if (row == 3 && show_time) { - g.setColor(1,1,1); - g.setFontAlign(0,0); + g.setColor(g.theme.fg).setFontAlign(0,0); g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2); } } } + + queueDraw(); } function toggleDate() { show_date = ! show_date; - drawBerlinClock(); + draw(); } function toggleTime() { show_time = ! show_time; - drawBerlinClock(); + draw(); } -// special function to handle display switch on -Bangle.on('lcdPower', (on) => { - g.clear(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - Bangle.drawWidgets(); - // call your app function here - drawBerlinClock(); + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// refesh every 15 sec -setInterval(drawBerlinClock, 15E3); +// Show launcher when button pressed, handle up/down +Bangle.setUI("clockupdown", dir=> { + if (dir<0) toggleTime(); + if (dir>0) toggleDate(); +}); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -drawBerlinClock(); -if (BTN3) { - // Toggle date display, when BTN3 is pressed - setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"}); - // Toggle date display, when BTN3 is pressed - setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"}); -} -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/sclock/ChangeLog b/apps/sclock/ChangeLog index 44a0ec504..dc76b8299 100644 --- a/apps/sclock/ChangeLog +++ b/apps/sclock/ChangeLog @@ -3,3 +3,4 @@ 0.04: Make this clock do 12h and 24h 0.05: setUI, screen size changes 0.06: Use Bangle.setUI for button/launcher handling +0.07: Update *on* the minute rather than every 15 secs diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js index 8fb204d22..a399b05a7 100644 --- a/apps/sclock/clock-simple.js +++ b/apps/sclock/clock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const big = g.getWidth()>200; const timeFontSize = big?6:5; const dateFontSize = big?3:2; @@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9; // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -60,11 +71,18 @@ function drawSimpleClock() { var gmt = da[5]; g.setFont(font, gmtFontSize); g.drawString(gmt, xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); // clean app screen @@ -74,8 +92,5 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); +draw(); diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 4db60ecd5..fb71fbeb8 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling -0.03: Scale to BangleJS 2 and add locale \ No newline at end of file +0.03: Scale to BangleJS 2 and add locale +0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index 637701a3f..e08c6fa2c 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const locale = require("locale"); var timeFontSize; @@ -12,8 +11,7 @@ var yposDate; var yposYear; var yposGMT; -switch (process.env.BOARD) { - case "EMSCRIPTEN": +if (g.getWidth() > 200) { timeFontSize = 65; dateFontSize = 20; gmtFontSize = 10; @@ -22,8 +20,7 @@ switch (process.env.BOARD) { yposDate = 130; yposYear = 175; yposGMT = 220; - break; - case "EMSCRIPTEN2": +} else { timeFontSize = 48; dateFontSize = 15; gmtFontSize = 10; @@ -32,12 +29,23 @@ switch (process.env.BOARD) { yposDate = 95; yposYear = 128; yposGMT = 161; - break; } // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { g.clear(); Bangle.drawWidgets(); @@ -76,23 +84,26 @@ function drawSimpleClock() { // draw gmt g.setFont(font, gmtFontSize); g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); +// Show launcher when button pressed +Bangle.setUI("clock"); // clean app screen g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/worldclock/ChangeLog b/apps/worldclock/ChangeLog index e922ef2a4..831dd3b5c 100644 --- a/apps/worldclock/ChangeLog +++ b/apps/worldclock/ChangeLog @@ -2,3 +2,5 @@ 0.02: Update custom.html for refactor; add README 0.03: Update for larger secondary timezone display (#610) 0.04: setUI, different screen sizes +0.05: Now update *on* the minute rather than every 15 secs + Fix rendering of single extra timezone on Bangle.js 2 diff --git a/apps/worldclock/app.js b/apps/worldclock/app.js index 84cb29874..2627e056c 100644 --- a/apps/worldclock/app.js +++ b/apps/worldclock/app.js @@ -1,5 +1,3 @@ -/* jshint esversion: 6 */ - const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; @@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1; const font = "6x8"; +/* TODO: we could totally use 'Layout' here and +avoid a whole bunch of hard-coded offsets */ + + const xyCenter = g.getWidth() / 2; const yposTime = big ? 75 : 60; +const yposTime2 = yposTime + (big ? 100 : 60); const yposDate = big ? 130 : 90; const yposWorld = big ? 170 : 120; @@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || []; // TESTING CODE // Used to test offset array values during development. // Uncomment to override secondary offsets value - -// const mockOffsets = { -// zeroOffsets: [], -// oneOffset: [["UTC", 0]], -// twoOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ], -// fourOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Miami", -5], -// ], -// fiveOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Chicago", -6], -// ["Miami", -5], -// ], -// }; +/* +const mockOffsets = { + zeroOffsets: [], + oneOffset: [["UTC", 0]], + twoOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ], + fourOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Miami", -5], + ], + fiveOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Chicago", -6], + ["Miami", -5], + ], +};*/ // Uncomment one at a time to test various offsets array scenarios -// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time -// offsets = mockOffsets.oneOffset; // should render larger in two rows -// offsets = mockOffsets.twoOffsets; // should render two in columns -// offsets = mockOffsets.fourOffsets; // should render in columns -// offsets = mockOffsets.fiveOffsets; // should render first four in columns +//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time +//offsets = mockOffsets.oneOffset; // should render larger in two rows +//offsets = mockOffsets.twoOffsets; // should render two in columns +//offsets = mockOffsets.fourOffsets; // should render in columns +//offsets = mockOffsets.fiveOffsets; // should render first four in columns // END TESTING CODE // Check settings for what type our clock should be //var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var secondInterval; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} function doublenum(x) { return x < 10 ? "0" + x : "" + x; @@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) { return new Date(dt.getTime() + offset * 60 * 60 * 1000); } -function drawSimpleClock() { +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -111,9 +125,9 @@ function drawSimpleClock() { // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize); - g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true); + g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); g.setFont(font, secondaryTimeZoneFontSize); - g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true); + g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true); // draw Day, name of month, Date g.setFont(font, secondaryTimeZoneFontSize); @@ -132,6 +146,8 @@ function drawSimpleClock() { g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true); } }); + + queueDraw(); } // clean app screen @@ -141,18 +157,15 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec when screen is on -Bangle.on("lcdPower", (on) => { - if (secondInterval) clearInterval(secondInterval); - secondInterval = undefined; +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - secondInterval = setInterval(drawSimpleClock, 15e3); - drawSimpleClock(); // draw immediately + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// draw now and every 15 sec until display goes off -drawSimpleClock(); -if (Bangle.isLCDOn()) { - secondInterval = setInterval(drawSimpleClock, 15e3); -} +// draw now +draw(); From e829ad54ac4b71bfcc3a1993473b85e548eaaef4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 10:14:00 +0100 Subject: [PATCH 086/325] widid works on bangle2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index d0fe261e4..ed9091d66 100644 --- a/apps.json +++ b/apps.json @@ -1502,7 +1502,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,address,mac", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widid.wid.js","url":"widget.js"} ] From 26f43a787ae180bd00ea1c345fe6aa2a36cbb746 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 10:14:40 +0100 Subject: [PATCH 087/325] Move default app handling into loader.js, clear list of installed apps on disconnect different default apps for the 2 bangle versions --- core | 2 +- defaultapps.json => defaultapps_banglejs1.json | 0 defaultapps_banglejs2.json | 1 + loader.js | 18 +++++++++++++++++- 4 files changed, 19 insertions(+), 2 deletions(-) rename defaultapps.json => defaultapps_banglejs1.json (100%) create mode 100644 defaultapps_banglejs2.json diff --git a/core b/core index bc5b1284f..cdbf46fea 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479 +Subproject commit cdbf46feaeefcb0d48ce9c170e90786dab11a03f diff --git a/defaultapps.json b/defaultapps_banglejs1.json similarity index 100% rename from defaultapps.json rename to defaultapps_banglejs1.json diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json new file mode 100644 index 000000000..2d32d285c --- /dev/null +++ b/defaultapps_banglejs2.json @@ -0,0 +1 @@ +["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"] diff --git a/loader.js b/loader.js index 90c1c5d96..45ec87df3 100644 --- a/loader.js +++ b/loader.js @@ -133,8 +133,8 @@ window.addEventListener('load', (event) => { }); }); -// Hook onto device chooser dropdown window.addEventListener('load', (event) => { + // Hook onto device chooser dropdown htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { button.addEventListener("click", event => { var a = event.target; @@ -144,4 +144,20 @@ window.addEventListener('load', (event) => { document.querySelector(".devicetype-nav span").innerText = a.innerText; }); }); + + // Button to install all default apps in one go + document.getElementById("installdefault").addEventListener("click",event=>{ + getInstalledApps().then(() => { + if (device.id == "BANGLEJS") + return httpGet("defaultapps_banglejs.json"); + if (device.id == "BANGLEJS2") + return httpGet("defaultapps_banglejs2.json"); + throw new Error("Unknown device "+device.id); + }).then(json=>{ + return installMultipleApps(JSON.parse(json), "default"); + }).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App Install failed, "+err,"error"); + }); + }); }); From 86ad7ee037666b876d75480ad6ca6c48b45a7b94 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 21 Oct 2021 10:16:39 +0200 Subject: [PATCH 088/325] feat: set compass power off when screen is off --- apps/arrow/app.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/arrow/app.js b/apps/arrow/app.js index 2cb3a42ad..ffa821a09 100644 --- a/apps/arrow/app.js +++ b/apps/arrow/app.js @@ -142,10 +142,13 @@ function docalibrate(e,first){ startdraw(); setTimeout(setButtons,1000); } - } - if (first===undefined) first=false; - stopdraw(); + } + + if (first === undefined) first = false; + + stopdraw(false); clearWatch(); + if (first) E.showAlert(msg,title).then(action.bind(null,true)); else @@ -153,16 +156,30 @@ function docalibrate(e,first){ } function startdraw(){ + if (!Bangle.isCompassOn()) { + Bangle.setCompassPower(1); + } + g.clear(); g.setColor(1,1,1); Bangle.drawWidgets(); candraw = true; + if (intervalRef) clearInterval(intervalRef); intervalRef = setInterval(reading,500); } -function stopdraw() { +function stopdraw(powerOffCompass) { + if (powerOffCompass === undefined) { + powerOffCompass = true; + } candraw=false; - if(intervalRef) {clearInterval(intervalRef);} + + if (powerOffCompass) { + Bangle.setCompassPower(0); + } + if (intervalRef) { + clearInterval(intervalRef); + } } function setButtons(){ @@ -170,7 +187,7 @@ function setButtons(){ setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"}); } - + Bangle.on('lcdPower',function(on) { if (on) { startdraw(); @@ -182,6 +199,5 @@ Bangle.on('lcdPower',function(on) { Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); Bangle.loadWidgets(); -Bangle.setCompassPower(1); startdraw(); setButtons(); From 9f05531ba7fe8bdecfeb22e6dadc7ab5fe8589fc Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:08:53 +0100 Subject: [PATCH 089/325] new spectre css --- css/spectre-exp.min.css | 2 +- css/spectre-icons.min.css | 2 +- css/spectre.min.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/css/spectre-exp.min.css b/css/spectre-exp.min.css index 942cf59bf..d3137743a 100644 --- a/css/spectre-exp.min.css +++ b/css/spectre-exp.min.css @@ -1 +1 @@ -/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/css/spectre-icons.min.css b/css/spectre-icons.min.css index 9b6167caa..0276f7b84 100644 --- a/css/spectre-icons.min.css +++ b/css/spectre-icons.min.css @@ -1 +1 @@ -/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/css/spectre.min.css b/css/spectre.min.css index 8df0bf64f..0fe23d9c0 100644 --- a/css/spectre.min.css +++ b/css/spectre.min.css @@ -1 +1 @@ -/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:sticky!important;position:-webkit-sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file From 47f32f34fa32af68206809523184c8b39d24b490 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:43:13 +0100 Subject: [PATCH 090/325] more bangle 2 compatibility --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index ed9091d66..98d9c672e 100644 --- a/apps.json +++ b/apps.json @@ -320,7 +320,7 @@ "icon": "slidingtext.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "allow_emulator": false, @@ -594,7 +594,7 @@ "description": "Application that allows you to record a GPS track. Can run in background", "icon": "app.png", "tags": "tool,outdoors,gps,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "interface": "interface.html", "storage": [ From 69c5c9d8a2335a2910248ccf2d7dbea1f3e8c673 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:43:30 +0100 Subject: [PATCH 091/325] move to icons defined in main.css --- core | 2 +- css/main.css | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core b/core index cdbf46fea..3a2c706b4 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit cdbf46feaeefcb0d48ce9c170e90786dab11a03f +Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a diff --git a/css/main.css b/css/main.css index 90b4ff280..44137e9b1 100644 --- a/css/main.css +++ b/css/main.css @@ -35,6 +35,21 @@ top: 36px; left: -24px; } -.btn-favourite { - color: red; +.btn.btn-favourite { color: red; } +.btn.btn-favourite:hover { color: red; } + +.icon.icon-emulator { text-indent: 0px; } /*override spectre*/ +.icon.icon-emulator::before { + content: "\01F5B5"; +} +.icon.icon-favourite { text-indent: 0px; } /*override spectre*/ +.icon.icon-favourite::before { + content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */ +} +.icon.icon-favourite-active::before { + content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ +} +.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/ +.icon.icon-interface::before { + content: "\01F5AB"; } From 38ab8521d12338305bf65991641b625b86df9c6d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 12:17:18 +0100 Subject: [PATCH 092/325] Fix compass and compass widget --- apps.json | 6 ++-- apps/compass/ChangeLog | 3 +- apps/compass/compass.js | 78 ++++++++++++++++++++++++----------------- apps/widcom/ChangeLog | 3 ++ apps/widcom/widget.js | 35 ++++++------------ 5 files changed, 64 insertions(+), 61 deletions(-) create mode 100644 apps/widcom/ChangeLog diff --git a/apps.json b/apps.json index 98d9c672e..6e2998d59 100644 --- a/apps.json +++ b/apps.json @@ -537,11 +537,11 @@ { "id": "compass", "name": "Compass", - "version": "0.03", + "version": "0.04", "description": "Simple compass that points North", "icon": "compass.png", "tags": "tool,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"compass.app.js","url":"compass.js"}, {"name":"compass.img","url":"compass-icon.js","evaluate":true} @@ -3382,7 +3382,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,compass", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widcom.wid.js","url":"widget.js"} diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index e70a5688b..d2bfbd4fa 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Show text if uncalibrated -0.03: Eliminate flickering \ No newline at end of file +0.03: Eliminate flickering +0.04: Fix for Bangle.js 2 and themes diff --git a/apps/compass/compass.js b/apps/compass/compass.js index 9b7ed56b7..d26081dd5 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -1,60 +1,72 @@ -var tg = Graphics.createArrayBuffer(120,20,1,{msb:true}); -var timg = { - width:tg.getWidth(), - height:tg.getHeight(), - bpp:1, - buffer:tg.buffer -}; - -var ag = Graphics.createArrayBuffer(160,160,2,{msb:true}); +var W = g.getWidth(); +var M = W/2; // middle of screen +// Angle buffer +var AGS = W > 200 ? 160 : 120; // buffer size +var AGM = AGS/2; // midpoint/radius +var AGH = AGM-10; // hand size +var ag = Graphics.createArrayBuffer(AGS,AGS,2,{msb:true}); var aimg = { width:ag.getWidth(), height:ag.getHeight(), bpp:2, buffer:ag.buffer, - palette:new Uint16Array([0,0x03FF,0xF800,0x001F]) + palette:new Uint16Array([ + g.theme.bg, + g.toColor("#07f"), + g.toColor("#f00"), + g.toColor("#00f")]) }; -ag.setColor(1); -ag.fillCircle(80,80,79,79); -ag.setColor(0); -ag.fillCircle(80,80,69,69); +ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1); +ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11); function arrow(r,c) { r=r*Math.PI/180; var p = Math.PI/2; - ag.setColor(c); - ag.fillPoly([ - 80+60*Math.sin(r), 80-60*Math.cos(r), - 80+10*Math.sin(r+p), 80-10*Math.cos(r+p), - 80+10*Math.sin(r-p), 80-10*Math.cos(r-p), + ag.setColor(c).fillPoly([ + AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r), + AGM+10*Math.sin(r+p), AGM-10*Math.cos(r+p), + AGM+10*Math.sin(r-p), AGM-10*Math.cos(r-p), ]); } +var wasUncalibrated = false; var oldHeading = 0; Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; - tg.clear(); - tg.setFont("6x8",1); - tg.setColor(1); + g.reset(); if (isNaN(m.heading)) { - tg.setFontAlign(0,-1); - tg.setFont("6x8",1); - tg.drawString("Uncalibrated",60,4); - tg.drawString("turn 360° around",60,12); + if (!wasUncalibrated) { + g.clearRect(0,24,W,48); + g.setFontAlign(0,-1).setFont("6x8"); + g.drawString("Uncalibrated\nturn 360° around",M,24+4); + wasUncalibrated = true; + } + } else { + if (wasUncalibrated) { + g.clearRect(0,24,W,48); + wasUncalibrated = false; + } + g.setFontAlign(0,0).setFont("6x8",3); + var y = 36; + g.clearRect(M-40,y,M+40,y+24); + g.drawString(Math.round(m.heading),M,y,true); } - else { - tg.setFontAlign(0,0); - tg.setFont("6x8",2); - tg.drawString(Math.round(m.heading),60,12); - } - g.drawImage(timg,0,0,{scale:2}); + ag.setColor(0); arrow(oldHeading,0); arrow(oldHeading+180,0); arrow(m.heading,2); arrow(m.heading+180,3); - g.drawImage(aimg,40,50); + g.drawImage(aimg, + (W-ag.getWidth())/2, + g.getHeight()-(ag.getHeight()+4)); oldHeading = m.heading; }); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); Bangle.setCompassPower(1); +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); diff --git a/apps/widcom/ChangeLog b/apps/widcom/ChangeLog new file mode 100644 index 000000000..5d08e91e6 --- /dev/null +++ b/apps/widcom/ChangeLog @@ -0,0 +1,3 @@ +0.02: Works with light theme + Doesn't drain battery by updating every 2 secs + Fix alignment diff --git a/apps/widcom/widget.js b/apps/widcom/widget.js index b9c911dbf..bce9453c5 100644 --- a/apps/widcom/widget.js +++ b/apps/widcom/widget.js @@ -1,30 +1,17 @@ (function(){ - //var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA")); - //var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA==")); - var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A")); - - function draw() { + var cp = Bangle.setCompassPower; + Bangle.setCompassPower = () => { + cp.apply(Bangle, arguments); + WIDGETS.compass.draw(); + }; + + WIDGETS.compass={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isCompassOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor(g.theme.dark ? "#FC0" : "#F00"); } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); } - g.drawImage(img, 10+this.x, 2+this.var); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.compass.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.compass={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var); + }}; })(); From 852f911dcfce73a2f03f0a474ba1e965be99b587 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 12:40:34 +0100 Subject: [PATCH 093/325] testing/converting heart rate apps for Bangle.js 2 --- apps.json | 16 +++++------ apps/heart/ChangeLog | 1 + apps/heart/app.js | 6 ++-- apps/hrm/ChangeLog | 1 + apps/hrm/heartrate-icon.js | 2 +- apps/hrm/heartrate.js | 26 ++++++++++------- apps/widhrm/ChangeLog | 1 + apps/widhrm/widget.js | 57 +++++++++++++++++--------------------- apps/widhrt/ChangeLog | 4 ++- apps/widhrt/widget.js | 30 +++++++------------- 10 files changed, 69 insertions(+), 75 deletions(-) diff --git a/apps.json b/apps.json index 6e2998d59..94d5e49e7 100644 --- a/apps.json +++ b/apps.json @@ -624,11 +624,11 @@ { "id": "heart", "name": "Heart Rate Recorder", - "version": "0.06", + "version": "0.07", "description": "Application that allows you to record your heart rate. Can run in background", "icon": "app.png", "tags": "tool,health,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "interface": "interface.html", "storage": [ {"name":"heart.app.js","url":"app.js"}, @@ -818,11 +818,11 @@ { "id": "hrm", "name": "Heart Rate Monitor", - "version": "0.05", + "version": "0.06", "description": "Measure your heart rate and see live sensor data", "icon": "heartrate.png", "tags": "health", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"hrm.app.js","url":"heartrate.js"}, {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true} @@ -831,12 +831,12 @@ { "id": "widhrm", "name": "Simple Heart Rate widget", - "version": "0.04", + "version": "0.05", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", "icon": "widget.png", "type": "widget", "tags": "health,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widhrm.wid.js","url":"widget.js"} ] @@ -3334,12 +3334,12 @@ { "id": "widhrt", "name": "HRM Widget", - "version": "0.02", + "version": "0.03", "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", "icon": "widget.png", "type": "widget", "tags": "widget,hrm", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widhrt.wid.js","url":"widget.js"} diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog index 8274169ee..f6fd9793e 100644 --- a/apps/heart/ChangeLog +++ b/apps/heart/ChangeLog @@ -12,3 +12,4 @@ Generate scale based on defined minimum and maximum measurement Added background line on 50% to ease estimation of drawn values 0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.07: theme support diff --git a/apps/heart/app.js b/apps/heart/app.js index 77a1c2106..ed249c343 100644 --- a/apps/heart/app.js +++ b/apps/heart/app.js @@ -221,9 +221,9 @@ function graphRecord(n) { if (tempCount == startLine) { // generating rgaph in loop when reaching startLine to keep loading // message on screen until graph can be drawn - g.clear(). + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()). // Home for Btn2 - setColor(1, 1, 1). + setColor(g.theme.fg). drawLine(220, 118, 227, 110). drawLine(227, 110, 234, 118). drawPoly([222,117,222,125,232,125,232,117], false). @@ -245,7 +245,7 @@ function graphRecord(n) { // scale indicator line for 50% drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2). // background line for 50% - setColor(1, 1, 1). + setColor(g.theme.fg). drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2). setFontAlign(1, -1, 0). setFont("Vector", 10); diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index d27886b15..9b390b63e 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -3,3 +3,4 @@ 0.03: Fix timing issues, and use 1/2 scale to keep graph on screen 0.04: Update for new firmwares that have a 'HRM-raw' event 0.05: Tweaks for 'HRM-raw' handling +0.06: Add widgets diff --git a/apps/hrm/heartrate-icon.js b/apps/hrm/heartrate-icon.js index cadbc7dfa..20c9b15f7 100644 --- a/apps/hrm/heartrate-icon.js +++ b/apps/hrm/heartrate-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA=")) +require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4=")) diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index a6b8e791a..a47251010 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -4,13 +4,14 @@ Bangle.setHRMPower(1); var hrmInfo, hrmOffset = 0; var hrmInterval; var btm = g.getHeight()-1; +var lastHrmPt = []; // last xy coords we draw a line to function onHRM(h) { if (counter!==undefined) { // the first time we're called remove // the countdown counter = undefined; - g.clear(); + g.clearRect(0,24,g.getWidth(),g.getHeight()); } hrmInfo = h; /* On 2v09 and earlier firmwares the only solution for realtime @@ -28,7 +29,7 @@ function onHRM(h) { var px = g.getWidth()/2; g.setFontAlign(0,0); - g.clearRect(0,24,239,80); + g.clearRect(0,24,g.getWidth(),80); g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); var str = hrmInfo.bpm; g.setFontVector(40).drawString(str,px,45); @@ -43,17 +44,18 @@ Bangle.on('HRM-raw', function(v) { hrmOffset++; if (hrmOffset>g.getWidth()) { hrmOffset=0; - g.clearRect(0,80,239,239); - g.moveTo(-100,0); + g.clearRect(0,80,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } y = E.clip(btm-v.filt/4,btm-10,btm); g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y); y = E.clip(170 - (v.raw/2),80,btm); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; if (counter !==undefined) { counter = undefined; - g.clear(); + g.clearRect(0,24,g.getWidth(),g.getHeight()); } }); @@ -65,7 +67,10 @@ function countDown() { setTimeout(countDown, 1000); } } -g.clear().setFont("6x8",2).setFontAlign(0,0); +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +g.reset().setFont("6x8",2).setFontAlign(0,0); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); @@ -79,13 +84,14 @@ function readHRM() { if (!hrmInfo) return; if (hrmOffset==0) { - g.clearRect(0,100,239,239); - g.moveTo(-100,0); + g.clearRect(0,100,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } for (var i=0;i<2;i++) { var a = hrmInfo.raw[hrmOffset]; hrmOffset++; y = E.clip(170 - (a*2),100,230); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; } } diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 45dcfa87e..93e2eaf66 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -2,3 +2,4 @@ 0.02: Tweaks for variable size widget system 0.03: Ensure redrawing works with variable size widget system 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2) diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 54b105d1e..f3b3ff45e 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,9 +1,28 @@ (() => { - var currentBPM = undefined; - var lastBPM = undefined; - var firstBPM = true; // first reading since sensor turned on + if (!Bangle.isLocked) return; // old firmware + var currentBPM; + var lastBPM; - function draw() { + // turn on sensor when the LCD is unlocked + Bangle.on('lock', function(isLocked) { + if (!isLocked) { + Bangle.setHRMPower(1,"widhrm"); + currentBPM = undefined; + WIDGETS["hrm"].draw(); + } else { + Bangle.setHRMPower(0,"widhrm"); + } + }); + + Bangle.on('HRM',function(d) { + currentBPM = d.bpm; + lastBPM = currentBPM; + WIDGETS["hrm"].draw(); + }); + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); + + // add your widget + WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); g.setFont("6x8", 1); @@ -16,36 +35,10 @@ } if (bpm===undefined) bpm = "--"; - g.setColor(isCurrent ? "#ffffff" : "#808080"); + g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); g.setColor(isCurrent ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); - } - - // redraw when the LCD turns on - Bangle.on('lcdPower', function(on) { - if (on) { - Bangle.setHRMPower(1,"widhrm"); - firstBPM = true; - currentBPM = undefined; - WIDGETS["hrm"].draw(); - } else { - Bangle.setHRMPower(0,"widhrm"); - } - }); - - Bangle.on('HRM',function(d) { - if (firstBPM) - firstBPM=false; // ignore the first one as it's usually rubbish - else { - currentBPM = d.bpm; - lastBPM = currentBPM; - } - WIDGETS["hrm"].draw(); - }); - Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm"); - - // add your widget - WIDGETS["hrm"]={area:"tl",width:24,draw:draw}; + }}; })(); diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog index fdb495797..39520ad6a 100644 --- a/apps/widhrt/ChangeLog +++ b/apps/widhrt/ChangeLog @@ -1,3 +1,5 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) - +0.03: Works with light theme + Doesn't drain battery by updating every 2 secs + fix alignment diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js index 8ac76def8..d9716fa24 100644 --- a/apps/widhrt/widget.js +++ b/apps/widhrt/widget.js @@ -1,28 +1,18 @@ (function(){ if (!Bangle.isHRMOn) return; // old firmware + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + WIDGETS.widhrt.draw(); + }; - function draw() { + WIDGETS.widhrt={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isHRMOn()) { - g.setColor(1,0,0); // on = red + g.setColor("#f00"); // on = red } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey } - g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.widhrt.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.widhrt={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y); + }}; })(); From f1b94d4c667dbfc820fc846cb060a1cf09f7a74d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:42:46 +0100 Subject: [PATCH 094/325] Bluetooth Heart Rate Monitor - Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. --- apps.json | 16 ++++++++++ apps/bthrm/ChangeLog | 1 + apps/bthrm/README.md | 45 ++++++++++++++++++++++++++ apps/bthrm/app-icon.js | 1 + apps/bthrm/app.png | Bin 0 -> 2756 bytes apps/bthrm/boot.js | 71 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+) create mode 100644 apps/bthrm/ChangeLog create mode 100644 apps/bthrm/README.md create mode 100644 apps/bthrm/app-icon.js create mode 100644 apps/bthrm/app.png create mode 100644 apps/bthrm/boot.js diff --git a/apps.json b/apps.json index 94d5e49e7..50e53f4f4 100644 --- a/apps.json +++ b/apps.json @@ -624,6 +624,7 @@ { "id": "heart", "name": "Heart Rate Recorder", + "shortName": "HRM Record", "version": "0.07", "description": "Application that allows you to record your heart rate. Can run in background", "icon": "app.png", @@ -841,6 +842,21 @@ {"name":"widhrm.wid.js","url":"widget.js"} ] }, + { "id": "bthrm", + "name": "Bluetooth Heart Rate Monitor", + "shortName":"BT HRM", + "version":"0.01", + "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", + "icon": "app.png", + "tags": "health,bluetooth", + "type": "boot", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthrm.boot.js","url":"boot.js"}, + {"name":"bthrm.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "stetho", "name": "Stethoscope", diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bthrm/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md new file mode 100644 index 000000000..f0c7775c2 --- /dev/null +++ b/apps/bthrm/README.md @@ -0,0 +1,45 @@ +# Bluetooth Heart Rate Monitor + +When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. + +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. + +This means it's compatible with many Bangle.js apps including: + +* [Heart Rate Widget](https://banglejs.com/apps/#widhrt) +* [Heart Rate Recorder](https://banglejs.com/apps/#heart) + +It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm) +as that requires live sensor data (rather than just BPM readings). + +## Usage + +Just install the app, then install an app that uses the heart rate monitor. + +Once installed it'll automatically try and connect to the first bluetooth +heart rate monitor it finds. + +**To disable this and return to normal HRM, uninstall the app** + +## Compatible Heart Rate Monitors + +This works with any heart rate monitor providing the standard Bluetooth +Heart Rate Service (`180D`) and characteristic (`2A37`). + +So far it has been tested on: + +* CooSpo Bluetooth Heart Rate Monitor + +## Internals + +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? + +## Creator + +Gordon Williams diff --git a/apps/bthrm/app-icon.js b/apps/bthrm/app-icon.js new file mode 100644 index 000000000..04a5ee610 --- /dev/null +++ b/apps/bthrm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA==")) diff --git a/apps/bthrm/app.png b/apps/bthrm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..40c2ab024622fd92cf3908991804eb1def70e7ac GIT binary patch literal 2756 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 KMNUMnLSTaZ|1XvR literal 0 HcmV?d00001 diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js new file mode 100644 index 000000000..06ead0965 --- /dev/null +++ b/apps/bthrm/boot.js @@ -0,0 +1,71 @@ +(function() { + var log = function() {};//print + var gatt; + + Bangle.setHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + log("setHRMPower ->", isOn, app); + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; + if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); + if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); + isOn = Bangle._PWR.HRM.length; + // so now we know if we're really on + if (isOn) { + log("setHRMPower on", app); + if (!gatt) { + log("HRM not already on"); + NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { + log("Found device "+device.id); + device.on('gattserverdisconnected', function(reason) { + gatt = undefined; + }); + return device.gatt.connect(); + }).then(function(g) { + log("Connected"); + gatt = g; + return gatt.getPrimaryService(0x180D); + }).then(function(service) { + return service.getCharacteristic(0x2A37); + }).then(function(characteristic) { + log("Got characteristic"); + characteristic.on('characteristicvaluechanged', function(event) { + var dv = event.target.value; + var flags = dv.getUint8(0); + // 0 = 8 or 16 bit + // 1,2 = sensor contact + // 3 = energy expended shown + // 4 = RR interval + var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit + /* var idx = 2 + (flags&1); // index of next field + if (flags&8) idx += 2; // energy expended + if (flags&16) { + var interval = dv.getUint16(idx,1); // in milliseconds + }*/ + Bangle.emit('HRM',{ + bpm:bpm, + confidence:100 + }); + }); + return characteristic.startNotifications(); + }).then(function() { + log("Ready"); + console.log("Done!"); + }).catch(function(err) { + console.log("Error",err); + gatt = undefined; + }); + } + } else { // not on + log("setHRMPower off", app); + if (gatt) { + log("HRM connected - disconnecting"); + try {gatt.disconnect();}catch(e) { + log("HRM disconnect error", e); + } + gatt = undefined; + } + } + }; +})(); From ce4830956847355f310bb24e5def43af6e1069a9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:50:19 +0100 Subject: [PATCH 095/325] Minor app tweaks --- apps/bthrm/boot.js | 3 +++ apps/heart/app.js | 2 +- apps/widhrm/widget.js | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 06ead0965..5e348672d 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -2,6 +2,9 @@ var log = function() {};//print var gatt; + Bangle.isHRMOn = function() { + return gatt!==undefined; + } Bangle.setHRMPower = function(isOn, app) { // Do app power handling if (!app) app="?"; diff --git a/apps/heart/app.js b/apps/heart/app.js index ed249c343..5428ea06b 100644 --- a/apps/heart/app.js +++ b/apps/heart/app.js @@ -303,7 +303,7 @@ function graphRecord(n) { log("Finished rendering data"); Bangle.buzz(200, 0.3); g.flip(); - setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false}); + setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false}); return; } diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index f3b3ff45e..7ffe1aa6d 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -2,6 +2,7 @@ if (!Bangle.isLocked) return; // old firmware var currentBPM; var lastBPM; + var isHRMOn = false; // turn on sensor when the LCD is unlocked Bangle.on('lock', function(isLocked) { @@ -14,19 +15,24 @@ } }); + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + isHRMOn = Bangle.isHRMOn(); + WIDGETS["hrm"].draw(); + }; + Bangle.on('HRM',function(d) { currentBPM = d.bpm; lastBPM = currentBPM; WIDGETS["hrm"].draw(); }); - Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); // add your widget WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); - g.setFont("6x8", 1); - g.setFontAlign(0, 0); + g.setFont("6x8", 1).setFontAlign(0, 0); g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background var bpm = currentBPM, isCurrent = true; if (bpm===undefined) { @@ -37,8 +43,10 @@ bpm = "--"; g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); - g.setColor(isCurrent ? "#ff0033" : "#808080"); + g.setColor(isHRMOn ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); }}; + + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); })(); From f6184f0fd1c8171b079cbee746f67baf6bec5173 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:57:14 +0100 Subject: [PATCH 096/325] minor tweaks --- apps/bthrm/boot.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 5e348672d..88e574480 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -1,9 +1,10 @@ (function() { var log = function() {};//print var gatt; + var status; Bangle.isHRMOn = function() { - return gatt!==undefined; + return (status=="searching" || status=="connecting") || (gatt!==undefined); } Bangle.setHRMPower = function(isOn, app) { // Do app power handling @@ -17,10 +18,12 @@ // so now we know if we're really on if (isOn) { log("setHRMPower on", app); - if (!gatt) { + if (!Bangle.isHRMOn()) { log("HRM not already on"); + status = "searching"; NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { log("Found device "+device.id); + status = "connecting"; device.on('gattserverdisconnected', function(reason) { gatt = undefined; }); @@ -54,16 +57,18 @@ return characteristic.startNotifications(); }).then(function() { log("Ready"); - console.log("Done!"); + status = "ok"; }).catch(function(err) { - console.log("Error",err); + log("Error",err); gatt = undefined; + status = "error"; }); } } else { // not on log("setHRMPower off", app); if (gatt) { log("HRM connected - disconnecting"); + status = undefined; try {gatt.disconnect();}catch(e) { log("HRM disconnect error", e); } From d9441b3a40e11f3a57d13fc40dd805d951424c43 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 14:49:51 +0100 Subject: [PATCH 097/325] Added vernier respiration app --- apps.json | 15 ++ apps/vernierrespirate/ChangeLog | 1 + apps/vernierrespirate/README.md | 26 +++ apps/vernierrespirate/app-icon.js | 1 + apps/vernierrespirate/app.js | 256 ++++++++++++++++++++++++++++++ apps/vernierrespirate/app.png | Bin 0 -> 1986 bytes 6 files changed, 299 insertions(+) create mode 100644 apps/vernierrespirate/ChangeLog create mode 100644 apps/vernierrespirate/README.md create mode 100644 apps/vernierrespirate/app-icon.js create mode 100644 apps/vernierrespirate/app.js create mode 100644 apps/vernierrespirate/app.png diff --git a/apps.json b/apps.json index 50e53f4f4..8715ede73 100644 --- a/apps.json +++ b/apps.json @@ -3977,5 +3977,20 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "vernierrespirate", + "name": "Vernier Go Direct Respiration Belt", + "shortName":"Respiration Belt", + "version":"0.01", + "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", + "icon": "app.png", + "tags": "health,bluetooth", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"vernierrespirate.app.js","url":"app.js"}, + {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"vernierrespirate.json"}] } ] diff --git a/apps/vernierrespirate/ChangeLog b/apps/vernierrespirate/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/vernierrespirate/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/vernierrespirate/README.md b/apps/vernierrespirate/README.md new file mode 100644 index 000000000..54dfc274b --- /dev/null +++ b/apps/vernierrespirate/README.md @@ -0,0 +1,26 @@ +# Vernier Go Direct Respiration Belt + +Connects to a [Go Direct Respiration Belt](https://www.vernier.com/product/go-direct-respiration-belt/) via Bluetooth and shows respiration rate + +![](data:image/png;base64,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) + +## Usage + +In the main menu: + +* `Connect` - connect and start displaying respiration +* `Vib` - Should we vibrate if the breaths per minute (BPM) is above a certain value? + * `No` - don't vibrate + * `Calculated` - vibrate if the app's reading is high. This is based on raw + sensor data and it responds quickly but may not be accurate. + * `Vernier` - vibrate if the Vernier sensor's own reading is high. This is + more accurate but responds very slowly. +* `Connect` - connect and start displaying respiration + +## TODO + +* Logging to a file? + +## Creator + +Gordon Williams diff --git a/apps/vernierrespirate/app-icon.js b/apps/vernierrespirate/app-icon.js new file mode 100644 index 000000000..f687d2a9d --- /dev/null +++ b/apps/vernierrespirate/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ")) diff --git a/apps/vernierrespirate/app.js b/apps/vernierrespirate/app.js new file mode 100644 index 000000000..945b72b77 --- /dev/null +++ b/apps/vernierrespirate/app.js @@ -0,0 +1,256 @@ + +// get settings +var settings = require("Storage").readJSON("vernierrespirate.json",1)||{}; +settings.vibrateBPM = settings.vibrateBPM||27; +// settings.vibrate; // undefined / "calculated" / "vernier" + +function saveSettings() { + require("Storage").writeJSON("vernierrespirate.json", settings); +} + + +g.clear(); +var graphHeight = g.getHeight()-100; +var last = { + time : Date.now(), + x : 0, + y : 24, +}; +var avrValue; +var aboveAvr = false; +var lastBreath; +var lastBreaths = []; +var vibrateInterval; + +function onMsg(txt) { + print(txt); + E.showMessage(txt); +} + +function setVibrate(isOn) { + var wasOn = vibrateInterval!==undefined; + if (isOn == wasOn) return; + + if (isOn) { + vibrateInterval = setInterval(function() { + Bangle.buzz(); + }, 1000); + } else { + clearInterval(vibrateInterval); + vibrateInterval = undefined; + } +} + +function onBreath() { + var t = Date.now(); + if (lastBreath!==undefined) { + // time between breaths + var value = 60000 / (t-lastBreath); + // average of last 3 + while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small + lastBreaths.push(value); + value = E.sum(lastBreaths) / lastBreaths.length; + // draw value + g.reset(); + g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "calculated") + setVibrate(value > settings.vibrateBPM); + } + lastBreath = t; +} + +function onData(n, value) { + g.reset(); + if (n==2) { + function scale(v) { + return Math.max(graphHeight - (1+v*4),24); + } + if (avrValue==undefined) avrValue=value; + avrValue = avrValue*0.95 + value*0.05; + if (avrValue < 1) avrValue = 1; + if (value > avrValue) { + if (!aboveAvr) onBreath(); + aboveAvr = true; + } else aboveAvr = false; + + var t = Date.now(); + var x = Math.round((t - last.time) / 100) // 10 per second + if (last.x>=g.getWidth()) { + x = 0; + last.x = 0; + last.time = t; + g.clearRect(0,24,g.getWidth(),graphHeight); + } + var y = scale(value); + g.setPixel(x, scale(avrValue), "#f00"); + g.drawLine(last.x, last.y, x, y); + last.x = x; + last.y = y; + } + if (n==4) { + g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight()); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "vernier") + setVibrate(value > settings.vibrateBPM); + } + Bangle.setLCDPower(1); // ensure LCD is on +} + +function connect() { + var gatt, service, rx, tx; + var rollingCounter = 0xFF; + + // any button to exit + Bangle.setUI("updown", function() { + setVibrate(false); + Bangle.buzz(); + try { + if (gatt) gatt.disconnect(); + } catch (e) { + } + setTimeout(mainMenu, 1000); + }); + + function sendCommand(subCommand) { + const command = new Uint8Array(4 + subCommand.length); + command.set(new Uint8Array(subCommand), 4); + // Populate the packet header bytes + command[0] = 0x58; // header + command[1] = command.length; + command[2] = --rollingCounter; + command[3] = E.sum(command) & 0xFF; // checksum + return tx.writeValue(command); + } + function firstSetBit(v) { + return v & -v; + } + function handleResponse(dv) { + //print(dv.buffer); + var resType = dv.getUint8(0); + if (resType==0x20) { + // [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127]) + // 6 = data type = real + // 6,0 = bit mask for sensors + // 2 = value count + if (dv.getUint8(4)!=6) return; //throw "Not float32 data"; + var sensorIds = dv.getUint16(5, true); + // var count = dv.getUint8(7); doesn't seem right + var offs = 9; + while (sensorIds) { + var value = dv.getFloat32(offs, true); + var s = firstSetBit(sensorIds); + if (isFinite(value)) onData(s,value); + //else print(s,value); + sensorIds &= ~s; + offs += 4; + } + } else { + var cmd = dv.getUint8(4); // cmd + //print("CMD",dv.buffer); + } + } + + onMsg("Searching..."); + NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) { + device.on("gattserverdisconnected", function() { + onMsg("Device disconnected"); + }); + onMsg("Found. Connecting..."); + return device.gatt.connect({minInterval:20, maxInterval:20}); + }).then(function(g) { + gatt = g; + return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112"); + }).then(function(s) { + service = s; + return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb"); + }).then(function(c) { + tx = c; + return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe"); + }).then(function(c) { + rx = c; + rx.on('characteristicvaluechanged', function(event) { + //print("EVT",event.target.value.buffer); + handleResponse(event.target.value); + }); + return rx.startNotifications(); + }).then(function() { + onMsg("Init"); + sendCommand([ // init + 0x1a, 0xa5, 0x4a, 0x06, + 0x49, 0x07, 0x48, 0x08, + 0x47, 0x09, 0x46, 0x0a, + 0x45, 0x0b, 0x44, 0x0c, + 0x43, 0x0d, 0x42, 0x0e, + 0x41, + ]); + /*setTimeout(function() { + print("Set measurement period"); + var us = 100000; // period in us + sendCommand([0x1b, 0xff, 0x00, + us & 255, + (us >> 8) & 255, + (us >> 16) & 255, + (us >> 24) & 255, + 0x00, + 0x00, + 0x00, + 0x00]); + }, 100);*/ + + /* setTimeout(function() { + print("Get sensor info"); + sendCommand([0x51, 0]); // get sensor IDs + // returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0] + // 54 is the bit mask of available channels + //sendCommand([106, 16]); // get sensor info + }, 2000);*/ + + setTimeout(function() { + onMsg("Start measurements"); + //https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588 + var channels = 6; // data channels 4 and 2 + sendCommand([ // start measurements + 0x18, 0xff, 0x01, channels, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]); + }, 500); + }).catch(function() { + onMsg("Connect Fail"); + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function mainMenu() { + var vibText = ["No","Calculated","Vernier"]; + var vibValue = ["","calculated","vernier"]; + E.showMenu({"":{title:"Respiration Belt"}, + "< Back" : () => { saveSettings(); load(); }, + "Connect" : () => { saveSettings(); E.showMenu(); connect(); }, + "Vib" : { + value : Math.max(vibValue.indexOf(settings.vibrate),0), + format : v => vibText[v], + min:0,max:2, + onchange : v => { settings.vibrate=vibValue[v]; } + }, + "BPM" : { + value : settings.vibrateBPM, + min:10,max:50, + onchange : v => { settings.vibrateBPM=v; } + } + }); +} + +mainMenu(); diff --git a/apps/vernierrespirate/app.png b/apps/vernierrespirate/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6b22af17e76005f58c052a2c72f7e43f90959f GIT binary patch literal 1986 zcmV;z2R-R7Gvns%k)0zo3?cw!x?&4iT7|;ukj9 zYnwP=hZ+o zbVaO?x(w1%1t$kWyY`h{Z#rv&4mJEtDRq#kpC3ip`T`24kSjr63x{^SnysU( z-Y4Ojo!>>&+cW8vT|XZ4H~Py{E+VnD>u4qUKiN725Dqm+Ci^5}qIQ<{G&aqoH)96+ ztLkeJb!;ZRM2sGJw>FVHfTp{fXX8VZ{Cc3KVeL$M(+1E}_3fb2^gjbRF@ws5pv{mt zr5(&5eMR1U)0moxLMu(WSpk7}I+xT3rrT=T01Q2UFR&tU6Jt#cxKrPN(W561fn@3? zwRgwMUz^g<6?|W=Nj@B5Wzg~MosQ#_0Sr~WP^pAh5@mX2F_@Vfm(q1cj-L80@Y5tY z+)W^~cl5}~*Qd&$3YzE@#mO=y`))4V9h!9jj%FMN@{*LROVEP;G+jMZLU4N>3fYyi zdy^=>30Mgcy&Qb@YiMN!>12W?T4+(KI7hdg1G5TXXvyv`0^8D*^Zcmt0<;3JBvg){ zz?Me{Jo6~3C@)!A)ZcQ0eJ|#{^weoVwgKZYKb6G{Tn&Z~ab2_pK~Nduj#%I~4{MtBM$QkO>CJR7^6DSWy9NH2h*&?z%+QcT za7V^bng*6l;GsQYetiwT<%<&zz-H|CXOZDiZ*BO{D;FZ8lej~-qim0s zbszunlmT=usSo71`VC-Vl9HjLFDpY8=cDBX@vW}*KDa{UUstd$TmzCP@pOnzV&kh{ zz2>!b5kvm53Ue7eGwU( z^fu5mR9*nFY^>(10O;w1Cu``{B>-5>R}srbxneC5joo$ z_miK4{Yf-aaw>cN?z;f?CtYayxiN9%Y%7uGHvCVo_qtD}iJ!tXp^Zvk+CGL4bHhr+ zahYg3+oVYl`mqZ0t_r6s;UWXpeO!N@y6yNr%wU+F~^-3vkltEGd9rZb4+ zVs2Q8GBQ2iPEPgAr1TGVyo zv->5U37;<8T@bO{CzX;qg{%`)72f}v0zf~a&WX Date: Thu, 21 Oct 2021 15:44:26 +0100 Subject: [PATCH 098/325] Calibrate at start if no info --- apps.json | 2 +- apps/arrow/ChangeLog | 4 +++- apps/arrow/app.js | 55 ++++++++++++++++++++++---------------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/apps.json b/apps.json index 8715ede73..c03e325ac 100644 --- a/apps.json +++ b/apps.json @@ -3407,7 +3407,7 @@ { "id": "arrow", "name": "Arrow Compass", - "version": "0.04", + "version": "0.05", "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", "icon": "arrow.png", "type": "app", diff --git a/apps/arrow/ChangeLog b/apps/arrow/ChangeLog index 2f1b2b4c4..edd5ccb3d 100644 --- a/apps/arrow/ChangeLog +++ b/apps/arrow/ChangeLog @@ -1,4 +1,6 @@ 0.01: First version 0.02: Moved arrow image load to global scope 0.03: faster drawCompass() function, does not cause buttons to become unresponsive -0.04: removed LCD1.write() as it was keeping LCD on +0.04: removed LED1.write() as it was keeping LCD on +0.05: Turn compass off when screen off + Calibrate at start if no info diff --git a/apps/arrow/app.js b/apps/arrow/app.js index ffa821a09..f1f85e880 100644 --- a/apps/arrow/app.js +++ b/apps/arrow/app.js @@ -1,5 +1,5 @@ -var pal1color = new Uint16Array([0x0000,0xFFC0],0,1); -var pal2color = new Uint16Array([0x0000,0xffff],0,1); +var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1); +var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1); var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true}); var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true}); var intervalRef; @@ -7,6 +7,7 @@ var bearing=0; // always point north var heading = 0; var oldHeading = 0; var candraw = false; +var isCalibrating = false; var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; function flip1(x,y) { @@ -29,7 +30,7 @@ function drawCompass(hd) { if (Math.abs(hd - oldHeading) < 2) return 0; hd=hd*Math.PI/180; var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760]; - + // using polar cordinates, 64,64 is the offset from the 0,0 origin var poly = [ 64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]), @@ -40,16 +41,16 @@ function drawCompass(hd) { 64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]), 64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6]) ]; - + buf1.fillPoly(poly); flip1(56, 56); } // stops violent compass swings and wobbles, takes 3ms -function newHeading(m,h){ +function newHeading(m,h){ var s = Math.abs(m - h); var delta = (m>h)?1:-1; - if (s>=180){s=360-s; delta = -delta;} + if (s>=180){s=360-s; delta = -delta;} if (s<2) return h; var hd = h + delta*(1 + Math.round(s/5)); if (hd<0) hd+=360; @@ -76,7 +77,7 @@ function tiltfixread(O,S){ return psi; } -function reading() { +function reading(m) { var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); heading = newHeading(d,heading); var dir = bearing - heading; @@ -97,18 +98,19 @@ function reading() { function calibrate(){ var max={x:-32000, y:-32000, z:-32000}, min={x:32000, y:32000, z:32000}; - var ref = setInterval(()=>{ - var m = Bangle.getCompass(); + function onMag(m) { max.x = m.x>max.x?m.x:max.x; max.y = m.y>max.y?m.y:max.y; max.z = m.z>max.z?m.z:max.z; min.x = m.x { setTimeout(()=>{ - if(ref) clearInterval(ref); + Bangle.removeListener('mag', onMag); var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2}; var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2}; var avg = (delta.x+delta.y+delta.z)/3; @@ -132,6 +134,7 @@ function docalibrate(e,first){ flip1(56,56); calibrate().then((r)=>{ + isCalibrating = false; require("Storage").write("magnav.json",r); Bangle.buzz(); CALIBDATA = r; @@ -146,39 +149,34 @@ function docalibrate(e,first){ if (first === undefined) first = false; - stopdraw(false); + stopdraw(); clearWatch(); + isCalibrating = true; - if (first) + if (first) E.showAlert(msg,title).then(action.bind(null,true)); - else + else E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action); } function startdraw(){ - if (!Bangle.isCompassOn()) { - Bangle.setCompassPower(1); - } + Bangle.setCompassPower(1, "app"); g.clear(); g.setColor(1,1,1); Bangle.drawWidgets(); candraw = true; if (intervalRef) clearInterval(intervalRef); - intervalRef = setInterval(reading,500); + intervalRef = setInterval(reading,200); } -function stopdraw(powerOffCompass) { - if (powerOffCompass === undefined) { - powerOffCompass = true; - } +function stopdraw() { candraw=false; - if (powerOffCompass) { - Bangle.setCompassPower(0); - } + Bangle.setCompassPower(0, "app"); if (intervalRef) { clearInterval(intervalRef); + intervalRef = undefined; } } @@ -189,6 +187,7 @@ function setButtons(){ } Bangle.on('lcdPower',function(on) { + if (isCalibrating) return; if (on) { startdraw(); } else { @@ -196,8 +195,8 @@ Bangle.on('lcdPower',function(on) { } }); -Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); - Bangle.loadWidgets(); -startdraw(); setButtons(); + +Bangle.setLCDPower(1); +if (CALIBDATA) startdraw(); else docalibrate({},true); From 7ae044e14b3049f3e131b5e93ffeb5faada65980 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 17:18:55 +0100 Subject: [PATCH 099/325] updated image --- apps/gpstime/gpstime-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpstime/gpstime-icon.js b/apps/gpstime/gpstime-icon.js index 665c8d5f6..99998c6c4 100644 --- a/apps/gpstime/gpstime-icon.js +++ b/apps/gpstime/gpstime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=")) +require("heatshrink").decompress(atob("mEw4UA////G161hyd8Jf4ALlQLK1WABREC1WgBZEK32oFxPW1QuJ7QwIFwOqvQLHhW31NaBY8qy2rtUFoAuG3W61EVqALF1+qr2gqtUHQu11dawNVqo6F22q9XFBYIwEhWqz2r6oLBGAheBqwuBBYx2CFwQLGlWqgoLCMAsKLoILChR6EgQuDqkqYYsBFweqYYoLDoWnYYoLD/WVYYv8FwXqPoIwEn52BqGrPoILEh/1FwOl9SsBBYcD/pdB2uq/QvEh/8LoOu1xHFh8/gGp9WWL4oMBgWltXeO4owBgWt1ReFYYh2GYYmXEQzDD3wiHegYKIGAJRGAAguJAH4AC")) From d64f120cbd7ce98c6b20a76f8eac185c2ebe3dc1 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:16:59 +1300 Subject: [PATCH 100/325] Update app.js --- apps/speedalt2/app.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 886c6ab70..a4f0216b5 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.01'; +var v = '1.01b'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -440,7 +440,6 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, - // fix:lf.fix, max:true, wp:false, sat:true @@ -451,7 +450,6 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:false, sat:true @@ -466,7 +464,6 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, - // fix:lf.fix, max:true, wp:false, sat:true @@ -477,7 +474,6 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:false, sat:true @@ -491,7 +487,6 @@ function onGPS(fix) { unit:cfg.dist_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:true, sat:true @@ -562,7 +557,7 @@ function setButtons(){ } else { Bangle.setLCDTimeout(0); - Bangle.setLCDPower(1); +// Bangle.setLCDPower(1); LED1.set(); } }, BTN2, {repeat:true,edge:"falling"}); From 5f91cf37cd7e8276fdce26ae450b4d29a2f7dbfd Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:22:46 +0100 Subject: [PATCH 101/325] Widbatpc: - revert hide change --- apps/widbatpc/ChangeLog | 1 - apps/widbatpc/settings.js | 11 ++--------- apps/widbatpc/widget.js | 3 +-- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index b8e594fc4..09e4fabf4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -10,4 +10,3 @@ 0.11: Don't overwrite existing settings on app update 0.12: Fixed for Bangle 2 0.13: Fillbar setting added, see README -0.14: Added setting to completely hide the widget diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index 55588238d..7dceaa2a8 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -12,8 +12,7 @@ 'percentage': true, 'fillbar': false, 'charger': true, - 'hideifmorethan': 100, - 'hidewidget': false, + 'hideifmorethan': 100 } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -33,7 +32,6 @@ } const onOffFormat = b => (b ? 'On' : 'Off') - const yesNoFormat = b => (b ? 'Yes' : 'No') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -69,12 +67,7 @@ step: 10, format: x => x+"%", onchange: save('hideifmorethan'), - }, - 'Hide Widget': { - value: s.hidewidget, - format: yesNoFormat, - onchange: save('hidewidget'), - }, + } } E.showMenu(menu) }) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index 574c22f6c..f6477c9bb 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -28,7 +28,7 @@ 'color': 'By Level', 'percentage': true, 'charger': true, - 'hideifmorethan': 100, + 'hideifmorethan': 100 }; Object.keys(DEFAULTS).forEach(k=>{ if (settings[k]===undefined) settings[k]=DEFAULTS[k] @@ -76,7 +76,6 @@ function draw() { // if hidden, don't draw if (!WIDGETS["batpc"].width) return; - if (setting('hidewidget')) return; // else... var s = 39; var x = this.x, y = this.y; From d1d1cf10ae38c7290ebe77adf73fb0341d09a566 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:23:42 +0100 Subject: [PATCH 102/325] Widbatpc: - revert hide change --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index e3e1de7b8..03421020d 100644 --- a/apps.json +++ b/apps.json @@ -743,7 +743,7 @@ "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "version": "0.14", + "version": "0.13", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "icon": "widget.png", "type": "widget", From 633552e0a3ad455b01e784fb4de8c20b6413a05f Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:30:07 +1300 Subject: [PATCH 103/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index a4f0216b5..b21e8120e 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.01b'; +var v = '1.02'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From cc4f93d246a0a67e2d3f5f28b2ab94e9c91f5b4b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:30:43 +1300 Subject: [PATCH 104/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 153f026da..07bf11c6d 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.01", + "version":"1.02", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 8667cf1b04ec470b34ed6ffbe69a5f8c7a1a4781 Mon Sep 17 00:00:00 2001 From: peeweek <4037271+peeweek@users.noreply.github.com> Date: Thu, 21 Oct 2021 21:34:04 +0200 Subject: [PATCH 105/325] Simplified (Removed settings) --- apps.json | 1 - apps/hcclock/ChangeLog | 2 +- apps/hcclock/hcclock.settings.js | 33 -------------------------------- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 apps/hcclock/hcclock.settings.js diff --git a/apps.json b/apps.json index a9a6d736d..ee81805a8 100644 --- a/apps.json +++ b/apps.json @@ -3397,7 +3397,6 @@ "allow_emulator":true, "storage": [ {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.settings.js","url":"hcclock.settings.js"}, {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} ] }, diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 343be7f07..aaa55d01a 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,2 +1,2 @@ 0.01: base code -0.02: added settings for color schemes \ No newline at end of file +0.02: saved settings when switching color scheme \ No newline at end of file diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js deleted file mode 100644 index 92d5f47e5..000000000 --- a/apps/hcclock/hcclock.settings.js +++ /dev/null @@ -1,33 +0,0 @@ -(function(back) { - - function getColorScheme() - { - let settings = require('Storage').readJSON("hcclock.json", true) || {}; - if (!("scheme" in settings)) { - settings.scheme = 0; - } - return settings.scheme; - } - function setColorScheme(value) - { - value = value + 1 % 2; - let settings = require('Storage').readJSON("hcclock.json", true) || {}; - settings.scheme = value? 1 : 0; - require('Storage').writeJSON('hcclock.json', settings); - } - function setIcon(visible) { - updateSetting('showIcon', visible); - - } - var mainmenu = { - "" : { "title" : "Hi-Contrast Clock" }, - "Color Scheme" : { - value: getColorScheme, - format: v => v == 0?"White":"Black", - onchange: setColorScheme - }, - "< Back" : back, - }; - E.showMenu(mainmenu); - }) - \ No newline at end of file From e0b7a8efef542f1e200d00204aaa64b627b947ac Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:41:04 +0100 Subject: [PATCH 107/325] Widbatpc: - revert hide change --- apps/widbatpc/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index 7dceaa2a8..b7a5db9e6 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -12,7 +12,7 @@ 'percentage': true, 'fillbar': false, 'charger': true, - 'hideifmorethan': 100 + 'hideifmorethan': 100, } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -31,7 +31,7 @@ } } - const onOffFormat = b => (b ? 'On' : 'Off') + const onOffFormat = b => (b ? 'on' : 'off') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -67,7 +67,7 @@ step: 10, format: x => x+"%", onchange: save('hideifmorethan'), - } + }, } E.showMenu(menu) }) From f6a6f03f70c55d9dfa58c68b4799a904b19aa4e3 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:42:39 +0100 Subject: [PATCH 108/325] Widbatpc: - revert hide change --- apps/widbatpc/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index f6477c9bb..caecf8ae4 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -28,7 +28,7 @@ 'color': 'By Level', 'percentage': true, 'charger': true, - 'hideifmorethan': 100 + 'hideifmorethan': 100, }; Object.keys(DEFAULTS).forEach(k=>{ if (settings[k]===undefined) settings[k]=DEFAULTS[k] From 0e52707ac5de98681910a29e2699e8107b8db8fd Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:48:36 +1300 Subject: [PATCH 109/325] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index b21e8120e..4fbe458db 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -588,9 +588,9 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode('SuperE'); // off g.clear(); Bangle.drawWidgets(); + setLpMode('SuperE'); // off onGPS(lf); // draw app screen } From 78cc981126b7190d26e42bef7f48e65721034999 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:50:30 +1300 Subject: [PATCH 110/325] Create app.js --- apps/speedclock/app.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/app.js diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/speedclock/app.js @@ -0,0 +1 @@ + From abab41825589f6cb368f6f2020b49925da7883ad Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:52:17 +1300 Subject: [PATCH 111/325] Update app.js --- apps/speedclock/app.js | 317 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index 8b1378917..4c74ce1be 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1 +1,318 @@ +// Morphing Clock + +// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch +// Icon by https://icons8.com +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; +var locale = require("locale"); +var CHARW = 28; // how tall are digits? +var CHARP = 2; // how chunky are digits? +var Y = 50; // start height +// Offscreen buffer +var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); +var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; +// The last time that we displayed +var lastTime = "-----"; +// If animating, this is the interval's id +var animInterval; +var timeInterval; +// Variables for the stopwatch +var counter = -1; // Counts seconds +var oldDate = new Date(2020,0,1); // Initialize to a past date +var swInterval; // The interval's id +var B3 = 0; // Flag to track BTN3's current function +var w1; // watch id for BTN1 +var w3; // watch id for BTN3 +/* Get array of lines from digit d to d+1. + n is the amount (0..1) + maxFive is true is this digit only counts 0..5 */ +const DIGITS = { + " ":n=>[], + "0":n=>[ + [n,0,1,0], + [1,0,1,1], + [1,1,1,2], + [n,2,1,2], + [n,1,n,2], + [n,0,n,1]], + "1":n=>[ + [1-n,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1-n,1,1-n,2], + [1-n,2,1,2]], + "2":n=>[ + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [0,1+n,0,2], + [1,2-n,1,2], + [0,2,1,2]], + "3":n=>[ + [0,0,1-n,0], + [0,0,0,n], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [n,2,1,2]], + "4":n=>[ + [0,0,0,1], + [1,0,1-n,0], + [1,0,1,1-n], + [0,1,1,1], + [1,1,1,2], + [1-n,2,1,2]], + "5to0": n=>[ // 5 -> 0 + [0,0,0,1], + [0,0,1,0], + [n,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2,0,2], + [1,1-n,1,1], + [0,1,0,1+n]], + "5to6": n=>[ // 5 -> 6 + [0,0,0,1], + [0,0,1,0], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2-n,0,2]], + "6":n=>[ + [0,0,0,1-n], + [0,0,1,0], + [n,1,1,1], + [1,1-n,1,1], + [1,1,1,2], + [n,2,1,2], + [0,1-n,0,2-2*n]], + "7":n=>[ + [0,0,0,n], + [0,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1,1,1,2], + [1-n,2,1,2], + [1-n,1,1-n,2]], + "8":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,1,0,2-n]], + "9":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1-n,1], + [0,1,0,1+n], + [1,1,1,2], + [0,2,1,2]], + ":":n=>[ + [0.4,0.4,0.6,0.4], + [0.6,0.4,0.6,0.6], + [0.6,0.6,0.4,0.6], + [0.4,0.4,0.4,0.6], + [0.4,1.4,0.6,1.4], + [0.6,1.4,0.6,1.6], + [0.6,1.6,0.4,1.6], + [0.4,1.4,0.4,1.6]] +}; + +/* Draw a transition between lastText and thisText. + 'n' is the amount - 0..1 */ +function drawDigits(lastText,thisText,n) { + "ram" + const p = CHARP; // padding around digits + const s = CHARW; // character size + var x = 16; // x offset + g.reset(); + for (var i=0;i{ + if (c[0]!=c[2]) // horiz + buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s); + else if (c[1]!=c[3]) // vert + buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s); + }); + g.drawImage(bufimg,x,Y); + } + if (thisCh==":") x-=4; + x+=s+p+7; + } +} +function drawDate() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // meridian + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true); + // date + g.setFont("Vector16"); + g.setFontAlign(0,-1); + // Only draw the date if it has changed: + if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) { + var date = locale.date(d,false); + g.clearRect(1,y+8,g.getWidth(),y+24); + g.drawString(date, g.getWidth()/2, y+8, true); + oldDate = d; + } +} + +function drawSeconds() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // seconds + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true); +} + +/* Show the current time, and animate if needed */ +function showTime() { + if (animInterval) return; // in animation - quit + var d = new Date(); + var hours = d.getHours(); + if (is12Hour) hours = ((hours + 11) % 12) + 1; + var t = (" "+hours).substr(-2)+":"+ + ("0"+d.getMinutes()).substr(-2); + var l = lastTime; + // same - don't animate + if (t==l || l=="-----") { + drawDigits(l,t,0); + drawDate(); + drawSeconds(); + lastTime = t; + return; + } + var n = 0; + animInterval = setInterval(function() { + n += 1/10; + if (n>=1) { + n=1; + clearInterval(animInterval); + animInterval = undefined; + } + drawDigits(l,t,n); + drawSeconds(); + }, 20); + lastTime = t; +} + +function stopWatch() { + + counter++; + + var hrs = Math.floor(counter/3600); + var mins = Math.floor((counter-hrs*3600)/60); + var secs = counter - mins*60 - hrs*3600; + + // When starting the stopwatch: + if (B3) { + // Set BTN3 to stop the stopwatch and bind itself to restart it: + w3=setWatch(() => {clearInterval(swInterval); + swInterval=undefined; + if (w3) {clearWatch(w3);w3=undefined;} + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 1;}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 0; // BTN3 is bound to stop the stopwatch + } + + // Bind BTN1 to call the reset function: + if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); + + // Draw elapsed time: + g.reset(); + g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24"); + g.clearRect(1,180,g.getWidth(),210); + if (hrs>0) { + g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true); + g.drawString( ":", g.getWidth()/2 - 48, 180, true); + } + g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true); + g.drawString( ":", g.getWidth()/2, 180, true); + g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true); + +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; + } + + // Clear the stopwatch: + g.clearRect(1,180,g.getWidth(),210); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch + } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + + +Bangle.on('lcdPower',function(on) { + if (animInterval) { + clearInterval(animInterval); + animInterval = undefined; + } + if (timeInterval) { + clearInterval(timeInterval); + timeInterval = undefined; + } + if (on) { + showTime(); + timeInterval = setInterval(showTime, 1000); + } else { + lastTime = "-----"; + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Update time once a second +timeInterval = setInterval(showTime, 1000); +showTime(); + +// Show launcher when button pressed +Bangle.setUI("clock"); + +// Start stopwatch when BTN3 is pressed +setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +B3 = 1; // BTN3 is bound to start the stopwatch From 551f6e38a7e6779a0c2ad7fa2ddac76e509c1993 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:53:37 +1300 Subject: [PATCH 112/325] Create ChangeLog --- apps/speedclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/ChangeLog diff --git a/apps/speedclock/ChangeLog b/apps/speedclock/ChangeLog new file mode 100644 index 000000000..c31405e08 --- /dev/null +++ b/apps/speedclock/ChangeLog @@ -0,0 +1 @@ +0.01: Created app From 4e28f9f3e7f59d7ebc21fc64b97c6f7691d99cfe Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:54:42 +1300 Subject: [PATCH 113/325] Create README.md --- apps/speedclock/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 apps/speedclock/README.md diff --git a/apps/speedclock/README.md b/apps/speedclock/README.md new file mode 100644 index 000000000..b4a3c83a7 --- /dev/null +++ b/apps/speedclock/README.md @@ -0,0 +1,21 @@ +# Morphing Clock Plus + +Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch. + +![](Screenshot.JPG) + +## Usage + +In addition to the Morphing Clock, a simple stopwatch can be started in the lower part of the display. + +BTN3 starts and stops the stopwatch. + +BTN1 resets/clears the stopwatch. + +## Requests + +Please leave bug reports and requests by raising an issue [here](https://github.com/skauertz/BangleApps). + +## Creator + +Sebastian Kauertz https://github.com/skauertz From 462537385a5afc0ec1851a02077b3ee5704976e0 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:55:51 +1300 Subject: [PATCH 114/325] Create icon.js --- apps/speedclock/icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/icon.js diff --git a/apps/speedclock/icon.js b/apps/speedclock/icon.js new file mode 100644 index 000000000..41a59f503 --- /dev/null +++ b/apps/speedclock/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) From 04359031900f3bbc3b0b7fae993714f24ac7b0ba Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:57:17 +1300 Subject: [PATCH 115/325] Add files via upload --- apps/speedclock/app.png | Bin 0 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedclock/app.png diff --git a/apps/speedclock/app.png b/apps/speedclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Fri, 22 Oct 2021 10:00:04 +1300 Subject: [PATCH 116/325] Update and rename icon.js to app-icon.js --- apps/speedclock/app-icon.js | 1 + apps/speedclock/icon.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 apps/speedclock/app-icon.js delete mode 100644 apps/speedclock/icon.js diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) diff --git a/apps/speedclock/icon.js b/apps/speedclock/icon.js deleted file mode 100644 index 41a59f503..000000000 --- a/apps/speedclock/icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) From e7f4edbd3288408f7dc4d31149aaa1307598c690 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 22:33:13 +0100 Subject: [PATCH 117/325] Added basic README and screenshots for a number of Apps --- apps/aclock/README.md | 4 ++++ apps/aclock/screenshot_analog.png | Bin 0 -> 3069 bytes apps/boldclk/README.md | 4 ++++ apps/boldclk/screenshot_bold.png | Bin 0 -> 3563 bytes apps/clock2x3/README.md | 4 ++++ apps/clock2x3/screenshot_pixel.png | Bin 0 -> 1926 bytes apps/ffcniftya/README.md | 4 ++++ apps/ffcniftya/screenshot_nifty.png | Bin 0 -> 3487 bytes apps/flappy/README.md | 5 +++++ apps/flappy/screenshot1_flappy.png | Bin 0 -> 2509 bytes apps/flappy/screenshot2_flappy.png | Bin 0 -> 2805 bytes apps/floralclk/README.md | 4 ++++ apps/floralclk/screenshot_floral.png | Bin 0 -> 6073 bytes apps/s7clk/README.md | 4 ++++ apps/s7clk/screenshot_s7segment.png | Bin 0 -> 2144 bytes apps/score/README.md | 3 +++ apps/score/screenshot_score.png | Bin 0 -> 1796 bytes apps/trex/README.md | 4 ++++ apps/trex/screenshot_trex.png | Bin 0 -> 668 bytes apps/waveclk/README.md | 4 ++++ 20 files changed, 40 insertions(+) create mode 100644 apps/aclock/README.md create mode 100644 apps/aclock/screenshot_analog.png create mode 100644 apps/boldclk/README.md create mode 100644 apps/boldclk/screenshot_bold.png create mode 100644 apps/clock2x3/README.md create mode 100644 apps/clock2x3/screenshot_pixel.png create mode 100644 apps/ffcniftya/README.md create mode 100644 apps/ffcniftya/screenshot_nifty.png create mode 100644 apps/flappy/README.md create mode 100644 apps/flappy/screenshot1_flappy.png create mode 100644 apps/flappy/screenshot2_flappy.png create mode 100644 apps/floralclk/README.md create mode 100644 apps/floralclk/screenshot_floral.png create mode 100644 apps/s7clk/README.md create mode 100644 apps/s7clk/screenshot_s7segment.png create mode 100644 apps/score/screenshot_score.png create mode 100644 apps/trex/README.md create mode 100644 apps/trex/screenshot_trex.png create mode 100644 apps/waveclk/README.md diff --git a/apps/aclock/README.md b/apps/aclock/README.md new file mode 100644 index 000000000..7d31cdae2 --- /dev/null +++ b/apps/aclock/README.md @@ -0,0 +1,4 @@ +# Analogue Clock + +![](screenshot_analog.png) + diff --git a/apps/aclock/screenshot_analog.png b/apps/aclock/screenshot_analog.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e84b4282a99cd420fd84f1bd07c1ce2bb43a04 GIT binary patch literal 3069 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 LNkvXXu0mjfYyh|2 literal 0 HcmV?d00001 diff --git a/apps/boldclk/README.md b/apps/boldclk/README.md new file mode 100644 index 000000000..0e7865b99 --- /dev/null +++ b/apps/boldclk/README.md @@ -0,0 +1,4 @@ +# Bold Clock + +![](screenshot_bold.png) + diff --git a/apps/boldclk/screenshot_bold.png b/apps/boldclk/screenshot_bold.png new file mode 100644 index 0000000000000000000000000000000000000000..4024fca40e6014286442a37fb3d5518ad9747380 GIT binary patch literal 3563 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�Qbsi zcX6$Zh;(4hP5bL`E8nXFOsNT9yVlC6f+|Jb|&sSw6yY0XX7liF55UX3pYMbATmZGd%-={jJOZu{pq$OWppie69u2 z0m=p73*uR>Ta}6Yo8aZ6kq^B-U~g4&19;?~ESG`&_{6R-3Q%NqD8SLj4-$v_9KDs= zwc3(7$blE)-zSD`&3r3*i6i zagpoKD`1brEjVf0$mO$`B|j+ z(GeJJqS<8`U8KD>AO+g=mpJN=qfkWmNFA;@G5}14m>Uc{P7f};Z(B3vz%v6-iorcP zC1RmISpc3ve1VAcM7Cymz6HP(u?ygzNKy2l=$Zx8(K^xtOc9ylEDuLyioldqf}*X5 zZYy&Xz$@xCeRl(Rw~|@vG8*D&R6e6bUsQTSb5oyZ0JtrSarDVrX_(%PcDclLcOT(} zh37FD3Gk?@KmnM#K{K{XpF(ge6K&ayQZCYROh$kyxv-Y~5#wwPFv{Uj^wmx+yZ~;~ zgq{x68hO_GE_E|(5Ae)+DEgx3R1vRsmH?x*Qa9_}rrqIv`+nyf7y&kOI&%1ZV^AHf z1u!MT_AXoqa-`Re&yNKd0g9Y0=}!TebJ;>^0D8V-Re`&Sjs>_)Es(P{p05Bb$zv(s zj8IPxE;w1*9}VzGWE5ScFnI&~gb0f^N&!6hPFIpYO8~w@h|(I$#zGE1?Rf#L0PyS! zuimYnng>^kfVbNt0Y`%T`f5HefIDiUqudJMN)h;I7r=bYhCL`b lu;O$z)afrW%ERX?@E3q@&z3pg2j~C*002ovPDHLkV1kctqHX{H literal 0 HcmV?d00001 diff --git a/apps/clock2x3/README.md b/apps/clock2x3/README.md new file mode 100644 index 000000000..0b5d25f9d --- /dev/null +++ b/apps/clock2x3/README.md @@ -0,0 +1,4 @@ +# 2x3 Pixel Clock + +![](screenshot_pixel.png) + diff --git a/apps/clock2x3/screenshot_pixel.png b/apps/clock2x3/screenshot_pixel.png new file mode 100644 index 0000000000000000000000000000000000000000..4b09f06a1386602290e86ac4d2a42c2dff285b02 GIT binary patch literal 1926 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 L+?}f(34i+&w))`% literal 0 HcmV?d00001 diff --git a/apps/score/README.md b/apps/score/README.md index 1de1ccdb5..6a5bf8172 100644 --- a/apps/score/README.md +++ b/apps/score/README.md @@ -4,6 +4,9 @@ This app will allow you to keep scores for most kinds of sports. To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`). In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again. +## Screenshot +![](screenshot_score.png) + ## Bangle.js 1 | Keybinding | Description | |---------------------|------------------------------| diff --git a/apps/score/screenshot_score.png b/apps/score/screenshot_score.png new file mode 100644 index 0000000000000000000000000000000000000000..662c59e9ea49f1d1bcfc4ac093a998c9c3d83405 GIT binary patch literal 1796 zcmeHIc~g^j5dP&R5sN5A2UQwY`5=D^^H4qCWTndN~OE?T=})^_(LgRyBlv zK4sYJh~{Q<$&HRx>l(-0OWNn1!bGP-#R+dO;C9qO8zDDamsfc>prcm12gv&0nWTW@ zMhPfT+Lp0HgQi}^0Sw=kyipjN+5b79PcG)$F)S{%!2v~Te;I>E`vd@}_O;6g(B(LZ z1B3*nt0AQ08VUn7v0wBcbP)d(-L;?IF4fMwIQP;DAP7OIp%*B6!=eQfR?42{9E&j? z!gV4kUB%d5B*pm(HhloUlWr;Uq^YoB9itn7Kue}P8Z~-*?E2bSYZIcgXk|=0oLm+x z_H4n#@@3wE&UdyQ+(z6A&18Ar)19^1&~kcKEyiSkeT`kJ6xy?I`c9SX265&M5gv48 zrk-uMP>sCsM#6CEua%bo>XliI?j|e893>kuJGK8=h7n8$s!{ko5vFRrju%Elbp@5C z0y!#fmt=mJ-)y>99H{pM83A{=!{+Ud)@&}t&{T8LF&BA#X`CQ_PEm$jZ0B}!;vqXD z(94i7DweX!2c2VvK`)D7|7f9PpUV`_o7pGs$g(+~IUdvVl)w0L3$I$O?(X|TF?@lm z-BGnUr;w^&_)k%@w{HM6-rE?yr7I)3YLJAS%=p7M(wnBph7a`DDwPvdpMr2mBj?;=cR%^$C{@`8uG}EBH^LW!F z2k_eb40l;FC8T6mtww-n96Dfl44~^-WQz>|5m+^%iKcHA>w)$S0+FwTHUZNS?!#0k9WxW^g%n_MzGwDO+d_?!@*NIll^;GIi z3R-qD$P#7`zm{EMfRTBja)&xSj4C{OW6PIPt-S)oqPWAuz+(4+hEaG&I zP9g;xH9=s&?9?NLou=2SN36LIi1S|_p4^%yOX153zsD^TA<{+rAAZCoHN7k{2#Du5 z#`}bE$AY)ZtI~0rtxyR@-e}Lvi)m|HoaRyaN{0~3RWow!c=bs7*%7CZ8NJ<2)W3V^ z!^O(c&Ci~n0SI18&$HC^b^uLR3z;eKaP;Vgsmq3TI!zTauH+(gAY*zZC6grOhNYS{ zJFG>O$x|pDTLNyK96NPnsd$N4|Lubca3Xf8FxI)cq84`bXU%SI7z~7UbYB&aKv)}J e+(G!4$IH6DqRVPDJzuVRYryRgh0Gz*vi}6(DCm;_ literal 0 HcmV?d00001 diff --git a/apps/trex/README.md b/apps/trex/README.md new file mode 100644 index 000000000..03e3f5883 --- /dev/null +++ b/apps/trex/README.md @@ -0,0 +1,4 @@ +# T-Rex + +![](screenshot_trex.png) + diff --git a/apps/trex/screenshot_trex.png b/apps/trex/screenshot_trex.png new file mode 100644 index 0000000000000000000000000000000000000000..a66cc013fa349cf661d12c4e511d0fd7529f0bda GIT binary patch literal 668 zcmV;N0%QG&P)Px%R!KxbRCr$P+~JagAPfXxegB8HV^4Z%iny#qN7(+kn>Nt$86#-zye!Ky=>ZGO z^E@T8_5bx@3GM6aYqNl1+bIY7M%>W-UjgH`pU9sKH5qNOQ-hEUPFs5K^FO&NdEm=4 z_y{+lKM~oEZT{?xNKcHRfNKUiYUWm->_M_M2QT0Xfm-B>u4{IRN5#S=;*;{*S$t%a z0w%9$Aek)OzZ9&_U?h`;`@etFz-dx#r*3<5Wu%<+8q);WUsbhh@m}H3ah(QMIVe+~ z23EEP0;9Xh$Z@AKcw{(q>;gum$T$I$aA-3Dqf%s?fJr#CnSfC#GETrG9NJ94s1z9| zU=j{(CSX*Gj1w>khc**1Dn-T#n1n-{2^f_k;{;5?q0I!0N|ABH0$+83R=~k`5)TJ# zzwW>8_0bn?hXT&s!)I^oj=&c1RwAOl!z^F{lk=lYGnI&Tmf4<4cR{H{G{X1DI>(gc-s#zT%ii6e&P4=o0Ram*SRlB2Ww3yQ1%kW3 zEQ8lmqQPSsx_fvY?0=Ugw=!xqr)+Uv;HnJuix+S31DqS2f9~klPmb32HT|*a?-Qal zuD$V~<1Br~H?ZvpeKR)8(EeFiWY6-aElYdeS>zWB@m^wF=+_(o0000 Date: Fri, 22 Oct 2021 10:59:37 +1300 Subject: [PATCH 118/325] Update app.js --- apps/speedalt2/app.js | 123 ++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 4fbe458db..8203988f4 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.02'; +var v = '1.03'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -407,12 +407,14 @@ function onGPS(fix) { if ( sp < 10 ) sp = sp.toFixed(1); else sp = Math.round(sp); - if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); + + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp; // Altitude al = lf.alt; al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); - if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); + + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al; // Distance to waypoint di = distance(lf,wp); @@ -538,6 +540,39 @@ function nextFunc(dur) { onGPS(lf); } + +function updateClock() { + if (!canDraw) return; + if ( cfg.modeA != 4 ) return; + drawClock(); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + g.clear(); + Bangle.drawWidgets(); + setLpMode('SuperE'); // off + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt2.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// == Events + function setButtons(){ // BTN1 - Max speed/alt or next waypoint @@ -579,37 +614,42 @@ function setButtons(){ } -function updateClock() { - if (!canDraw) return; - if ( cfg.modeA != 4 ) return; - drawClock(); - if ( emulator ) {max.spd++;max.alt++;} -} +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); -function startDraw(){ - canDraw=true; - g.clear(); - Bangle.drawWidgets(); - setLpMode('SuperE'); // off - onGPS(lf); // draw app screen -} +Bangle.on('swipe',function(dir) { + if(dir == 1) { +console.log('RIGHT'); + prevScrn(); + } + else { +console.log('LEFT'); + nextScrn(); + } +}); -function stopDraw() { - canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. -} +Bangle.on('touch', function(button){ + switch(button){ + case 1: // BTN4 +console.log('BTN4'); + prevScrn(); + break; + case 2: // BTN5 +console.log('BTN5'); + nextScrn(); + break; + case 3: +console.log('MDL'); + nextFunc(0); // Centre - same function as short BTN1 + break; + } + }); -function savSettings() { - require("Storage").write('speedalt2.json',cfg); -} -function setLpMode(m) { - if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power - if ( !gpssetup ) return; - gpssetup.setPowerMode({power_mode:m}); -} - -// =Main Prog +// == Main Prog // Read settings. let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; @@ -657,29 +697,6 @@ var SCREENACCESS = { release:function(){this.withApp=true;startDraw();} }; -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) startDraw(); - else stopDraw(); -}); - -Bangle.on('swipe',function(dir) { - if(dir == 1) prevScrn(); - else nextScrn(); -}); - -/* -dir : "left/right/top/bottom/front/back", - double : true/false // was this a double-tap? - x : -2 .. 2, // the axis of the tap - y : -2 .. 2, // the axis of the tap - z : -2 .. 2 // the axis of the tap -Bangle.on('tap',function(tap) { - console.log('Tap : '+tap.dir); - if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 -}); -*/ - var gpssetup; try { gpssetup = require("gpssetup"); From 0d79ca3f0cd9f584b97f9ea6d1af2dd69a92e324 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:00:31 +1300 Subject: [PATCH 119/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 07bf11c6d..70b7d3086 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.02", + "version":"1.03", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From e797480d166bbc6bcd890430e3656ed1b3a11e2a Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:14:38 +1300 Subject: [PATCH 120/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 70b7d3086..1ec109fbc 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.03", + "version":"1.04", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 4088f7ec768bc86f440e3f9564420f4bc43649c0 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:16:13 +1300 Subject: [PATCH 121/325] Update app.js --- apps/speedalt2/app.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 8203988f4..6bd967b1b 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.03'; +var v = '1.04'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -621,17 +621,13 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { - if(dir == 1) { -console.log('RIGHT'); - prevScrn(); - } - else { -console.log('LEFT'); - nextScrn(); - } + if(dir == 1) prevScrn(); + else nextScrn(); }); Bangle.on('touch', function(button){ + nextFunc(0); // Same function as short BTN1 +/* switch(button){ case 1: // BTN4 console.log('BTN4'); @@ -646,9 +642,11 @@ console.log('MDL'); nextFunc(0); // Centre - same function as short BTN1 break; } +*/ }); + // == Main Prog // Read settings. From 70202e32ea27cc5b1745ac933c91c8fcd4735f99 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 14:46:37 +1300 Subject: [PATCH 122/325] Update app.js --- apps/speedclock/app.js | 371 ++++++++++++++--------------------------- 1 file changed, 128 insertions(+), 243 deletions(-) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index 4c74ce1be..ed4e2b47e 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1,19 +1,9 @@ -// Morphing Clock + -// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch -// Icon by https://icons8.com -var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var locale = require("locale"); -var CHARW = 28; // how tall are digits? -var CHARP = 2; // how chunky are digits? -var Y = 50; // start height -// Offscreen buffer -var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); -var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; -// The last time that we displayed -var lastTime = "-----"; -// If animating, this is the interval's id -var animInterval; -var timeInterval; + +var v='0.01'; + +// timeout used to update every minute +var drawTimeout; +var x,y,w,h; // Variables for the stopwatch var counter = -1; // Counts seconds var oldDate = new Date(2020,0,1); // Initialize to a past date @@ -22,206 +12,25 @@ var B3 = 0; // Flag to track BTN3's current function var w1; // watch id for BTN1 var w3; // watch id for BTN3 -/* Get array of lines from digit d to d+1. - n is the amount (0..1) - maxFive is true is this digit only counts 0..5 */ -const DIGITS = { - " ":n=>[], - "0":n=>[ - [n,0,1,0], - [1,0,1,1], - [1,1,1,2], - [n,2,1,2], - [n,1,n,2], - [n,0,n,1]], - "1":n=>[ - [1-n,0,1,0], - [1,0,1,1], - [1-n,1,1,1], - [1-n,1,1-n,2], - [1-n,2,1,2]], - "2":n=>[ - [0,0,1,0], - [1,0,1,1], - [0,1,1,1], - [0,1+n,0,2], - [1,2-n,1,2], - [0,2,1,2]], - "3":n=>[ - [0,0,1-n,0], - [0,0,0,n], - [1,0,1,1], - [0,1,1,1], - [1,1,1,2], - [n,2,1,2]], - "4":n=>[ - [0,0,0,1], - [1,0,1-n,0], - [1,0,1,1-n], - [0,1,1,1], - [1,1,1,2], - [1-n,2,1,2]], - "5to0": n=>[ // 5 -> 0 - [0,0,0,1], - [0,0,1,0], - [n,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,2,0,2], - [1,1-n,1,1], - [0,1,0,1+n]], - "5to6": n=>[ // 5 -> 6 - [0,0,0,1], - [0,0,1,0], - [0,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,2-n,0,2]], - "6":n=>[ - [0,0,0,1-n], - [0,0,1,0], - [n,1,1,1], - [1,1-n,1,1], - [1,1,1,2], - [n,2,1,2], - [0,1-n,0,2-2*n]], - "7":n=>[ - [0,0,0,n], - [0,0,1,0], - [1,0,1,1], - [1-n,1,1,1], - [1,1,1,2], - [1-n,2,1,2], - [1-n,1,1-n,2]], - "8":n=>[ - [0,0,0,1], - [0,0,1,0], - [1,0,1,1], - [0,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,1,0,2-n]], - "9":n=>[ - [0,0,0,1], - [0,0,1,0], - [1,0,1,1], - [0,1,1-n,1], - [0,1,0,1+n], - [1,1,1,2], - [0,2,1,2]], - ":":n=>[ - [0.4,0.4,0.6,0.4], - [0.6,0.4,0.6,0.6], - [0.6,0.6,0.4,0.6], - [0.4,0.4,0.4,0.6], - [0.4,1.4,0.6,1.4], - [0.6,1.4,0.6,1.6], - [0.6,1.6,0.4,1.6], - [0.4,1.4,0.4,1.6]] -}; - -/* Draw a transition between lastText and thisText. - 'n' is the amount - 0..1 */ -function drawDigits(lastText,thisText,n) { - "ram" - const p = CHARP; // padding around digits - const s = CHARW; // character size - var x = 16; // x offset - g.reset(); - for (var i=0;i{ - if (c[0]!=c[2]) // horiz - buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s); - else if (c[1]!=c[3]) // vert - buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s); - }); - g.drawImage(bufimg,x,Y); - } - if (thisCh==":") x-=4; - x+=s+p+7; - } -} -function drawDate() { - var x = (CHARW + CHARP + 8)*5; - var y = Y + 2*CHARW + CHARP; - var d = new Date(); - // meridian - g.reset(); - g.setFont("6x8",2); - g.setFontAlign(-1,-1); - if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true); - // date - g.setFont("Vector16"); - g.setFontAlign(0,-1); - // Only draw the date if it has changed: - if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) { - var date = locale.date(d,false); - g.clearRect(1,y+8,g.getWidth(),y+24); - g.drawString(date, g.getWidth()/2, y+8, true); - oldDate = d; - } +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); } -function drawSeconds() { - var x = (CHARW + CHARP + 8)*5; - var y = Y + 2*CHARW + CHARP; - var d = new Date(); - // seconds - g.reset(); - g.setFont("6x8",2); - g.setFontAlign(-1,-1); - g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true); -} +function stopWatch(clear) { -/* Show the current time, and animate if needed */ -function showTime() { - if (animInterval) return; // in animation - quit - var d = new Date(); - var hours = d.getHours(); - if (is12Hour) hours = ((hours + 11) % 12) + 1; - var t = (" "+hours).substr(-2)+":"+ - ("0"+d.getMinutes()).substr(-2); - var l = lastTime; - // same - don't animate - if (t==l || l=="-----") { - drawDigits(l,t,0); - drawDate(); - drawSeconds(); - lastTime = t; - return; - } - var n = 0; - animInterval = setInterval(function() { - n += 1/10; - if (n>=1) { - n=1; - clearInterval(animInterval); - animInterval = undefined; - } - drawDigits(l,t,n); - drawSeconds(); - }, 20); - lastTime = t; -} - -function stopWatch() { + x = 240; + y = 200; + w = 110; + h = 24; + g.clearRect(x-w,y-h,x,y); // clear the background + if (clear) return; + counter++; var hrs = Math.floor(counter/3600); @@ -240,21 +49,22 @@ function stopWatch() { BTN3, {repeat:false,edge:"falling"}); B3 = 0; // BTN3 is bound to stop the stopwatch } - + // Bind BTN1 to call the reset function: if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); // Draw elapsed time: + g.reset(); - g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24"); - g.clearRect(1,180,g.getWidth(),210); - if (hrs>0) { - g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true); - g.drawString( ":", g.getWidth()/2 - 48, 180, true); - } - g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true); - g.drawString( ":", g.getWidth()/2, 180, true); - g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true); + g.setColor(0.0,0.5,1.0); + g.setFontAlign(1,1); + g.setFont("Vector24"); + + var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); + + if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; + + g.drawString(swStr, x, y, true); } @@ -267,7 +77,8 @@ function resetStopWatch() { } // Clear the stopwatch: - g.clearRect(1,180,g.getWidth(),210); + stopWatch(true); +// g.clearRect(1,180,g.getWidth(),210); // Reset the counter: counter = -1; @@ -286,33 +97,107 @@ function resetStopWatch() { } -Bangle.on('lcdPower',function(on) { - if (animInterval) { - clearInterval(animInterval); - animInterval = undefined; +function drawDate() { + // draw date + x = 0; + y = 200; + w = 70; + h = 10; + + var date = new Date(); + var dateStr = require("locale").date(date); + g.reset(); + + g.clearRect(x,y-h,x+w,y); // clear the background + + g.setFontAlign(-1,1).setFont("6x8"); + g.drawString(dateStr,x,y); + +} + + +function drawTime() { + x = 120; + y = 107; + w = 120; + h = 134; + + var date = new Date(); + var timeStr = require("locale").time(date,1); + g.reset(); + + g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background + + g.setFontAlign(0,0); + g.setFontVector(85); +// g.setColor(0.5,0.5,0.5); + g.drawString(timeStr.substring(0,2),x,y-30); + g.drawString(timeStr.substring(3,5),x,y+38); +// g.drawString(timeStr,x,y); +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; } - if (timeInterval) { - clearInterval(timeInterval); - timeInterval = undefined; + + // Clear the stopwatch: + stopWatch(true); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + +function draw() { + x = g.getWidth()/2; + y = g.getHeight()/2; + g.reset(); + + + drawTime(); + drawDate(); + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); + +// draw immediately at first, queue update +draw(); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - showTime(); - timeInterval = setInterval(showTime, 1000); - } else { - lastTime = "-----"; + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); - -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -// Update time once a second -timeInterval = setInterval(showTime, 1000); -showTime(); - -// Show launcher when button pressed +// Show launcher when middle button pressed Bangle.setUI("clock"); // Start stopwatch when BTN3 is pressed setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); B3 = 1; // BTN3 is bound to start the stopwatch + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); From 49600be430a30f1ee5e9bff06d4c765944743cbf Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:01:48 +1300 Subject: [PATCH 123/325] Update app.js --- apps/speedclock/app.js | 84 +++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index ed4e2b47e..28e9494f4 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1,9 +1,15 @@ +/* +Simple watch [speedwatch] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ var v='0.01'; // timeout used to update every minute var drawTimeout; var x,y,w,h; + // Variables for the stopwatch var counter = -1; // Counts seconds var oldDate = new Date(2020,0,1); // Initialize to a past date @@ -12,6 +18,11 @@ var B3 = 0; // Flag to track BTN3's current function var w1; // watch id for BTN1 var w3; // watch id for BTN3 +// Colours +var colTime = 0x4FE0; +var colDate = 0xEFE0; +var colSW = 0x1DFD; + // schedule a draw for the next minute function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); @@ -23,12 +34,14 @@ function queueDraw() { function stopWatch(clear) { - x = 240; + x = 120; y = 200; - w = 110; - h = 24; + w = 240; + h = 25; - g.clearRect(x-w,y-h,x,y); // clear the background + g.reset(); + g.setColor(colSW); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background if (clear) return; counter++; @@ -54,10 +67,7 @@ function stopWatch(clear) { if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); // Draw elapsed time: - - g.reset(); - g.setColor(0.0,0.5,1.0); - g.setFontAlign(1,1); + g.setFontAlign(0,1); g.setFont("Vector24"); var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); @@ -78,7 +88,9 @@ function resetStopWatch() { // Clear the stopwatch: stopWatch(true); -// g.clearRect(1,180,g.getWidth(),210); + + // Restore the date + drawDate(); // Reset the counter: counter = -1; @@ -99,18 +111,20 @@ function resetStopWatch() { function drawDate() { // draw date - x = 0; + x = 120; y = 200; - w = 70; - h = 10; + w = 240; + h = 25; - var date = new Date(); - var dateStr = require("locale").date(date); g.reset(); - - g.clearRect(x,y-h,x+w,y); // clear the background + g.setColor(colDate); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - g.setFontAlign(-1,1).setFont("6x8"); + var date = new Date(); +// var dateStr = require("locale").date(date,1); + var dateStr = date.getDate() + ' ' +require("locale").month(date,1); + g.setFontAlign(0,1); + g.setFont("Vector24"); g.drawString(dateStr,x,y); } @@ -125,42 +139,12 @@ function drawTime() { var date = new Date(); var timeStr = require("locale").time(date,1); g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); g.setFontVector(85); -// g.setColor(0.5,0.5,0.5); + g.setColor(colTime); g.drawString(timeStr.substring(0,2),x,y-30); g.drawString(timeStr.substring(3,5),x,y+38); -// g.drawString(timeStr,x,y); -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} } function draw() { @@ -168,9 +152,8 @@ function draw() { y = g.getHeight()/2; g.reset(); - drawTime(); - drawDate(); + if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. // queue draw in one minute queueDraw(); @@ -191,6 +174,7 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); + // Show launcher when middle button pressed Bangle.setUI("clock"); From 314d0cf3b0dc700852f970f71e1a69d75175e0ee Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:20:51 +1300 Subject: [PATCH 124/325] Update app-icon.js --- apps/speedclock/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js index f4f24a18b..22e264124 100644 --- a/apps/speedclock/app-icon.js +++ b/apps/speedclock/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) From eeab8a5397102cfe02b5eeaa4e0cafd8679cab9f Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:21:27 +1300 Subject: [PATCH 125/325] Add files via upload --- apps/speedclock/watch.png | Bin 0 -> 1439 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedclock/watch.png diff --git a/apps/speedclock/watch.png b/apps/speedclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 From 642efb242e8a52c9f7a34aa61ac3de8982dad444 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:22:17 +1300 Subject: [PATCH 126/325] Delete app.png --- apps/speedclock/app.png | Bin 1639 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/speedclock/app.png diff --git a/apps/speedclock/app.png b/apps/speedclock/app.png deleted file mode 100644 index 93d8e57dcbfaf905321fffcf06df042aa49d7dc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Fri, 22 Oct 2021 16:28:38 +1300 Subject: [PATCH 127/325] Update apps.json --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index 1ec109fbc..062e363ba 100644 --- a/apps.json +++ b/apps.json @@ -2916,6 +2916,25 @@ {"name":"speedalt2.json"} ] }, +{ "id": "speedclock", + "name": "SloMo Clock", + "shortName":"SloMo Clock", + "icon": "watch.png", + "version":"0.01", + "description": "Simple clock face with large digits hour above minutes.", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"speedclock.app.js","url":"app.js"}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, + {"name":"speedclock.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"speedclock.json"} + ] +}, { "id": "de-stress", "name": "De-Stress", "shortName":"De-Stress", From 18d0e7e161fbc0dd85964fed10f47e1f89ab6d46 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:32:16 +1300 Subject: [PATCH 128/325] Update apps.json --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index 062e363ba..f848201af 100644 --- a/apps.json +++ b/apps.json @@ -2929,7 +2929,6 @@ "storage": [ {"name":"speedclock.app.js","url":"app.js"}, {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, - {"name":"speedclock.settings.js","url":"settings.js"} ], "data": [ {"name":"speedclock.json"} From e262d1bfdb2f38a7971ed6872a19555dfe048d56 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:34:33 +1300 Subject: [PATCH 129/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f848201af..781a36434 100644 --- a/apps.json +++ b/apps.json @@ -2928,7 +2928,7 @@ "readme": "README.md", "storage": [ {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"speedclock.json"} From f413930ced017c08b18c4f909657d0cda7388144 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 08:52:05 +0100 Subject: [PATCH 130/325] Misc battery widget tweaks, and make icon = the actual widget's icon --- apps.json | 19 ++++++++++++++++--- apps/widbat/ChangeLog | 1 + apps/widbat/widget.js | 29 ++++++++++++----------------- apps/widbat/widget.png | Bin 297 -> 280 bytes apps/widbatv/ChangeLog | 1 + apps/widbatv/widget.js | 19 +++++++++++++++++++ apps/widbatv/widget.png | Bin 0 -> 221 bytes apps/widtbat/ChangeLog | 1 + apps/widtbat/widget.js | 28 +++++++++++----------------- apps/widtbat/widget.png | Bin 911 -> 238 bytes 10 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 apps/widbatv/ChangeLog create mode 100644 apps/widbatv/widget.js create mode 100644 apps/widbatv/widget.png diff --git a/apps.json b/apps.json index 839787a2b..4a7517e22 100644 --- a/apps.json +++ b/apps.json @@ -716,7 +716,7 @@ { "id": "widbat", "name": "Battery Level Widget", - "version": "0.08", + "version": "0.09", "description": "Show the current battery level and charging status in the top right of the clock", "icon": "widget.png", "type": "widget", @@ -726,6 +726,19 @@ {"name":"widbat.wid.js","url":"widget.js"} ] }, + { + "id": "widbatv", + "name": "Battery Level Widget (Vertical)", + "version": "0.01", + "description": "Slim, vertical battery widget that only takes up 14px", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbatv.wid.js","url":"widget.js"} + ] + }, { "id": "widlock", "name": "Lock Widget", @@ -1617,12 +1630,12 @@ { "id": "widtbat", "name": "Tiny Battery Widget", - "version": "0.01", + "version": "0.02", "description": "Tiny blueish battery widget, vibs and changes level color when charging", "icon": "widget.png", "type": "widget", "tags": "widget,tool,system", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widtbat.wid.js","url":"widget.js"} ] diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index a5fdc31cc..5986ecf3f 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -5,3 +5,4 @@ 0.06: Use 'g.theme' (requires bootloader 0.23) 0.07: Move CHARGING variable to more readable string 0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on +0.09: Misc speed/memory tweaks diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index 739326df0..a8a0c5382 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -1,26 +1,11 @@ (function(){ - function setWidth() { WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0); } - function draw() { - var s = 39; - var x = this.x, y = this.y; - g.reset(); - if (Bangle.isCharging()) { - g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); - x+=16; - } - g.setColor(g.theme.fg); - g.fillRect(x,y+2,x+s-4,y+21); - g.clearRect(x+2,y+4,x+s-6,y+19); - g.fillRect(x+s-3,y+10,x+s,y+14); - g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); - } Bangle.on('charging',function(charging) { if(charging) Bangle.buzz(); setWidth(); - Bangle.drawWidgets(); // relayout widgets + Bangle.drawWidgets(); // re-layout widgets g.flip(); }); var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined; @@ -37,6 +22,16 @@ } } }); - WIDGETS["bat"]={area:"tr",width:40,draw:draw}; + WIDGETS["bat"]={area:"tr",width:40,draw:function() { + var s = 39; + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + x+=16; + } + g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); + g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); + }}; setWidth(); })() diff --git a/apps/widbat/widget.png b/apps/widbat/widget.png index 630692e38e3b9ba5fbb62b2bc3a33cd61be04836..4f7491ee9f2b43d2eb9d76638d0ac7236f71b3c3 100644 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=luL|VRno>I?jle~HZvrm#5q4VH#M&W$Yo$~E=o--Nlj5G&n(GMaQE~LNYP7W z2a5N3x;Tbp+k%W^7#OjF z>xR6YxZwP~msj(vrfOZ-XeRq|Me#Trz%PXw%s%enD+*Q(4(O$7{!;ZftnH z;OBlT>zT*Z6fWiIJ=^fB#+Gq&KYwNU9G>d#i1%lbc&mQzv1$1Hd4@jEzQ$ig(U1P} ztw_>iy0bUgRQAhv#r8w_3THnth|Y_Bu~2P(K-W{of(yL1!oTHzSg}U@{@LL7qVaI@ z+@$_vb^q8z5_}o2;UU)6GFFD!1nXQXDJyW8*}q*=>C>8uX+JZBtJj^_U(L8LQa1YE T%Q{h@PZ>O2{an^LB{Ts5c#wMk diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widbatv/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js new file mode 100644 index 000000000..cc52a0f8e --- /dev/null +++ b/apps/widbatv/widget.js @@ -0,0 +1,19 @@ +Bangle.on('charging',function(charging) { + if(charging) Bangle.buzz(); + WIDGETS["batv"].draw(); +}); +setInterval(()=>WIDGETS["batv"].draw(), 60000); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["batv"].draw(); // refresh at power on +}); +WIDGETS["batv"]={area:"tr",width:14,draw:function() { + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + } else { + g.clearRect(x,y,x+14,y+24); + g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); + g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20); + } +}}; diff --git a/apps/widbatv/widget.png b/apps/widbatv/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..e31704d7bb7d90eeee09b4d116729f723a5f9490 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfQ!X)fMY&@Rd}Tl(+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9Vi~`>EamTas2HxOD+Zl9v0(A|M$y82O63^I+&;4;^W8jF3);u&Sn;lsp4-Ne(kiA zI^!$7f3MReSyibvNwwvMwORZISC|=D1RngEf99y { - const CBS = 0x41f, CBC = 0x07E0; - var xo = 6, xl = 22, yo = 9, h = 17; - - function draw() { - g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); - g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); - var cbc = (Bangle.isCharging()) ? CBC : CBS; - g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); - } - Bangle.on('charging', function(charging) { - if (charging) Bangle.buzz(); - Bangle.drawWidgets(); - }); - WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw }; -})(); +Bangle.on('charging', function(charging) { + if (charging) Bangle.buzz(); + WIDGETS["widtbat"].draw(); +}); +WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() { + const xo = 6, xl = 22, yo = 9, h = 17; + g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); + g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); + var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f"; + g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); +} }; diff --git a/apps/widtbat/widget.png b/apps/widtbat/widget.png index 4294f0ca32299687f44c631a14109629f857f4a1..f2943bc52013b52db5cba8383076118faf2cee42 100644 GIT binary patch delta 210 zcmeBYf5$jMrJl3EBeIx*fm;}a85w5Hkzin8U@!6Xb!C6bCB|;Z|IEndDp07`)5S3) zc47kxh(UJ5u}CVu5^^4XIdJ^guk&< uHnkdA$(@|PJx4l!-G_RZkwCiPK9iWFx9rgeFQONMggssTT-G@yGywoz*h~We literal 911 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}0jUw5X}-P; zT0k}j0~4bV12aeo5Hhr9GO&Qz3=C>Ont_3N0V6_o0TW!-U;#6N4N`dWm6H(AkjTuC zh>{3jAFJg2T)o7U{G?R9irfN_0tTB3D*7iAWdWaj57fXq!y$}cUk zRZ;?31P2gzmSmu1yMhW`HkzTTSoaJ~2f8+kKR6OrFMCe`Z3#?B8 zC49*z`DeL<{|n(yEW*2#>kN&)@-iK7o}>EZyS}siuHP4aO2{#|b+YeT#Q0>l@TS?U z-Aq$V>eZY@m1ibibT(C-EV=!^vgEPuBgal1dB(`on$ebYVzQRQSqH^a%uB2$%z8DE z>HW$2WB(=7FJIlJG`oKy`vg^X$>_F+_K$r^%z0J$4H%3Uf0^)&Vb^2FKZ>Hvi{%c8 zF){0!&1!K=Za5?$k>$y;xZYrj&4(F{orb)&huCVPI|2^(Yw!ygvDk*ZIC=b8qrU_D zf!{11YaOe0ai@u|sP7R^IL*BK!>a|WRUYR#T}!NZa6kT-*Meo`Ehd*7|H&FNC$lP6 z8`u=+ML*@cApFdsH@o47vLbVa!hgXV%VUoI`al2PNuH;-O$_R Date: Fri, 22 Oct 2021 08:54:44 +0100 Subject: [PATCH 131/325] widbt: memory usage improvements --- apps.json | 2 +- apps/widbt/ChangeLog | 1 + apps/widbt/widget.js | 30 +++++++++++++----------------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps.json b/apps.json index 4a7517e22..d6dc915e9 100644 --- a/apps.json +++ b/apps.json @@ -790,7 +790,7 @@ { "id": "widbt", "name": "Bluetooth Widget", - "version": "0.06", + "version": "0.07", "description": "Show the current Bluetooth connection status in the top right of the clock", "icon": "widget.png", "type": "widget", diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index 4509487ac..7aa96ce5c 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -3,3 +3,4 @@ 0.04: Fix automatic update of Bluetooth connection status 0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color 0.06: Tweaking colors for dark/light themes and low bpp screens +0.07: Memory usage improvements diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index a25d2c21c..209a8c8d8 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,17 +1,13 @@ -(function(){ - function draw() { - g.reset(); - if (NRF.getSecurityStatus().connected) - g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else - g.setColor(g.theme.dark ? "#666" : "#999"); - g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); - } - function changed() { - WIDGETS["bluetooth"].draw(); - g.flip();// turns screen on - } - NRF.on('connect',changed); - NRF.on('disconnect',changed); - WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw}; -})() +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); +WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { + g.reset(); + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS["bluetooth"].draw(); + Bangle.setLCDPower(1); // turn screen on +}}; From 1942d5d6846c5dc093ce304f635ab9aaaea8788d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 11:46:58 +0100 Subject: [PATCH 132/325] minor original launcher tweak --- apps/launch/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launch/app.js b/apps/launch/app.js index 449e16e62..3d4682e55 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -16,7 +16,7 @@ function drawMenu() { var w = g.getWidth(); var h = g.getHeight(); var m = w/2; - var n = (h-48)/64; + var n = Math.floor((h-48)/64); if (selected>=n+menuScroll) menuScroll = 1+selected-n; if (selected Date: Fri, 22 Oct 2021 11:47:23 +0100 Subject: [PATCH 133/325] Adding BETA messages app --- apps.json | 20 ++- apps/messages/README.md | 21 +++ apps/messages/app-icon.js | 1 + apps/messages/app.js | 273 ++++++++++++++++++++++++++++++++++++++ apps/messages/app.png | Bin 0 -> 917 bytes apps/messages/boot.js | 36 +++++ apps/messages/lib.js | 0 apps/messages/widget.js | 20 +++ 8 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 apps/messages/README.md create mode 100644 apps/messages/app-icon.js create mode 100644 apps/messages/app.js create mode 100644 apps/messages/app.png create mode 100644 apps/messages/boot.js create mode 100644 apps/messages/lib.js create mode 100644 apps/messages/widget.js diff --git a/apps.json b/apps.json index d6dc915e9..a372476a0 100644 --- a/apps.json +++ b/apps.json @@ -31,6 +31,23 @@ ], "sortorder": -10 }, + { + "id": "messages", + "name": "Messages", + "version": "0.01", + "description": "App to display notifications from iOS and Gadgetbridge (BETA)", + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"messages.app.js","url":"app.js"}, + {"name":"messages.img","url":"app-icon.js","evaluate":true}, + {"name":"messages.boot.js","url":"boot.js"}, + {"name":"messages.wid.js","url":"widget.js"} + ], + "sortorder": -9 + }, { "id": "health", "name": "Health Tracking", @@ -46,8 +63,9 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder": -10 + "sortorder": -8 }, + { "id": "moonphase", "name": "Moonphase", diff --git a/apps/messages/README.md b/apps/messages/README.md new file mode 100644 index 000000000..c243ec06a --- /dev/null +++ b/apps/messages/README.md @@ -0,0 +1,21 @@ +# Messages app + +**THIS APP IS CURRENTLY BETA** + +This app handles the display of messages and message notifications. It stores +a list of currently received messages and allows them to be listed, viewed, +and responded to. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + +## Usage + +... + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams diff --git a/apps/messages/app-icon.js b/apps/messages/app-icon.js new file mode 100644 index 000000000..6ed3c1141 --- /dev/null +++ b/apps/messages/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA=")) diff --git a/apps/messages/app.js b/apps/messages/app.js new file mode 100644 index 000000000..d369ee175 --- /dev/null +++ b/apps/messages/app.js @@ -0,0 +1,273 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +/* For example for maps: + +GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) +GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."}) +GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."}) +GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"}) +GB({"t":"notify-","id":1}) + +GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) +GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) +GB({"t":"notify~","id":1,"title":"High St"}) +GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"}) +GB({"t":"notify~","id":1,"title":"0 yd - High St"}) +GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"}) + +*/ + +var Layout = require("Layout"); + +var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; +if (!Array.isArray(MESSAGES)) MESSAGES=[]; +var onMessagesModified = function(msg) { + // TODO: if new, show this new one + if (msg.new) Bangle.buzz(); + showMessage(msg.id); +}; +function saveMessages() { + require("Storage").writeJSON("messages.json",MESSAGES) +} + +function showMapMessage(msg) { + var m; + var distance, street, target, eta; + m=msg.title.match(/(.*) - (.*)/); + if (m) { + distance = m[1]; + street = m[2]; + } else street=msg.title; + m=msg.body.match(/(.*) - (.*)/); + if (m) { + target = m[1]; + eta = m[2]; + } else target=msg.body; + layout = new Layout({ + type:"v", c: [ + {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, + {type:"h", bgCol:"#0f0", fillx:1, c: [ + {type:"txt", font:"6x8", label:"Towards" }, + {type:"txt", font:"6x15:2", label:street } + ]}, + {type:"h",fillx:1, filly:1, c: [ + {type:"img",src:atob(msg.img)}, + {type:"v", fillx:1, c: [ + {type:"txt", font:"6x15:2", label:distance||"" } + ]}, + ]}, + + {type:"txt", font:"6x8:2", label:eta } + ] + }); + g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1); + layout.render(); + Bangle.setUI("updown",function() { + // any input to mark as not new and return to menu + msg.new = false; + saveMessages(); + checkMessages(); + }); +} + +function showMessage(msgid) { + var msg = MESSAGES.find(m=>m.id==msgid); + if (!msg) return checkMessages(); // go home if no message found + if (msg.src=="Maps") return showMapMessage(msg); + var m = msg.title+"\n"+msg.body; + E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => { + if (chosen=="read") { + // any input to mark as not new and return to menu + msg.new = false; + saveMessages(); + checkMessages(); + } else { + checkMessages(true); + } + }); +} + +// Show a single menu item for the message +function showMessageMenuItem(y, idx) { + var msg = MESSAGES[idx]; + var W = g.getWidth(), H=48; + if (msg.new) g.setBgColor("#4F4"); + else g.setBgColor("#CFC"); + g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg); + var m = msg.title+"\n"+msg.body; + if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2); + if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2); + if (msg.body) { + g.setFontAlign(-1,-1).setFont("6x8"); + var l = g.wrapString(msg.body, W-14); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), 12,y+20); + } +} +//test +//g.clear(1); showMessageMenuItem(MESSAGES[0],24) + +if (process.env.HWVERSION==1) { // Bangle.js 1 + showBigMenu = function(options) { + /* options = { + h = height + items = # of items + draw = function(y, idx) + onSelect = function(idx) + }*/ + var selected = 0; + var menuScroll = 0; + var menuShowing = false; + var w = g.getWidth(); + var h = g.getHeight(); + var m = w/2; + var n = Math.floor((h-48)/options.h); + + function drawMenu() { + g.reset(); + if (selected>=n+menuScroll) menuScroll = 1+selected-n; + if (selected=options.items) break; + var y = 24+i*options.h; + options.draw(y, idx); + // border for selected + if (i+menuScroll==selected) { + g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2); + } + } + // arrows + g.setColor(menuScroll ? g.theme.fg : g.theme.bg); + g.fillPoly([m,6,m-14,20,m+14,20]); + g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg); + g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); + } + g.clearRect(0,24,w-1,h-1); + drawMenu(); + Bangle.setUI("updown",dir=>{ + if (dir) { + selected += dir; + if (selected<0) selected = options.items-1; + if (selected>=options.items) selected = 0; + drawMenu(); + } else { + options.onSelect(selected); + } + }); + } +} else { // Bangle.js 2 + showBigMenu = function(options) { + /* options = { + h = height + items = # of items + draw = function(y, idx) + onSelect = function(idx) + }*/ + var menuScroll = 0; + var menuShowing = false; + var w = g.getWidth(); + var h = g.getHeight(); + var n = Math.ceil((h-24)/options.h); + var menuScrollMax = options.h*options.items - (h-24); + + function drawItem(i) { + var y = 24+i*options.h-menuScroll; + if (i<0 || i>=options.items || y<-options.h || y>=h) return; + options.draw(y, i); + } + + function drawMenu() { + g.reset().clearRect(0,24,w-1,h-1); + g.setClipRect(0,24,w-1,h-1); + for (var i=0;i{ + var dy = e.dy; + if (menuScroll - dy < 0) + dy = menuScroll; + if (menuScroll - dy > menuScrollMax) + dy = menuScroll - menuScrollMax; + if (!dy) return; + g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); + g.scroll(0,dy); + menuScroll -= dy; + if (e.dy < 0) { + drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1); + if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2); + } else { + drawItem(Math.floor((menuScroll+24)/options.h)); + if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1); + } + g.setClipRect(0,0,w-1,h-1); + }; + Bangle.on('drag',Bangle.dragHandler); + Bangle.touchHandler = (_,e)=>{ + if (e.y<20) return; + var i = Math.floor((e.y+menuScroll-24) / options.h); + if (i>=0 && i { load() }); + // we have >0 messages + // TODO: IF A NEW MESSAGE, SHOW IT + if (!forceShowMenu) { + var newMessages = MESSAGES.filter(m=>m.new); + if (newMessages.length) + return showMessage(newMessages[0].id); + } + // Otherwise show a menu + var m = { + "":{title:"Messages"}, + "< Back": ()=>load() + }; + /*g.setFont("6x8"); + MESSAGES.forEach(msg=>{ + // "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents" + var title = g.wrapString(msg.title, g.getWidth())[0]; + m[title] = function() { + showMessage(msg.id); + } + }); + E.showMenu(m);*/ + showBigMenu({ + h : 48, + items : MESSAGES.length, + draw : showMessageMenuItem, + onSelect : idx => showMessage(MESSAGES[idx].id) + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +checkMessages(); diff --git a/apps/messages/app.png b/apps/messages/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/messages/boot.js b/apps/messages/boot.js new file mode 100644 index 000000000..dce3979da --- /dev/null +++ b/apps/messages/boot.js @@ -0,0 +1,36 @@ +(function() { + var _GB = global.GB; + global.GB = (event) => { + if (_GB) setTimeout(_GB,0,event); + // call handling? + if (!event.t.startsWith("notify")) return; + /* event is: + {t:"notify",id:int, src,title,subject,body,sender,tel:string} + {t:"notify~",id:int, title:string} // modified + {t:"notify-",id:int} // remove + */ + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) + messages = MESSAGES; // we're in an app that has already loaded messages + else // no app - load messages + messages = require("Storage").readJSON("messages.json",1)||[]; + // now modify/delete as appropriate + var mIdx = messages.findIndex(m=>m.id==event.id); + if (event.t=="notify-") { + if (mIdx>=0) messages.splice(mIdx, 1); // remove item + mIdx=-1; + } else { // add/modify + if (event.t=="notify") event.new=true; // new message + if (mIdx<0) mIdx=messages.push(event)-1; + else Object.assign(messages[mIdx], event); + } + require("Storage").writeJSON("messages.json",messages); + if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); + // ok, saved now - we only care if it's new + if (event.t!="notify") return; + // if we're in a clock, go straight to messages app + if (Bangle.CLOCK) return load("messages.app.js"); + if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know + WIDGETS.messages.newMessage(); + }; +})() diff --git a/apps/messages/lib.js b/apps/messages/lib.js new file mode 100644 index 000000000..e69de29bb diff --git a/apps/messages/widget.js b/apps/messages/widget.js new file mode 100644 index 000000000..eda4a85a5 --- /dev/null +++ b/apps/messages/widget.js @@ -0,0 +1,20 @@ +WIDGETS["messages"]={area:"tl",width:0,draw:function() { + if (!this.width) return; + var c = (Date.now()-this.t)/1000; + g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff"); + g.clearRect(this.x,this.y,this.x+this.width,this.y+23); + g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12); + //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute + if (c<120 && (Date.now()-this.l)>4000) { + this.l = Date.now(); + Bangle.buzz(); // buzz every 4 seconds + } + setTimeout(()=>WIDGETS["messages"].draw(), 1000); +},newMessage:function() { + WIDGETS["messages"].t=Date.now(); // first time + WIDGETS["messages"].l=Date.now()-10000; // last buzz + if (WIDGETS["messages"].c!==undefined) return; // already called + WIDGETS["messages"].width=64; + Bangle.drawWidgets(); + Bangle.setLCDPower(1);// turns screen on +}}; From 4fae5fc05dad52ad997221493528f77b12cfa17e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 12:57:53 +0100 Subject: [PATCH 134/325] oops --- apps/widbt/widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index 209a8c8d8..88be3d5c9 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,5 +1,3 @@ -NRF.on('connect',WIDGETS["bluetooth"].changed); -NRF.on('disconnect',WIDGETS["bluetooth"].changed); WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { g.reset(); if (NRF.getSecurityStatus().connected) @@ -11,3 +9,5 @@ WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { WIDGETS["bluetooth"].draw(); Bangle.setLCDPower(1); // turn screen on }}; +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); From 68d7d80341de375d70582627ddf568dd1137ddd2 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Fri, 22 Oct 2021 23:29:49 +0100 Subject: [PATCH 135/325] Created Nifty Clock B --- apps/ffcniftyb/ChangeLog | 1 + apps/ffcniftyb/app-icon.js | 1 + apps/ffcniftyb/app.js | 158 +++++++++++++++++++++++++++++++++++++ apps/ffcniftyb/app.png | Bin 0 -> 2188 bytes apps/ffcniftyb/settings.js | 50 ++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 apps/ffcniftyb/ChangeLog create mode 100644 apps/ffcniftyb/app-icon.js create mode 100644 apps/ffcniftyb/app.js create mode 100644 apps/ffcniftyb/app.png create mode 100644 apps/ffcniftyb/settings.js diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog new file mode 100644 index 000000000..f6516c6de --- /dev/null +++ b/apps/ffcniftyb/ChangeLog @@ -0,0 +1 @@ +0.01: New Clock Nifty B diff --git a/apps/ffcniftyb/app-icon.js b/apps/ffcniftyb/app-icon.js new file mode 100644 index 000000000..f0a2393b1 --- /dev/null +++ b/apps/ffcniftyb/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js new file mode 100644 index 000000000..60d76ff0a --- /dev/null +++ b/apps/ffcniftyb/app.js @@ -0,0 +1,158 @@ +// setTimeout(load,100);Bangle.factoryReset(); +console.log('mem', process.memory().usage); +const locale = require("locale"); +const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + + +/* Clock *********************************************/ +const scale = g.getWidth() / 176; + +const screen = { + width: g.getWidth(), + height: g.getHeight() - 24, +}; + +const center = { + x: screen.width / 2, + y: screen.height / 2, +}; + +const color = g.toColor(255, 0, 0); +console.log('color', color); + +function d02(value) { + return ('0' + value).substr(-2); +} + +// const c = E.compiledC(` +// // void xor(int, int, int) +// void xor(int len, int *dst, int *src){ +// len = len>>2; +// while (len--) { +// *dst ^= *src; +// dst++; +// src++; +// } +// } +// `); + +// function combineLayers(l1, l2) { +// // const l1ptr = E.getAddressOf(l1.buffer, true); +// // const l2ptr = E.getAddressOf(l2.buffer, true); +// // if (l1ptr && l2ptr) { +// // c.xor(l1.buffer.length, l1ptr, l2ptr); +// // } +// if (l1 && l1.buffer && l2 && l2.buffer) { +// for (let i = 0; i < l1.buffer.length; i++) { +// l1.buffer[i] ^= l2.buffer[i]; +// } +// } +// return l1; +// } + +function renderEllipse(g) { + g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); +} + +function renderText(g) { + const now = new Date(); + + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + + g.setFontAlign(1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 80 * scale, center.y - 42 * scale); + g.drawString(month, center.x + 80 * scale, center.y - 26 * scale); + g.drawString(day, center.x + 80 * scale, center.y - 10 * scale); + g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale); + g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); +} + +function draw() { + const s = new Date().getTime(); + console.log('mem.b', process.memory().usage); + + let buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { + msb: true + }); + + let img = { + width: screen.width, + height: screen.height, + transparent: 0, + bpp: 1, + buffer: buf.buffer + }; + + // cleat screen area + g.clearRect(0, 24, g.getWidth(), g.getHeight()); + + // render outside text with ellipse + buf.clear() + renderText(buf.setColor(1)); + renderEllipse(buf.setColor(0)); + g.setColor(color).drawImage(img, 0, 24); + + // render ellipse with inside text + buf.clear() + renderEllipse(buf.setColor(1)); + renderText(buf.setColor(0)); + g.setColor(color).drawImage(img, 0, 24); + + buf = undefined; + img = undefined; + + console.log('mem.e', process.memory().usage); + console.log('draw', new Date().getTime() - s); +} + + +/* Minute Ticker *************************************/ + +let ticker; + +function stopTick() { + if (ticker) { + clearTimeout(ticker); + ticker = undefined; + } +} + +function startTick(run) { + stopTick(); + run(); + ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); + // ticker = setTimeout(() => startTick(run), 3000); +} + +/* Init **********************************************/ + +g.clear(); +startTick(draw); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', (on) => { + if (on) { + startTick(draw); + } else { + stopTick(); + } +}); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +console.log('mem', process.memory().usage); \ No newline at end of file diff --git a/apps/ffcniftyb/app.png b/apps/ffcniftyb/app.png new file mode 100644 index 0000000000000000000000000000000000000000..1cd8a49b7ba951670fb8c007055a64f6d29dfc98 GIT binary patch literal 2188 zcmV;72y^#|P)?hiUTNH2?4Z%C_80Yk+zhAr8q)Ct(B5m*P-K< z-4S(wt>O&pbbYMTu{_)o+B!O2s61R07z*;}3M7)4K*;;-#KXpzS5fKyvEwiQeD}M* z@7!O`ch5QBcfkw1z;l9R8_r-bNF)-3aDILsLg;*acXxLHfKIDhP%kWhF6az;eM7xW zE>kEJqZ6YH2E!2(!?1%N931{}cve0ul}o2)rkc(-@p!!DRFl3v{ipjs!T-Vl4d~z7 zV)dHUW8-7c9^Ni|dl{s7vOG`Jo-hyp;Nb%Zp+qKOGMRSy-`)JqOqR$b5}8D$RiR6z zN=d_MmgK6Bt2QQXM7EEQPf}7603bLdcvIpgGMSv3mda!@Jv=?uu3P&!W>exOG~B?@ zKzu@c)A=SRf?iqs3L?+SXXE1In2!)Z1YG&@3i6*hcgB*h(x?u6Z~)of-rh(5a8z_e zR9sSAeY~0z$SEi&`1JUv#U;hJ`fec(1pwgX?F9g+`BP2lzEZtj|9<)VPNuU;RE3PR zw6q)7Zy;N#RKB+5wOzTp$QW6nR9NzdhlgipXOSI11oYqO_hI{Jwc48FH8HDWW~4KN zLxU6J6LIlzj|E93(xN>@wI^zki%cdv5k#YzGNRRKZ(P5@<#L0Ag8={!A3p5t>^yts ztdl?hK*jzFI!@oYW9Qy|dp8Rx#ziP|x>+dTlEI>A$P7etQ!Rfe*iwlRt0RRLA2J!ejfn+kjAL7yUNPs&K!EzLp zx+V3{;X`h2Zg1tkRrPTdj^nnRXB0#wQ`^Pu(c#fTflw$Au6cP)T|*s&kV>YCj))vZ zjgOD(_4?QivD{#8Pj^pbROGS=A^}oXR+d_=Hhc2l%%4@5R#S9Tw3Dc?m0>Iv3#Z}X z5#jUm^R^tyQ%fN&9)EP4E(OxZ15qe?gB}22_b~_|AtB>qgsAsf63kw7Sp6vrBXQ(M5d5qV`FhT9vKz6Dtwh)j%kJhg93Nw?uGywj0ONe zWOSqf8fY|{=!i(8(U37R36Rbyv*(< z1R=cEe$8rd%YCT!bZu^4EX(ABjH7c|FI`^Z)=l|Jj+Dnd$52n=DL@ii~n~b+zS~mW;2TZ;CJl0%$ZE?MTKj z5+F6VG~fUCeXCm;+cP}eJs>ds#A5N-*qCj2Uw>ciiP}@Or_d#b!a z>xX65Szb|Yghum{aaa!kP+VL*H8W+k#7>?%$qD3GF`qVNiwF)0{`UH}mZLkVsSi~i za$h3!)~_>P`uqEv0}&o|9a5>?V7qW}aY2T= zhx^q1DYIXz)jDriOfO$~DU;5OPl$&Qj@=v6YPChhMYsEIpKm&E<~;SrC=xAJs4Xom zVXMNBO(Ky_*PmYDzQTE|Kp;5$;o%E^yKt`QT+N?r2!g;!80xFccCZW-2ykGP5P)~6>U_)I)Yg=pI?Y@e#iru-pU0q!}#2o^m;AbA`ybVIF zx4~er-Gp>d2O(^3ZMGy^t+LS2Q1lH0$8k5B8)|1Q`Ok8eiQC0IKJSyGpQsiVi$jf0 zQ}>s;iSdb_jU5~uR45dvuv9OYdW6Y|C%V;VJX}Y9965O4;L6aIn;(H6!q0yrpF}31 za>Y?pL16)o<4J;~#NQ-3;@C+!TK1-IEB0z?L9!r-6QtAY#2w<%k4=z`OVGE zA*V{ELNPdwpE_--w&oY+g~>v6bOeV4H=b+6-AsdsJH(bExLmHMrzf&I#2qoKWA60d ziHMA-I8gD~@z1DKDuqJf@p%Jx2Oh_4h})o0DC7z``ua4lnOE!83%UiVTq==Cb7j%g6wS>+dMrzgSbJ0WI_4<^6i=134-wQ z^78fbePSkUi+o#H7WsTWzxQ^pEk`bw=e?QdC|^s2DP=P=GXnzyMqos(rxhq<3I?$0 z>1nfjXlN)pGCDgudr$Em27>_s!~oXT)+Q2(WV5p86!Zcw@c#t>z<&U{^`%9!77N+{ O0000 () => { + settings.color = color; + save(settings); + showSettings(); + } + + E.showMenu({ + '': { 'title': 'Colors' }, + '< Back': showSettings, + 'White': saveColor(65535), + 'Red': saveColor(63488), + 'Yellow': saveColor(65504), + 'Cyan': saveColor(2047), + 'Green': saveColor(2016), + 'Blue': saveColor(31), + 'Black': saveColor(0), + }) + } + + function showSettings() { + E.showMenu({ + '': { 'title': 'Nifty B Clock' }, + '< Back': back, + 'Color': showColors, + }) + } + + showColors(); +}) From 383f07606d2a3592229bf6d645492db4bb2227b8 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:15:45 +1300 Subject: [PATCH 136/325] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 781a36434..e5577b0ce 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "speedclock", +{ "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple clock face with large digits hour above minutes.", + "description": "Simple 24h clock face with large digits hours above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.app.js","url":"app.js"}, + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"speedclock.json"} + {"name":"slomoclock.json"} ] }, { "id": "de-stress", From d3519e4290c2893f06f5a9d736ed4d93f1099776 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:23:41 +1300 Subject: [PATCH 137/325] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index e5577b0ce..781a36434 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "slomoclock", +{ "id": "speedclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple 24h clock face with large digits hours above minutes.", + "description": "Simple clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"speedclock.app.js","url":"app.js"}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"slomoclock.json"} + {"name":"speedclock.json"} ] }, { "id": "de-stress", From 5e2a740f850605031ee2e033901a52c86af2cc7b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:27:58 +1300 Subject: [PATCH 138/325] Create ChangeLog --- apps/slomoclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/slomoclock/ChangeLog diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/slomoclock/ChangeLog @@ -0,0 +1 @@ + From 8671ff9ae80944efc06bcff2c86d74c6ccf5da77 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:29:33 +1300 Subject: [PATCH 139/325] Update ChangeLog --- apps/slomoclock/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog index 8b1378917..c31405e08 100644 --- a/apps/slomoclock/ChangeLog +++ b/apps/slomoclock/ChangeLog @@ -1 +1 @@ - +0.01: Created app From 443afe923e761f0354158ef02c63e744c6ca4dbe Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:31:48 +1300 Subject: [PATCH 140/325] Create README.md --- apps/slomoclock/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 apps/slomoclock/README.md diff --git a/apps/slomoclock/README.md b/apps/slomoclock/README.md new file mode 100644 index 000000000..9a6bbbdd2 --- /dev/null +++ b/apps/slomoclock/README.md @@ -0,0 +1,6 @@ +# SloMo Clock + +Simple 24h clock with large digits. + +![](Screenshot.JPG) + From 5b3310f9e7f1bd2373853b4a437297d1f5bea2de Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:32:35 +1300 Subject: [PATCH 141/325] Create app-icon.js --- apps/slomoclock/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/slomoclock/app-icon.js diff --git a/apps/slomoclock/app-icon.js b/apps/slomoclock/app-icon.js new file mode 100644 index 000000000..22e264124 --- /dev/null +++ b/apps/slomoclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) From 189444f74008b5f2cd55311165cac95d1d28f8e8 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:33:23 +1300 Subject: [PATCH 142/325] Create app.js --- apps/slomoclock/app.js | 187 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 apps/slomoclock/app.js diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js new file mode 100644 index 000000000..28e9494f4 --- /dev/null +++ b/apps/slomoclock/app.js @@ -0,0 +1,187 @@ +/* +Simple watch [speedwatch] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ + +var v='0.01'; + +// timeout used to update every minute +var drawTimeout; +var x,y,w,h; + +// Variables for the stopwatch +var counter = -1; // Counts seconds +var oldDate = new Date(2020,0,1); // Initialize to a past date +var swInterval; // The interval's id +var B3 = 0; // Flag to track BTN3's current function +var w1; // watch id for BTN1 +var w3; // watch id for BTN3 + +// Colours +var colTime = 0x4FE0; +var colDate = 0xEFE0; +var colSW = 0x1DFD; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function stopWatch(clear) { + + x = 120; + y = 200; + w = 240; + h = 25; + + g.reset(); + g.setColor(colSW); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + if (clear) return; + + counter++; + + var hrs = Math.floor(counter/3600); + var mins = Math.floor((counter-hrs*3600)/60); + var secs = counter - mins*60 - hrs*3600; + + // When starting the stopwatch: + if (B3) { + // Set BTN3 to stop the stopwatch and bind itself to restart it: + w3=setWatch(() => {clearInterval(swInterval); + swInterval=undefined; + if (w3) {clearWatch(w3);w3=undefined;} + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 1;}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 0; // BTN3 is bound to stop the stopwatch + } + + // Bind BTN1 to call the reset function: + if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); + + // Draw elapsed time: + g.setFontAlign(0,1); + g.setFont("Vector24"); + + var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); + + if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; + + g.drawString(swStr, x, y, true); + +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; + } + + // Clear the stopwatch: + stopWatch(true); + + // Restore the date + drawDate(); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch + } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + + +function drawDate() { + // draw date + x = 120; + y = 200; + w = 240; + h = 25; + + g.reset(); + g.setColor(colDate); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + + var date = new Date(); +// var dateStr = require("locale").date(date,1); + var dateStr = date.getDate() + ' ' +require("locale").month(date,1); + g.setFontAlign(0,1); + g.setFont("Vector24"); + g.drawString(dateStr,x,y); + +} + + +function drawTime() { + x = 120; + y = 107; + w = 120; + h = 134; + + var date = new Date(); + var timeStr = require("locale").time(date,1); + g.reset(); + g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background + g.setFontAlign(0,0); + g.setFontVector(85); + g.setColor(colTime); + g.drawString(timeStr.substring(0,2),x,y-30); + g.drawString(timeStr.substring(3,5),x,y+38); +} + +function draw() { + x = g.getWidth()/2; + y = g.getHeight()/2; + g.reset(); + + drawTime(); + if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); + +// draw immediately at first, queue update +draw(); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Start stopwatch when BTN3 is pressed +setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +B3 = 1; // BTN3 is bound to start the stopwatch + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); From 85fd2b89673dd947d18d3503dd2488d1356e40d7 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:33:47 +1300 Subject: [PATCH 143/325] Add files via upload --- apps/slomoclock/watch.png | Bin 0 -> 1439 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/slomoclock/watch.png diff --git a/apps/slomoclock/watch.png b/apps/slomoclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 From cfbef5ae350a0ac8249a0a73f60768bcd6bd6cfa Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:35:33 +1300 Subject: [PATCH 144/325] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 781a36434..22f75982a 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "speedclock", +{ "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple clock face with large digits hour above minutes.", + "description": "Simple 24h clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.app.js","url":"app.js"}, + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"speedclock.json"} + {"name":"slomoclock.json"} ] }, { "id": "de-stress", From 3ff66912860887546c6c52e704042a5d1872091b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:36:23 +1300 Subject: [PATCH 145/325] Delete apps/speedclock directory --- apps/speedclock/ChangeLog | 1 - apps/speedclock/README.md | 21 ---- apps/speedclock/app-icon.js | 1 - apps/speedclock/app.js | 187 ------------------------------------ apps/speedclock/watch.png | Bin 1439 -> 0 bytes 5 files changed, 210 deletions(-) delete mode 100644 apps/speedclock/ChangeLog delete mode 100644 apps/speedclock/README.md delete mode 100644 apps/speedclock/app-icon.js delete mode 100644 apps/speedclock/app.js delete mode 100644 apps/speedclock/watch.png diff --git a/apps/speedclock/ChangeLog b/apps/speedclock/ChangeLog deleted file mode 100644 index c31405e08..000000000 --- a/apps/speedclock/ChangeLog +++ /dev/null @@ -1 +0,0 @@ -0.01: Created app diff --git a/apps/speedclock/README.md b/apps/speedclock/README.md deleted file mode 100644 index b4a3c83a7..000000000 --- a/apps/speedclock/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Morphing Clock Plus - -Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch. - -![](Screenshot.JPG) - -## Usage - -In addition to the Morphing Clock, a simple stopwatch can be started in the lower part of the display. - -BTN3 starts and stops the stopwatch. - -BTN1 resets/clears the stopwatch. - -## Requests - -Please leave bug reports and requests by raising an issue [here](https://github.com/skauertz/BangleApps). - -## Creator - -Sebastian Kauertz https://github.com/skauertz diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js deleted file mode 100644 index 22e264124..000000000 --- a/apps/speedclock/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js deleted file mode 100644 index 28e9494f4..000000000 --- a/apps/speedclock/app.js +++ /dev/null @@ -1,187 +0,0 @@ -/* -Simple watch [speedwatch] -Mike Bennett mike[at]kereru.com -0.01 : Initial -*/ - -var v='0.01'; - -// timeout used to update every minute -var drawTimeout; -var x,y,w,h; - -// Variables for the stopwatch -var counter = -1; // Counts seconds -var oldDate = new Date(2020,0,1); // Initialize to a past date -var swInterval; // The interval's id -var B3 = 0; // Flag to track BTN3's current function -var w1; // watch id for BTN1 -var w3; // watch id for BTN3 - -// Colours -var colTime = 0x4FE0; -var colDate = 0xEFE0; -var colSW = 0x1DFD; - -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -} - -function stopWatch(clear) { - - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colSW); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - if (clear) return; - - counter++; - - var hrs = Math.floor(counter/3600); - var mins = Math.floor((counter-hrs*3600)/60); - var secs = counter - mins*60 - hrs*3600; - - // When starting the stopwatch: - if (B3) { - // Set BTN3 to stop the stopwatch and bind itself to restart it: - w3=setWatch(() => {clearInterval(swInterval); - swInterval=undefined; - if (w3) {clearWatch(w3);w3=undefined;} - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 1;}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 0; // BTN3 is bound to stop the stopwatch - } - - // Bind BTN1 to call the reset function: - if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); - - // Draw elapsed time: - g.setFontAlign(0,1); - g.setFont("Vector24"); - - var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); - - if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; - - g.drawString(swStr, x, y, true); - -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Restore the date - drawDate(); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} -} - - -function drawDate() { - // draw date - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colDate); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - - var date = new Date(); -// var dateStr = require("locale").date(date,1); - var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(0,1); - g.setFont("Vector24"); - g.drawString(dateStr,x,y); - -} - - -function drawTime() { - x = 120; - y = 107; - w = 120; - h = 134; - - var date = new Date(); - var timeStr = require("locale").time(date,1); - g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); - g.setFontVector(85); - g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-30); - g.drawString(timeStr.substring(3,5),x,y+38); -} - -function draw() { - x = g.getWidth()/2; - y = g.getHeight()/2; - g.reset(); - - drawTime(); - if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. - - // queue draw in one minute - queueDraw(); -} - -// Clear the screen once, at startup -g.clear(); - -// draw immediately at first, queue update -draw(); - -// Stop updates when LCD is off, restart when on -Bangle.on('lcdPower',on=>{ - if (on) { - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - } -}); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); - -// Start stopwatch when BTN3 is pressed -setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -B3 = 1; // BTN3 is bound to start the stopwatch - -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); diff --git a/apps/speedclock/watch.png b/apps/speedclock/watch.png deleted file mode 100644 index b77f302d5291d39650ffd4c5400d0a7dd3e30d70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n From 91a5c56eeecede87d395fff331ea7707a34f47ee Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:55:48 +1300 Subject: [PATCH 146/325] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 28e9494f4..c2b504a75 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -1,5 +1,5 @@ /* -Simple watch [speedwatch] +Simple watch [slomoclock] Mike Bennett mike[at]kereru.com 0.01 : Initial */ From d415356bb750356de2359496bfec6993c21a418a Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 22:52:09 +1300 Subject: [PATCH 147/325] Update app.js --- apps/slomoclock/app.js | 43 +++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index c2b504a75..b4c6727bd 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -4,7 +4,7 @@ Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v='0.01'; +var v='0.02'; // timeout used to update every minute var drawTimeout; @@ -32,6 +32,7 @@ function queueDraw() { }, 60000 - (Date.now() % 60000)); } +/* function stopWatch(clear) { x = 120; @@ -108,22 +109,23 @@ function resetStopWatch() { if (w1) {clearWatch(w1);w1=undefined;} } +*/ function drawDate() { // draw date - x = 120; - y = 200; - w = 240; - h = 25; + x = 240; + y = 40; + w = 25; + h = 90; g.reset(); g.setColor(colDate); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + g.clearRect(x-w,y,x,y+h); // clear the background var date = new Date(); // var dateStr = require("locale").date(date,1); var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(0,1); + g.setFontAlign(1,1,3); g.setFont("Vector24"); g.drawString(dateStr,x,y); @@ -132,19 +134,30 @@ function drawDate() { function drawTime() { x = 120; - y = 107; - w = 120; - h = 134; + y = 120; + w = 130; + h = 160; var date = new Date(); var timeStr = require("locale").time(date,1); + var t = parseFloat(timeStr); + + if ( t < 24 ) colTime = 0x01BD; + if ( t < 19 ) colTime = 0x701F; + if ( t < 18 ) colTime = 0xEC80; + if ( t < 17 ) colTime = 0xF780; + if ( t < 12 ) colTime = 0xAEC2; + if ( t < 7 ) colTime = 0x1EC2; + if ( t < 6 ) colTime = 0x01BD; + + g.reset(); g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background g.setFontAlign(0,0); - g.setFontVector(85); + g.setFontVector(100); g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-30); - g.drawString(timeStr.substring(3,5),x,y+38); + g.drawString(timeStr.substring(0,2),x,y-35); + g.drawString(timeStr.substring(3,5),x,y+49); } function draw() { @@ -179,8 +192,8 @@ Bangle.on('lcdPower',on=>{ Bangle.setUI("clock"); // Start stopwatch when BTN3 is pressed -setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -B3 = 1; // BTN3 is bound to start the stopwatch +//setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +//B3 = 1; // BTN3 is bound to start the stopwatch // Load widgets Bangle.loadWidgets(); From 96109735716f1de861c7b57c648b6341c2ce1425 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 22:52:49 +1300 Subject: [PATCH 148/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 22f75982a..d251f4193 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.01", + "version":"0.02", "description": "Simple 24h clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", From c139651b4f70ec9b07910ebdd86df12098986e08 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 20:25:35 +0100 Subject: [PATCH 149/325] GPS Touch: swipe left, right to change display --- apps.json | 15 +++ apps/gpstouch/README.md | 5 + apps/gpstouch/geotools.js | 128 +++++++++++++++++++ apps/gpstouch/gpstouch.app.js | 222 +++++++++++++++++++++++++++++++++ apps/gpstouch/gpstouch.icon.js | 1 + apps/gpstouch/gpstouch.png | Bin 0 -> 1571 bytes 6 files changed, 371 insertions(+) create mode 100644 apps/gpstouch/README.md create mode 100644 apps/gpstouch/geotools.js create mode 100644 apps/gpstouch/gpstouch.app.js create mode 100644 apps/gpstouch/gpstouch.icon.js create mode 100644 apps/gpstouch/gpstouch.png diff --git a/apps.json b/apps.json index 51cca784b..b1ad57074 100644 --- a/apps.json +++ b/apps.json @@ -4037,5 +4037,20 @@ {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"vernierrespirate.json"}] + }, + { + "id": "gpstouch", + "name": "GPS Touch", + "version": "0.01", + "description": "A touch based GPS watch, shows OS map reference", + "icon": "gpstouch.png", + "tags": "tools,app", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"geotools","url":"geotools.js"}, + {"name":"gpstouch.app.js","url":"gpstouch.app.js"}, + {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true} + ] } ] diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md new file mode 100644 index 000000000..1d6bb5d17 --- /dev/null +++ b/apps/gpstouch/README.md @@ -0,0 +1,5 @@ +# GPS Touch + +## Screenshots + + diff --git a/apps/gpstouch/geotools.js b/apps/gpstouch/geotools.js new file mode 100644 index 000000000..5adc57872 --- /dev/null +++ b/apps/gpstouch/geotools.js @@ -0,0 +1,128 @@ +/** + * + * A module of Geo functions for use with gps fixes + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + * + */ + +Number.prototype.toRad = function() { return this*Math.PI/180; }; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */ +/* - www.movable-type.co.uk/scripts/gridref.js */ +/* - www.movable-type.co.uk/scripts/latlon-gridref.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +function OsGridRef(easting, northing) { + this.easting = 0|easting; + this.northing = 0|northing; +} +OsGridRef.latLongToOsGrid = function(point) { + var lat = point.lat.toRad(); + var lon = point.lon.toRad(); + + var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes + var F0 = 0.9996012717; // NatGrid scale factor on central meridian + var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W + var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres + var e2 = 1 - (b*b)/(a*a); // eccentricity squared + var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; + + var cosLat = Math.cos(lat), sinLat = Math.sin(lat); + var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature + var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature + var eta2 = nu/rho-1; + + var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0); + var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0); + var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0)); + var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0)); + var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + var cos3lat = cosLat*cosLat*cosLat; + var cos5lat = cos3lat*cosLat*cosLat; + var tan2lat = Math.tan(lat)*Math.tan(lat); + var tan4lat = tan2lat*tan2lat; + + var I = M + N0; + var II = (nu/2)*sinLat*cosLat; + var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2); + var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat); + var IV = nu*cosLat; + var V = (nu/6)*cos3lat*(nu/rho-tan2lat); + var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2); + + var dLon = lon-lon0; + var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon; + + var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6; + var E = E0 + IV*dLon + V*dLon3 + VI*dLon5; + + return new OsGridRef(E, N); +}; + +/* + * converts northing, easting to standard OS grid reference. + * + * [digits=10] - precision (10 digits = metres) + * to_map_ref(8, 651409, 313177); => 'TG 5140 1317' + * to_map_ref(0, 651409, 313177); => '651409,313177' + * + */ +function to_map_ref(digits, easting, northing) { + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + + let e = easting; + let n = northing; + + // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 + if (digits == 0) { + const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 }; + const ePad = e.toLocaleString('en', format); + const nPad = n.toLocaleString('en', format); + return `${ePad},${nPad}`; + } + + // get the 100km-grid indices + const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000); + + // translate those into numeric equivalents of the grid letters + let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5); + let l2 = (19 - n100km) * 5 % 25 + e100km % 5; + + // compensate for skipped 'I' and build grid letter-pairs + if (l1 > 7) l1++; + if (l2 > 7) l2++; + const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0)); + + // strip 100km-grid indices from easting & northing, and reduce precision + e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2)); + n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2)); + + // pad eastings & northings with leading zeros + e = e.toString().padStart(digits/2, '0'); + n = n.toString().padStart(digits/2, '0'); + + return `${letterPair} ${e} ${n}`; +} + +/** + * + * Module exports section, example code below + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + */ + +// get easting and northings +exports.gpsToOSGrid = function(gps_fix) { + return OsGridRef.latLongToOsGrid(gps_fix); +} + +// string with an OS Map grid reference +exports.gpsToOSMapRef = function(gps_fix) { + let os = OsGridRef.latLongToOsGrid(last_fix); + return to_map_ref(6, os.easting, os.northing); +} diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js new file mode 100644 index 000000000..3b2c54569 --- /dev/null +++ b/apps/gpstouch/gpstouch.app.js @@ -0,0 +1,222 @@ +const h = g.getHeight(); +const w = g.getWidth(); +let last_fix; + +function resetLastFix() { + last_fix = { + fix: 0, + alt: 0, + lat: 0, + lon: 0, + speed: 0, + time: 0, + course: 0, + satellites: 0 + }; +} + +function processFix(fix) { + last_fix.time = fix.time; + + if (fix.fix) { + if (!last_fix.fix) { + // we dont need to suppress this in quiet mode as it is user initiated + Bangle.buzz(); // buzz on first position + } + last_fix = fix; + } +} + +function draw() { + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + + g.reset(); + drawTop(d,hh,mm); + drawInfo(); +} + +function drawTop(d,hh,mm) { + g.setFont("Vector", w/3); + g.setFontAlign(0, 0); + g.setColor(g.theme.bg); + g.fillRect(0, 24, w, ((h-24)/2) + 24); + g.setColor(g.theme.fg); + + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 6, ((h-24)/4) + 24); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 6, ((h-24)/4) + 24); + + // for the colon + g.setFontAlign(0,0); // centre aligned + if (d.getSeconds()&1) g.drawString(":", w/2, ((h-24)/4) + 24); +} + +function drawInfo() { + if (infoData[infoMode] && infoData[infoMode].calc) { + g.setFont("Vector", w/7); + g.setFontAlign(0, 0); + + if (infoData[infoMode].get_color) + g.setColor(infoData[infoMode].get_color()); + else + g.setColor(g.theme.bgH); + g.fillRect(0, ((h-24)/2) + 24 + 1, w, h); + + if (infoData[infoMode].is_control) + g.setColor("#fff"); + else + g.setColor(g.theme.fg); + + g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); + } +} + +const infoData = { + ID_LAT: { + calc: () => 'Lat: ' + last_fix.lat.toFixed(4), + }, + ID_LON: { + calc: () => 'Lon: ' + last_fix.lon.toFixed(4), + }, + ID_SPEED: { + calc: () => 'Speed: ' + last_fix.speed.toFixed(1), + }, + ID_ALT: { + calc: () => 'Alt: ' + last_fix.alt.toFixed(0), + }, + ID_COURSE: { + calc: () => 'Course: '+ last_fix.course.toFixed(0), + }, + ID_SATS: { + calc: () => 'Satelites: ' + last_fix.satellites, + }, + ID_TIME: { + calc: () => formatTime(last_fix.time), + }, + OS_REF: { + calc: () => 'NZ 208 987', + }, + GPS_POWER: { + calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', + action: () => toggleGPS(), + get_color: () => Bangle.isGPSOn() ? '#f00' : '#00f', + is_control: true, + }, + GPS_LOGGER: { + calc: () => 'Logger ' + loggerStatus(), + action: () => toggleLogger(), + get_color: () => loggerStatus() == "ON" ? '#f00' : '#00f', + is_control: true, + }, +}; + +function toggleGPS() { + Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch'); + // add or remove listenner + if (Bangle.isGPSOn()) + Bangle.on('GPS', processFix); + else + Bangle.removeListener("GPS", processFix); + resetLastFix(); +} + +function loggerStatus() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return "Install"; + return settings.recording ? "ON" : "OFF"; +} + +function toggleLogger() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return; + + settings.recording = !settings.recording; + require("Storage").write("gpsrec.json", settings); + + if (WIDGETS["gpsrec"]) + WIDGETS["gpsrec"].reload(); + + // not sure if safe to register a listenner again + if (Bangle.isGPSOn()) + Bangle.on('GPS', processFix); +} + +function formatTime(now) { + try { + var fd = now.toUTCString().split(" "); + return fd[4]; + } catch (e) { + return "00:00:00"; + } +} + +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + +Bangle.on('swipe', dir => { + if (dir == 1) prevInfo() else nextInfo(); + draw(); +}); + +let prevTouch = 0; + +Bangle.on('touch', function(button, xy) { + let dur = 1000*(getTime() - prevTouch); + prevTouch = getTime(); + + if (dur <= 1000 && xy.y < h/2 && infoData[infoMode].is_control) { + Bangle.buzz(); + if (infoData[infoMode] && infoData[infoMode].action) { + infoData[infoMode].action(); + draw(); + } + } +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', on => { + if (secondInterval) + clearInterval(secondInterval); + secondInterval = undefined; + if (on) + secondInterval = setInterval(draw, 1000); + draw(); +}); + + +resetLastFix(); + +// add listenner if already powered on, plus tag app +if (Bangle.isGPSOn()) { + Bangle.setGPSPower(1, 'gpstouch'); + Bangle.on('GPS', processFix); +} + +g.clear(); +var secondInterval = setInterval(draw, 1000); +draw(); +// Show launcher when button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/gpstouch/gpstouch.icon.js b/apps/gpstouch/gpstouch.icon.js new file mode 100644 index 000000000..c4cf85676 --- /dev/null +++ b/apps/gpstouch/gpstouch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA=")) diff --git a/apps/gpstouch/gpstouch.png b/apps/gpstouch/gpstouch.png new file mode 100644 index 0000000000000000000000000000000000000000..c411356ae69347a83882fe195782df211aa629e9 GIT binary patch literal 1571 zcmV+;2Hg3HP)7!JOvVz*38;=4<&H5LVRE7i! zwE@)hpWulvKu;xEMTMGS7!-*xS1GdF+PWqHWMt@9fSV%xU>F?sdga$tN-6RC zc>^dn3?@fxJ#7!&a)Df>SSW(u?^pGJ*X^c8L{vPhdGzZM^83|YAaiF|hEwH1-av$*f)f8Y~qFARB^73HnR2V(_5xr7y^Cn!p2z7PP*%_}x)2Obf zP;<)4n!xDr40nTEmM?i{9J z%~@k5kQZyu%Y))#NKfyZBO?P=tbmy_6E;7m>%3)26G%{rT%EM6S> zm(4;1mMnqs;}bUf%y4L^%OFA1X#)Y3uN1-FHeH935*Ru(-v95`dt7R40${T1S%zi~ ziSxO87pkft9FDdt#Z0ePem0}abSE|pmd5_e)2GLaS4y$B<^+e1pP{Q;nXWrnws;OJ zUwaAZMUs^j;i#*NOZO^ZssPh{K!h2wwkcEM{XReR9o0uqJrobn)fMK;+VA<|tCMm5 zi8vDFN(L*Pcyz2GCnx&){(}$)zCN9lp!(Qp9<+y|Y-7fl%ashKAEDOPuBocmFDfA%F)r$N#l)Xawm>^XmdIt6zZhC{gI!hNv>I4rVDU57=DanXj1DWu*^Xjx}7r9)0c9bymH(Fe$;R6^qdI=sR%jT0++WDIjjJyKo`i zY2KU}Y!%hP&Te!&dbkFkMqBHF`h%j+z<*D9wANRq+vwc>g%Do8AgqY zw!b)g8gpmm(%fwaSFggJJtp?dz)Q7`-09fd@eM2eM+u0+sRe;YYe<;Prc4CuYpVuq#b8Qo_y)TrN zg%?t?&EBGEyl*$yb^+_wNl4Rp7wE9d(T&w*Wpc}IuvSo787wNV7 zk4FyJ2`nnvzNDbEGN_3n-#{kWRWNB1+eP@+YMfGBN=swC-Mj`eNfi10J|8G9mT*@W z%YjA9ioJUX_XXY Date: Sat, 23 Oct 2021 21:08:32 +0100 Subject: [PATCH 150/325] Added change log for GPS Touch --- apps/gpstouch/Changelog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/gpstouch/Changelog diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/Changelog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/gpstouch/Changelog @@ -0,0 +1 @@ +0.01: First version From 9c7f734afe35ea18ebea6507bde0e983a3417f06 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Sat, 23 Oct 2021 21:21:36 +0100 Subject: [PATCH 151/325] Add BETA thumbnailer code that can use the JS emulator to create thumbnails for Bangle.js 1 and 2 --- bin/thumbnailer.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++ core | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100755 bin/thumbnailer.js diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js new file mode 100755 index 000000000..922217fb2 --- /dev/null +++ b/bin/thumbnailer.js @@ -0,0 +1,84 @@ +#!/usr/bin/node + +var EMULATOR = "banglejs1"; + +var appId; + +if (process.argv.length!=3) { + console.log("USAGE:"); + console.log(" bin/thumbnailer.jd APP_ID"); + process.exit(1); +} +appId = process.argv[2]; +imageFn = "out.png"; + +if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { + console.log("You need to:"); + console.log(" git clone https://github.com/espruino/EspruinoWebIDE"); + console.log("At the same level as this project"); + process.exit(1); +} + +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emulator_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emu_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/common.js").toString()); + +var SETTINGS = { + pretokenise : true +}; +var Const = { +}; +module = undefined; +eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); +var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); +var app = apps.find(a=>a.id==appId); +if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); +if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + +jsRXCallback = function() {}; +jsUpdateGfx = function() {}; + +// wait until loaded... +setTimeout(function() { + console.log("Loaded..."); + jsInit(); + jsIdle(true); // not automatic + + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, settings : SETTINGS}).then(files => { + //console.log(files); + var command = "Bangle.factoryReset()\n"; + command += files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + //console.log(command); + console.log("Uploading..."); + jsTransmitString(command); + console.log("Done."); + jsIdle(); + jsIdle(); + jsIdle(); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + }); + }); +}); + + +}); diff --git a/core b/core index 3a2c706b4..8bbdf6992 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a +Subproject commit 8bbdf699210ab4d265a29a2bb0fd823cb5bca78a From dc3dda8235a5ba6892c671c124a149e4dd14e2bb Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 21:46:43 +0100 Subject: [PATCH 152/325] Added README.md files into apps.json --- apps.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps.json b/apps.json index b1ad57074..8ff29bdd3 100644 --- a/apps.json +++ b/apps.json @@ -458,6 +458,7 @@ "icon": "clock-analog.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -473,6 +474,7 @@ "icon": "clock2x3.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -503,6 +505,7 @@ "description": "T-Rex game in the style of Chrome's offline game", "icon": "trex.png", "tags": "game", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1093,6 +1096,7 @@ "icon": "icon.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1312,6 +1316,7 @@ "description": "A Flappy Bird game clone", "icon": "app.png", "tags": "game", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1386,6 +1391,7 @@ "icon": "bold_clock.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -3941,6 +3947,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -3956,6 +3963,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -4002,6 +4010,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ From f5fa689b42849522626040e782ae47476591f8a0 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 10:26:28 +1300 Subject: [PATCH 153/325] Update app.js --- apps/slomoclock/app.js | 208 ++++++++++------------------------------- 1 file changed, 47 insertions(+), 161 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index b4c6727bd..d9e0683c7 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -2,145 +2,41 @@ Simple watch [slomoclock] Mike Bennett mike[at]kereru.com 0.01 : Initial +0.03 : Use Layout library */ -var v='0.02'; +var v='0.03'; -// timeout used to update every minute -var drawTimeout; -var x,y,w,h; +var Layout = require("Layout"); +var layout = new Layout( { + type:"v", c: [ + {type:undefined, height:40 }, // Widgets top -// Variables for the stopwatch -var counter = -1; // Counts seconds -var oldDate = new Date(2020,0,1); // Initialize to a past date -var swInterval; // The interval's id -var B3 = 0; // Flag to track BTN3's current function -var w1; // watch id for BTN1 -var w3; // watch id for BTN3 + {type:"h", c: [ + {type:"v", c: [ + {type:"txt", font:"40%", label:"", id:"hour", valign:1}, + {type:"txt", font:"40%", label:"", id:"min", valign:-1}, + ]}, + {type:"v", c: [ + {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, + {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, + ]} + ]}, -// Colours -var colTime = 0x4FE0; -var colDate = 0xEFE0; -var colSW = 0x1DFD; + {type:undefined, height:40 }, // Widgets bottom -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -} - -/* -function stopWatch(clear) { - - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colSW); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - if (clear) return; + ] - counter++; +}, {lazy:true}); - var hrs = Math.floor(counter/3600); - var mins = Math.floor((counter-hrs*3600)/60); - var secs = counter - mins*60 - hrs*3600; - - // When starting the stopwatch: - if (B3) { - // Set BTN3 to stop the stopwatch and bind itself to restart it: - w3=setWatch(() => {clearInterval(swInterval); - swInterval=undefined; - if (w3) {clearWatch(w3);w3=undefined;} - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 1;}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 0; // BTN3 is bound to stop the stopwatch - } - - // Bind BTN1 to call the reset function: - if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); - - // Draw elapsed time: - g.setFontAlign(0,1); - g.setFont("Vector24"); - - var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); - - if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; - - g.drawString(swStr, x, y, true); - -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Restore the date - drawDate(); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} -} - -*/ - -function drawDate() { - // draw date - x = 240; - y = 40; - w = 25; - h = 90; - - g.reset(); - g.setColor(colDate); - g.clearRect(x-w,y,x,y+h); // clear the background - +// update the screen +function draw() { var date = new Date(); -// var dateStr = require("locale").date(date,1); - var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(1,1,3); - g.setFont("Vector24"); - g.drawString(dateStr,x,y); -} - - -function drawTime() { - x = 120; - y = 120; - w = 130; - h = 160; - - var date = new Date(); + // Update time var timeStr = require("locale").time(date,1); var t = parseFloat(timeStr); + var colTime; if ( t < 24 ) colTime = 0x01BD; if ( t < 19 ) colTime = 0x701F; @@ -148,53 +44,43 @@ function drawTime() { if ( t < 17 ) colTime = 0xF780; if ( t < 12 ) colTime = 0xAEC2; if ( t < 7 ) colTime = 0x1EC2; - if ( t < 6 ) colTime = 0x01BD; + if ( t < 6 ) colTime = 0x01BD; + + layout.hour.label = timeStr.substring(0,2); + layout.min.label = timeStr.substring(3,5); + layout.hour.col = colTime; + layout.min.col = colTime; - - g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); - g.setFontVector(100); - g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-35); - g.drawString(timeStr.substring(3,5),x,y+49); + // Update date + layout.day.label = date.getDate(); + layout.mon.label = require("locale").month(date,1); + + layout.render(); } -function draw() { - x = g.getWidth()/2; - y = g.getHeight()/2; - g.reset(); - - drawTime(); - if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. - - // queue draw in one minute - queueDraw(); -} - -// Clear the screen once, at startup -g.clear(); - -// draw immediately at first, queue update -draw(); +// Events // Stop updates when LCD is off, restart when on Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; if (on) { - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; + secondInterval = setInterval(draw, 10000); + draw(); // draw immediately } }); +var secondInterval = setInterval(draw, 10000); + + + +// update time and draw +g.clear(); +draw(); + // Show launcher when middle button pressed Bangle.setUI("clock"); -// Start stopwatch when BTN3 is pressed -//setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -//B3 = 1; // BTN3 is bound to start the stopwatch - // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); From 13dc813cc14bbad52d6b7ef93452b1c7677be06b Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 10:28:11 +1300 Subject: [PATCH 154/325] Update apps.json --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d251f4193..56c77d6e9 100644 --- a/apps.json +++ b/apps.json @@ -2920,8 +2920,8 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.02", - "description": "Simple 24h clock face with large digits hour above minutes.", + "version":"0.03", + "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", "allow_emulator":true, From bce96a00007af45e8af77e21c84eea59ee39297e Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 22:31:12 +0100 Subject: [PATCH 155/325] Updated README and added screenshots --- apps/gpstouch/README.md | 13 ++++++++++++- apps/gpstouch/gpstouch.app.js | 4 ++-- apps/gpstouch/screenshot1.png | Bin 0 -> 2627 bytes apps/gpstouch/screenshot2.png | Bin 0 -> 2555 bytes apps/gpstouch/screenshot3.png | Bin 0 -> 2474 bytes apps/gpstouch/screenshot4.png | Bin 0 -> 2750 bytes 6 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 apps/gpstouch/screenshot1.png create mode 100644 apps/gpstouch/screenshot2.png create mode 100644 apps/gpstouch/screenshot3.png create mode 100644 apps/gpstouch/screenshot4.png diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md index 1d6bb5d17..7329f9833 100644 --- a/apps/gpstouch/README.md +++ b/apps/gpstouch/README.md @@ -1,5 +1,16 @@ # GPS Touch +- A touch controlled GPS watch for Bangle JS 2 +- Key feature is the conversion of Lat/Lon into Ordinance Servey Grid Reference +- Swipe left and right to change the display +- Select GPS and switch the GPS On or Off by touching twice in the top half of the display +- Select LOGGER and switch the GPS Recorder On or Off by touching twice in the top half of the display +- Displays the GPS time in the bottom half of the screen when the GPS is powered on, otherwise 00:00:00 +- Select display of Course, Speed, Altitude, Longitude, Latitude, Ordinance Servey Grid Reference + ## Screenshots - +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 3b2c54569..9a022095d 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -1,5 +1,6 @@ const h = g.getHeight(); const w = g.getWidth(); +let geo = require("geotools"); let last_fix; function resetLastFix() { @@ -99,7 +100,7 @@ const infoData = { calc: () => formatTime(last_fix.time), }, OS_REF: { - calc: () => 'NZ 208 987', + calc: () => last_fix.lat == 0 ? "Searching.." : geo.gpsToOSMapRef(last_fix), }, GPS_POWER: { calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', @@ -204,7 +205,6 @@ Bangle.on('lcdPower', on => { draw(); }); - resetLastFix(); // add listenner if already powered on, plus tag app diff --git a/apps/gpstouch/screenshot1.png b/apps/gpstouch/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..03cb1e2a953dc8945c1c7dcacf0644992e5019e2 GIT binary patch literal 2627 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 literal 0 HcmV?d00001 From 81d34b22a4781cdef5f8c56e11f33f1ed66099b1 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:23:11 +1300 Subject: [PATCH 156/325] Create settings.js --- apps/slomoclock/settings.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/slomoclock/settings.js diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js new file mode 100644 index 000000000..0485400fe --- /dev/null +++ b/apps/slomoclock/settings.js @@ -0,0 +1,18 @@ +(function(back) { + + let settings = require('Storage').readJSON('slomoclock.json',1)||{}; + + function writeSettings() { + require('Storage').write('slomoclock.json',settings); + } + + const appMenu = { + '': {'title': 'SloMo Clock'}, + '< Back': back, + 'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }, + 'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + E.showMenu(appMenu); + +}); From a534b8c688b564899e798a4fd3a9d3aa32262543 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:24:30 +1300 Subject: [PATCH 157/325] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 56c77d6e9..e31fa73f7 100644 --- a/apps.json +++ b/apps.json @@ -2928,7 +2928,8 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, + {"name":"slomoclock.settings.js","url":"settings.js"} ], "data": [ {"name":"slomoclock.json"} From 55cf1a1d64a4e59a6367caa745bfdf4a7c0953fc Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:29:58 +1300 Subject: [PATCH 158/325] Update app.js --- apps/slomoclock/app.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index d9e0683c7..96a269b4f 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -7,10 +7,15 @@ Mike Bennett mike[at]kereru.com var v='0.03'; +// Read settings. +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.widTop = cfg.widTop==undefined?true:cfg.widTop; +cfg.widBot = cfg.widBot==undefined?true:cfg.widBot; + var Layout = require("Layout"); var layout = new Layout( { type:"v", c: [ - {type:undefined, height:40 }, // Widgets top + {type:undefined, height:widTop?40:0 }, // Widgets top {type:"h", c: [ {type:"v", c: [ @@ -23,7 +28,7 @@ var layout = new Layout( { ]} ]}, - {type:undefined, height:40 }, // Widgets bottom + {type:undefined, height:widBot?40:0 }, // Widgets bottom ] From 3ea42d0ab90df177e83fd66adb5eabc955cec6d7 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:59:42 +1300 Subject: [PATCH 159/325] Update app.js --- apps/slomoclock/app.js | 61 ++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 96a269b4f..271974b98 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,19 +5,38 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.03'; +var v='0.04'; -// Read settings. -let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; -cfg.widTop = cfg.widTop==undefined?true:cfg.widTop; -cfg.widBot = cfg.widBot==undefined?true:cfg.widBot; +// Colours +const col = []; +col[0]= 0x001F; +col[1]= 0x023F; +col[2]= 0x039F; +col[3]= 0x051F; +col[4]= 0x067F; +col[5]= 0x07FD; +col[6]= 0x07F6; +col[7]= 0x07EF; +col[8]= 0x07E8; +col[9]= 0x07E3; +col[10]= 0x07E0; +col[11]= 0x5FE0; +col[12]= 0x97E0; +col[13]= 0xCFE0; +col[14]= 0xFFE0; +col[15]= 0xFE60; +col[16]= 0xFC60; +col[17]= 0xFAA0; +col[18]= 0xF920; +col[19]= 0xF803; +col[20]= 0xF80E; +col[21]= 0xF817; +col[22]= 0xE81F; +col[23]= 0x801F; var Layout = require("Layout"); var layout = new Layout( { - type:"v", c: [ - {type:undefined, height:widTop?40:0 }, // Widgets top - - {type:"h", c: [ + type:"h", c: [ {type:"v", c: [ {type:"txt", font:"40%", label:"", id:"hour", valign:1}, {type:"txt", font:"40%", label:"", id:"min", valign:-1}, @@ -26,12 +45,7 @@ var layout = new Layout( { {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, ]} - ]}, - - {type:undefined, height:widBot?40:0 }, // Widgets bottom - - ] - + ] }, {lazy:true}); // update the screen @@ -40,21 +54,12 @@ function draw() { // Update time var timeStr = require("locale").time(date,1); - var t = parseFloat(timeStr); - var colTime; + var hh = parseFloat(timeStr.substring(0,2)); - if ( t < 24 ) colTime = 0x01BD; - if ( t < 19 ) colTime = 0x701F; - if ( t < 18 ) colTime = 0xEC80; - if ( t < 17 ) colTime = 0xF780; - if ( t < 12 ) colTime = 0xAEC2; - if ( t < 7 ) colTime = 0x1EC2; - if ( t < 6 ) colTime = 0x01BD; - layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = colTime; - layout.min.col = colTime; + layout.hour.col = col[hh]; + layout.min.col = col[hh]; // Update date layout.day.label = date.getDate(); @@ -77,8 +82,6 @@ Bangle.on('lcdPower',on=>{ var secondInterval = setInterval(draw, 10000); - - // update time and draw g.clear(); draw(); From 6de5ba92321f635aea29d17a6e8fe55bb1c62da0 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 13:00:59 +1300 Subject: [PATCH 160/325] Update apps.json --- apps.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index e31fa73f7..ffca3dfcd 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.03", + "version":"0.04", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", @@ -2928,8 +2928,7 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, - {"name":"slomoclock.settings.js","url":"settings.js"} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"slomoclock.json"} From e7e6b0db175a5281b102a38425f9c0ada0226c74 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:12:19 +1300 Subject: [PATCH 161/325] Update app.js --- apps/slomoclock/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 271974b98..96bcc08a0 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -30,9 +30,9 @@ col[17]= 0xFAA0; col[18]= 0xF920; col[19]= 0xF803; col[20]= 0xF80E; -col[21]= 0xF817; -col[22]= 0xE81F; -col[23]= 0x801F; +col[21]= 0x981F; +col[22]= 0x681F; +col[23]= 0x301F; var Layout = require("Layout"); var layout = new Layout( { From 3151c6b3a0da2797466addd5188ec455122c7d45 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:12:34 +1300 Subject: [PATCH 162/325] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 96bcc08a0..73ad7ba7d 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.04'; +var v='0.05'; // Colours const col = []; From ec65ca790b32c791f1487ef06a193e28abfd5f62 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:13:11 +1300 Subject: [PATCH 163/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ffca3dfcd..5ad1d046d 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.04", + "version":"0.05", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 5e068166e133b8c9d8ec5ae29031380d4839952d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sun, 24 Oct 2021 18:11:52 +0100 Subject: [PATCH 164/325] GPS touch, tweaks after real world testing --- apps.json | 2 +- apps/gpstouch/gpstouch.app.js | 50 ++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps.json b/apps.json index 8ff29bdd3..f67557bdd 100644 --- a/apps.json +++ b/apps.json @@ -3510,7 +3510,7 @@ "icon": "simplest.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"simplest.app.js","url":"app.js"}, diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 9a022095d..0425cdc23 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -2,6 +2,11 @@ const h = g.getHeight(); const w = g.getWidth(); let geo = require("geotools"); let last_fix; +let listennerCount = 0; + +function log_debug(o) { + //console.log(o); +} function resetLastFix() { last_fix = { @@ -18,11 +23,12 @@ function resetLastFix() { function processFix(fix) { last_fix.time = fix.time; - + log_debug(fix); + if (fix.fix) { if (!last_fix.fix) { // we dont need to suppress this in quiet mode as it is user initiated - Bangle.buzz(); // buzz on first position + Bangle.buzz(1500); // buzz on first position } last_fix = fix; } @@ -65,13 +71,13 @@ function drawInfo() { if (infoData[infoMode].get_color) g.setColor(infoData[infoMode].get_color()); else - g.setColor(g.theme.bgH); + g.setColor("#0ff"); g.fillRect(0, ((h-24)/2) + 24 + 1, w, h); if (infoData[infoMode].is_control) g.setColor("#fff"); else - g.setColor(g.theme.fg); + g.setColor("#000"); g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); } @@ -100,7 +106,7 @@ const infoData = { calc: () => formatTime(last_fix.time), }, OS_REF: { - calc: () => last_fix.lat == 0 ? "Searching.." : geo.gpsToOSMapRef(last_fix), + calc: () => !last_fix.fix ? "OO 000 000" : geo.gpsToOSMapRef(last_fix), }, GPS_POWER: { calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', @@ -117,12 +123,24 @@ const infoData = { }; function toggleGPS() { + if (loggerStatus() == "ON") + return; + Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch'); // add or remove listenner - if (Bangle.isGPSOn()) - Bangle.on('GPS', processFix); - else - Bangle.removeListener("GPS", processFix); + if (Bangle.isGPSOn()) { + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } + } else { + if (listennerCount > 0) { + Bangle.removeListener("GPS", processFix); + listennerCount--; + log_debug("listennerCount=" + listennerCount); + } + } resetLastFix(); } @@ -142,9 +160,11 @@ function toggleLogger() { if (WIDGETS["gpsrec"]) WIDGETS["gpsrec"].reload(); - // not sure if safe to register a listenner again - if (Bangle.isGPSOn()) + if (settings.recording && listennerCount == 0) { Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } } function formatTime(now) { @@ -208,9 +228,13 @@ Bangle.on('lcdPower', on => { resetLastFix(); // add listenner if already powered on, plus tag app -if (Bangle.isGPSOn()) { +if (Bangle.isGPSOn() || loggerStatus() == "ON") { Bangle.setGPSPower(1, 'gpstouch'); - Bangle.on('GPS', processFix); + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } } g.clear(); From 474d705c300b29be00ce91b5d3cfeef1542d9015 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 08:55:35 +1300 Subject: [PATCH 165/325] Update settings.js --- apps/slomoclock/settings.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index 0485400fe..abf767fdc 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -6,11 +6,29 @@ require('Storage').write('slomoclock.json',settings); } + function setColour(c) { + settings.colour = c; + writeSettings(); + } + const appMenu = { '': {'title': 'SloMo Clock'}, '< Back': back, - 'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }, - 'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + 'Colours' : function() { E.showMenu(colMenu); } + //,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); } + //,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Surprise' : function() { setColour(0); }, + 'Red' : function() { setColour(1); }, + 'Orange' : function() { setColour(2); }, + 'Yellow' : function() { setColour(3); }, + 'Green' : function() { setColour(4); }, + 'Blue' : function() { setColour(5); }, + 'Violet' : function() { setColour(6); } }; E.showMenu(appMenu); From 270e2f04733d9e2b25689dddc70b03939ebaf333 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:09:21 +1300 Subject: [PATCH 166/325] Update app.js --- apps/slomoclock/app.js | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 73ad7ba7d..8956f37b6 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,34 +5,42 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.05'; +var v='0.06'; // Colours const col = []; -col[0]= 0x001F; -col[1]= 0x023F; -col[2]= 0x039F; -col[3]= 0x051F; -col[4]= 0x067F; -col[5]= 0x07FD; -col[6]= 0x07F6; -col[7]= 0x07EF; -col[8]= 0x07E8; -col[9]= 0x07E3; -col[10]= 0x07E0; -col[11]= 0x5FE0; -col[12]= 0x97E0; -col[13]= 0xCFE0; -col[14]= 0xFFE0; -col[15]= 0xFE60; -col[16]= 0xFC60; -col[17]= 0xFAA0; -col[18]= 0xF920; -col[19]= 0xF803; -col[20]= 0xF80E; -col[21]= 0x981F; -col[22]= 0x681F; -col[23]= 0x301F; +col[1] = 0xF800; +col[2] = 0xFAE0; +col[3] = 0xF7E0; +col[4] = 0x4FE0; +col[5] = 0x019F; +col[6] = 0x681F; + +const colH = []; +colH[0]= 0x001F; +colH[1]= 0x023F; +colH[2]= 0x039F; +colH[3]= 0x051F; +colH[4]= 0x067F; +colH[5]= 0x07FD; +colH[6]= 0x07F6; +colH[7]= 0x07EF; +colH[8]= 0x07E8; +colH[9]= 0x07E3; +colH[10]= 0x07E0; +colH[11]= 0x5FE0; +colH[12]= 0x97E0; +colH[13]= 0xCFE0; +colH[14]= 0xFFE0; +colH[15]= 0xFE60; +colH[16]= 0xFC60; +colH[17]= 0xFAA0; +colH[18]= 0xF920; +colH[19]= 0xF803; +colH[20]= 0xF80E; +colH[21]= 0x981F; +colH[22]= 0x681F; +colH[23]= 0x301F; var Layout = require("Layout"); var layout = new Layout( { @@ -58,8 +66,8 @@ function draw() { layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = col[hh]; - layout.min.col = col[hh]; + layout.hour.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; // Update date layout.day.label = date.getDate(); @@ -82,6 +90,10 @@ Bangle.on('lcdPower',on=>{ var secondInterval = setInterval(draw, 10000); +// Configuration +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.colour = cfg.colour||0; // Colours + // update time and draw g.clear(); draw(); From 0c9d200eb7a88e95d60f2342f46e9f584f3d9294 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:10:29 +1300 Subject: [PATCH 167/325] Update apps.json --- apps.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 5ad1d046d..15dc96926 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.05", + "version":"0.06", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", @@ -2928,7 +2928,8 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, + {"name":"slomoclock.settings.js","url":"settings.js"} ], "data": [ {"name":"slomoclock.json"} From 44259c6e151ddfd376c94b43619c3403e7b53c6a Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:20:36 +1300 Subject: [PATCH 168/325] Update settings.js --- apps/speedalt2/settings.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 26ae010bb..96174a89b 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -37,12 +37,12 @@ '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, - 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, - 'Vibrate' : { - value : settings.buzz, - format : v => v?"On":"Off", - onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } - }*/ + 'Kalman Filter' : function() { E.showMenu(kalMenu); }, + 'Touch' : { + value : settings.touch, + format : v => v?"On":"Off", + onchange : () => { settings.touch = !settings.touch; writeSettings(); } + } }; const unitsMenu = { From 8d9966c2829300e2339a0a545f3f07d3663aed7c Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:25:06 +1300 Subject: [PATCH 169/325] Update app.js --- apps/speedalt2/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 6bd967b1b..0db9629c7 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.04'; +var v = '1.05'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -621,11 +621,13 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { + if ( ! cfg.touch ) return; if(dir == 1) prevScrn(); else nextScrn(); }); Bangle.on('touch', function(button){ + if ( ! cfg.touch ) return; nextFunc(0); // Same function as short BTN1 /* switch(button){ @@ -665,6 +667,7 @@ cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; +cfg.touch = cfg.touch==undefined?true:cfg.touch; if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); From 167e068a9c0d8387de7ff3477503900033be8338 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:26:02 +1300 Subject: [PATCH 170/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 15dc96926..a354cde39 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.04", + "version":"1.05", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 719dacb85f4ee968c024b641bb5e5babe544f6db Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:30:00 +1300 Subject: [PATCH 171/325] Update README.md --- apps/speedalt2/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 511932298..24d21cbef 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -22,6 +22,13 @@ BTN3 : Cycles the screens between Speed, Altitude, Distance to waypoint, Positio BTN3 : Long press exit and return to watch. +[Touch Screen] If the 'Touch' setting is ON then : + +Swipe Left/Right cycles between the five screens. + +Touch functions as BTN1 short press. + + ## App Settings Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). From 24da4c609289ef5b14afa7aa656fb8ab3c0fc0ff Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:39:58 +1300 Subject: [PATCH 172/325] Update README.md --- apps/speedalt2/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 24d21cbef..a39690338 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,10 +1,10 @@ # GPS Speed, Altimeter and Distance to Waypoint -What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports II] ? +What is the difference between **GPS Adventure Sports** and **GPS Adventure Sports II** ? -[GPS Adventure Sports] has 3 screens, each of which display different sets of information. +**GPS Adventure Sports** has 3 screens, each of which display different sets of information. -[GPS Adventure Sports II] has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. +**GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. In all other respect they perform the same functions. Use BTN3 or swipe left/right to cycle through the screens. @@ -12,17 +12,17 @@ The waypoints list is the same as that used with the [GPS Navigation](https://ba ## Buttons and Controls -BTN1 ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. +**BTN1** ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. -BTN1 ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. +**BTN1** ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. -BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. +**BTN2** : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. -BTN3 : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time +**BTN3** : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time -BTN3 : Long press exit and return to watch. +**BTN3** : Long press exit and return to watch. -[Touch Screen] If the 'Touch' setting is ON then : +**Touch Screen** If the 'Touch' setting is ON then : Swipe Left/Right cycles between the five screens. From 6bbec69ea994c8769948408d48aef3b3c2499673 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:41:05 +1300 Subject: [PATCH 173/325] Update README.md --- apps/speedalt2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index a39690338..30a706b7b 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -6,7 +6,7 @@ What is the difference between **GPS Adventure Sports** and **GPS Adventure Spor **GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. -In all other respect they perform the same functions. Use BTN3 or swipe left/right to cycle through the screens. +In all other respect they perform the same functions. The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. From c23b3ce8e533087cd9261e45f90c083aca666298 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 16:37:57 +1300 Subject: [PATCH 174/325] Update settings.js --- apps/slomoclock/settings.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index abf767fdc..e073dda7e 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -22,13 +22,15 @@ const colMenu = { '': {'title': 'Colours'}, '< Back': function() { E.showMenu(appMenu); }, + 'Mysterion' : function() { setColour(0); }, 'Surprise' : function() { setColour(0); }, - 'Red' : function() { setColour(1); }, - 'Orange' : function() { setColour(2); }, - 'Yellow' : function() { setColour(3); }, - 'Green' : function() { setColour(4); }, - 'Blue' : function() { setColour(5); }, - 'Violet' : function() { setColour(6); } + 'Red' : function() { setColour(2); }, + 'Orange' : function() { setColour(3); }, + 'Yellow' : function() { setColour(4); }, + 'Green' : function() { setColour(5); }, + 'Blue' : function() { setColour(6); }, + 'Violet' : function() { setColour(7); }, + 'White' : function() { setColour(8); }\ }; E.showMenu(appMenu); From 3c5986b9869d197a53bbd3c1cb38bd271c4a5e00 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:06:26 +1300 Subject: [PATCH 175/325] Update app.js --- apps/slomoclock/app.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 8956f37b6..6afecc1fa 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,16 +5,17 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.06'; +var v='0.07'; // Colours const col = []; -col[1] = 0xF800; -col[2] = 0xFAE0; -col[3] = 0xF7E0; -col[4] = 0x4FE0; -col[5] = 0x019F; -col[6] = 0x681F; +col[2] = 0xF800; +col[3] = 0xFAE0; +col[4] = 0xF7E0; +col[5] = 0x4FE0; +col[6] = 0x019F; +col[7] = 0x681F; +col[8] = 0xFFFF; const colH = []; colH[0]= 0x001F; @@ -42,6 +43,9 @@ colH[21]= 0x981F; colH[22]= 0x681F; colH[23]= 0x301F; +// Colour incremented with every 10 sec timer event +var colNum = 0; + var Layout = require("Layout"); var layout = new Layout( { type:"h", c: [ @@ -60,14 +64,19 @@ var layout = new Layout( { function draw() { var date = new Date(); + // Surprise colours + colNum = (colNum+256)%65536; + // Update time var timeStr = require("locale").time(date,1); var hh = parseFloat(timeStr.substring(0,2)); layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; + + // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From f826f9c2c47b171e57dc419120ddab90ed6e72fb Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:07:34 +1300 Subject: [PATCH 176/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a354cde39..874ae3ae1 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.06", + "version":"0.07", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 894676045954322c98bbf4dae81924119f9acb60 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:25:43 +1300 Subject: [PATCH 177/325] Update app.js --- apps/slomoclock/app.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 6afecc1fa..3bb67b0ca 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.07'; +var v='0.08'; // Colours const col = []; @@ -45,6 +45,7 @@ colH[23]= 0x301F; // Colour incremented with every 10 sec timer event var colNum = 0; +var lastMin = -1; var Layout = require("Layout"); var layout = new Layout( { @@ -64,19 +65,21 @@ var layout = new Layout( { function draw() { var date = new Date(); - // Surprise colours - colNum = (colNum+256)%65536; - // Update time var timeStr = require("locale").time(date,1); var hh = parseFloat(timeStr.substring(0,2)); + var mm = parseFloat(timeStr.substring(3,5)); + + // Surprise colours + if ( lastMin != mm ) colNum = Math.floor(Math.random() * 24); + lastMin = mm; layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. - layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum :col[cfg.colour]; + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From d70a7eef542ab45f73d8eaeacc02b59fec975302 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:26:24 +1300 Subject: [PATCH 178/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 874ae3ae1..6e393b8aa 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.07", + "version":"0.08", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 87c354e7b5572798ff4d579f30a804f3aa14315d Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:35:38 +1300 Subject: [PATCH 179/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 6e393b8aa..8aa251542 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.08", + "version":"0.09", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 9d456a2c728e0426b9a06fe228632095d1b4b215 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:44:49 +1300 Subject: [PATCH 180/325] Update settings.js --- apps/slomoclock/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index e073dda7e..af67069dc 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -23,14 +23,14 @@ '': {'title': 'Colours'}, '< Back': function() { E.showMenu(appMenu); }, 'Mysterion' : function() { setColour(0); }, - 'Surprise' : function() { setColour(0); }, + 'Surprise' : function() { setColour(1); }, 'Red' : function() { setColour(2); }, 'Orange' : function() { setColour(3); }, 'Yellow' : function() { setColour(4); }, 'Green' : function() { setColour(5); }, 'Blue' : function() { setColour(6); }, 'Violet' : function() { setColour(7); }, - 'White' : function() { setColour(8); }\ + 'White' : function() { setColour(8); } }; E.showMenu(appMenu); From 0f23695bc671653943f9aaea7c7425635c8200e7 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:45:31 +1300 Subject: [PATCH 181/325] Update app.js --- apps/slomoclock/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 3bb67b0ca..8d830907e 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -78,8 +78,8 @@ function draw() { layout.min.label = timeStr.substring(3,5); // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. - layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] :col[cfg.colour]; + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From c0f0ebbd60bed1d5a137b2204c4b0f3f531c2c92 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:45:50 +1300 Subject: [PATCH 182/325] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 8d830907e..e3933af1b 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.08'; +var v='0.10'; // Colours const col = []; From f3168a43aab918d343a7a34b5b256e72f574bfe7 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:46:30 +1300 Subject: [PATCH 183/325] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 8aa251542..8c792a687 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.09", + "version":"0.10", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 5eccc0ae0b3a5be62abd526067557b41ffe4e604 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:51:34 +1300 Subject: [PATCH 184/325] Update ChangeLog --- apps/slomoclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog index c31405e08..cfab5da55 100644 --- a/apps/slomoclock/ChangeLog +++ b/apps/slomoclock/ChangeLog @@ -1 +1,2 @@ 0.01: Created app +0.10: Different colour schemes selectable in SloMo Clock settings. From 1624bb5435b622302dfa4cad8ad42b8d95d9e7f3 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Fri, 22 Oct 2021 16:56:20 +0200 Subject: [PATCH 185/325] feat: add app for navigate between clock and launcher with Swipe action --- apps.json | 13 +++++++++++++ apps/swiperclocklaunch/ChangeLog | 1 + apps/swiperclocklaunch/boot.js | 17 +++++++++++++++++ apps/swiperclocklaunch/swiperclocklaunch.png | Bin 0 -> 889 bytes 4 files changed, 31 insertions(+) create mode 100644 apps/swiperclocklaunch/ChangeLog create mode 100644 apps/swiperclocklaunch/boot.js create mode 100644 apps/swiperclocklaunch/swiperclocklaunch.png diff --git a/apps.json b/apps.json index f187ff3d6..440257e3c 100644 --- a/apps.json +++ b/apps.json @@ -4101,5 +4101,18 @@ {"name":"gpstouch.app.js","url":"gpstouch.app.js"}, {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true} ] + }, + { + "id": "swiperclocklaunch", + "name": "Swiper Clock Launch", + "version": "0.01", + "description": "Navigate between clock and launcher with Swipe action", + "icon": "swiperclocklaunch.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"swiperclocklaunch.boot.js","url":"boot.js"} + ] } ] diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/swiperclocklaunch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js new file mode 100644 index 000000000..0bb8d588a --- /dev/null +++ b/apps/swiperclocklaunch/boot.js @@ -0,0 +1,17 @@ +// clock -> launcher +(function() { + var sui = Bangle.setUI; + Bangle.setUI = function(mode, cb) { + sui(mode,cb); + if (!mode.startsWith("clock")) return; + Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; + Bangle.on("swipe", Bangle.swipeHandler); + }; +})(); +// launcher -> clock +setTimeout(function() { + if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") { + Bangle.swipeHandler = dir => { if (dir>0) load(); }; + Bangle.on("swipe", Bangle.swipeHandler); + } +}, 10); \ No newline at end of file diff --git a/apps/swiperclocklaunch/swiperclocklaunch.png b/apps/swiperclocklaunch/swiperclocklaunch.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f16c2b18ed67170e425ec92af33da5da40a295 GIT binary patch literal 889 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkQ1DEEPlzj!X4rg$VbuYKH3v}xyxJbZy+<7uF2O5096dHv?yyLU|&ZykC33~1G^2T#N{9o5@; z>hklKT02ex)l{Fm@%+sjAme9bNl-ptoK!J3d9KezuImvO-+7QVE=9u}?IR}Rw{eoa~A#?;&0A)Me7rJ`|R zdEB3^{FytZZ0cgUaW!p{L-~xhg!5|G^n?;b|Kv3GPdy>jvZK4nhl542RpB)+hm)21 zStg6Q0)N$>{BxXOBDY#sP+-&3eo^l0{#=LnxlSm@-VQuvCnOS?EueW#`kGXG$mf%+ zPc*OAc=6tjK38Ao_}QXMZ-PeVr&I9@9(3(;I(S9!L|L z(^Qjt#7tP~=9}5?mmWB=$0Yt_<{7Q&Cuej03rd@+ZanX1q4Bn-X=~=Z-Iu?6+56e+ z_Hw_yH`#Yx*ynI&R{!gd-KR}A+@#^P^+|wD8o%o_ufVHeRf`v{-}3k2ryGBd&wt3i XqO?C%)!fY;ltVmS{an^LB{Ts5&i}k1 literal 0 HcmV?d00001 From 92cb0a5a0764b59f7a012a09208160f1e1495afe Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 25 Oct 2021 14:35:00 +0100 Subject: [PATCH 186/325] Add showscroller polyfill, message app tidying --- apps.json | 6 +- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 5 ++ apps/messages/app.js | 181 ++++++---------------------------------- 4 files changed, 35 insertions(+), 158 deletions(-) diff --git a/apps.json b/apps.json index f187ff3d6..27e1f92d5 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.32", + "version": "0.33", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -3248,7 +3248,7 @@ "data": [ {"name":"speedalt2.json"} ] - }, + }, { "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", @@ -3268,7 +3268,7 @@ "data": [ {"name":"slomoclock.json"} ] - }, + }, { "id": "de-stress", "name": "De-Stress", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 59d9e4c65..104afed63 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -34,3 +34,4 @@ 0.32: Fix single quote error in g.wrapString polyfill improve g.stringMetrics polyfill Fix issue where re-running bootupdate could disable existing polyfills +0.33: Add E.showScroller polyfill diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 269a80831..c0f494726 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -133,6 +133,11 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted +if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill + boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fgG).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), +k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; +} delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.imageMetrics=function(src) { diff --git a/apps/messages/app.js b/apps/messages/app.js index d369ee175..801434498 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -21,9 +21,6 @@ GB({"t":"notify-","id":1}) GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) GB({"t":"notify~","id":1,"title":"High St"}) -GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"}) -GB({"t":"notify~","id":1,"title":"0 yd - High St"}) -GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"}) */ @@ -97,140 +94,6 @@ function showMessage(msgid) { }); } -// Show a single menu item for the message -function showMessageMenuItem(y, idx) { - var msg = MESSAGES[idx]; - var W = g.getWidth(), H=48; - if (msg.new) g.setBgColor("#4F4"); - else g.setBgColor("#CFC"); - g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg); - var m = msg.title+"\n"+msg.body; - if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2); - if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2); - if (msg.body) { - g.setFontAlign(-1,-1).setFont("6x8"); - var l = g.wrapString(msg.body, W-14); - if (l.length>3) { - l = l.slice(0,3); - l[l.length-1]+="..."; - } - g.drawString(l.join("\n"), 12,y+20); - } -} -//test -//g.clear(1); showMessageMenuItem(MESSAGES[0],24) - -if (process.env.HWVERSION==1) { // Bangle.js 1 - showBigMenu = function(options) { - /* options = { - h = height - items = # of items - draw = function(y, idx) - onSelect = function(idx) - }*/ - var selected = 0; - var menuScroll = 0; - var menuShowing = false; - var w = g.getWidth(); - var h = g.getHeight(); - var m = w/2; - var n = Math.floor((h-48)/options.h); - - function drawMenu() { - g.reset(); - if (selected>=n+menuScroll) menuScroll = 1+selected-n; - if (selected=options.items) break; - var y = 24+i*options.h; - options.draw(y, idx); - // border for selected - if (i+menuScroll==selected) { - g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2); - } - } - // arrows - g.setColor(menuScroll ? g.theme.fg : g.theme.bg); - g.fillPoly([m,6,m-14,20,m+14,20]); - g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg); - g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); - } - g.clearRect(0,24,w-1,h-1); - drawMenu(); - Bangle.setUI("updown",dir=>{ - if (dir) { - selected += dir; - if (selected<0) selected = options.items-1; - if (selected>=options.items) selected = 0; - drawMenu(); - } else { - options.onSelect(selected); - } - }); - } -} else { // Bangle.js 2 - showBigMenu = function(options) { - /* options = { - h = height - items = # of items - draw = function(y, idx) - onSelect = function(idx) - }*/ - var menuScroll = 0; - var menuShowing = false; - var w = g.getWidth(); - var h = g.getHeight(); - var n = Math.ceil((h-24)/options.h); - var menuScrollMax = options.h*options.items - (h-24); - - function drawItem(i) { - var y = 24+i*options.h-menuScroll; - if (i<0 || i>=options.items || y<-options.h || y>=h) return; - options.draw(y, i); - } - - function drawMenu() { - g.reset().clearRect(0,24,w-1,h-1); - g.setClipRect(0,24,w-1,h-1); - for (var i=0;i{ - var dy = e.dy; - if (menuScroll - dy < 0) - dy = menuScroll; - if (menuScroll - dy > menuScrollMax) - dy = menuScroll - menuScrollMax; - if (!dy) return; - g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - g.scroll(0,dy); - menuScroll -= dy; - if (e.dy < 0) { - drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1); - if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2); - } else { - drawItem(Math.floor((menuScroll+24)/options.h)); - if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1); - } - g.setClipRect(0,0,w-1,h-1); - }; - Bangle.on('drag',Bangle.dragHandler); - Bangle.touchHandler = (_,e)=>{ - if (e.y<20) return; - var i = Math.floor((e.y+menuScroll-24) / options.h); - if (i>=0 && i { load() }); // we have >0 messages - // TODO: IF A NEW MESSAGE, SHOW IT + // If we have a new message, show it if (!forceShowMenu) { var newMessages = MESSAGES.filter(m=>m.new); if (newMessages.length) return showMessage(newMessages[0].id); } // Otherwise show a menu - var m = { - "":{title:"Messages"}, - "< Back": ()=>load() - }; - /*g.setFont("6x8"); - MESSAGES.forEach(msg=>{ - // "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents" - var title = g.wrapString(msg.title, g.getWidth())[0]; - m[title] = function() { - showMessage(msg.id); - } - }); - E.showMenu(m);*/ - showBigMenu({ + E.showScroller({ h : 48, - items : MESSAGES.length, - draw : showMessageMenuItem, - onSelect : idx => showMessage(MESSAGES[idx].id) + c : MESSAGES.length, + draw : function(idx, r) {"ram" + var msg = MESSAGES[idx-1]; + if (msg && msg.new) g.setBgColor("#4F4"); + else g.setBgColor((idx&1) ? "#CFC" : "#9F9"); + g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); + if (idx==0) msg = {title:"< Back"}; + var m = msg.title+"\n"+msg.body; + if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2); + if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2); + if (msg.body) { + g.setFontAlign(-1,-1).setFont("6x8"); + var l = g.wrapString(msg.body, r.w-14); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), r.x+12,r.y+20); + } + }, + select : idx => { + if (idx==0) load(); + else showMessage(MESSAGES[idx-1].id); + } }); } From 294729717d11c6faf8ff8f478f7ec1fe794e935e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 25 Oct 2021 15:30:52 +0100 Subject: [PATCH 187/325] Remove emoji that Android didn't like, use inline SVG --- css/main.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/css/main.css b/css/main.css index 44137e9b1..9b4bd3d84 100644 --- a/css/main.css +++ b/css/main.css @@ -39,8 +39,8 @@ .btn.btn-favourite:hover { color: red; } .icon.icon-emulator { text-indent: 0px; } /*override spectre*/ -.icon.icon-emulator::before { - content: "\01F5B5"; +.icon.icon-emulator { + content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E"); } .icon.icon-favourite { text-indent: 0px; } /*override spectre*/ .icon.icon-favourite::before { @@ -53,3 +53,4 @@ .icon.icon-interface::before { content: "\01F5AB"; } + From c8950400554bcf7ec282a282aa67a77301c9f048 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Tue, 26 Oct 2021 01:27:05 +0100 Subject: [PATCH 188/325] Nifty Clock B - Some code refactoring --- apps.json | 17 +++++++++ apps/ffcniftyb/ChangeLog | 1 + apps/ffcniftyb/app-icon.js | 2 +- apps/ffcniftyb/app.js | 58 +++++------------------------- apps/ffcniftyb/app.png | Bin 2188 -> 1218 bytes apps/ffcniftyb/settings.js | 71 ++++++++++++++++++------------------- 6 files changed, 63 insertions(+), 86 deletions(-) diff --git a/apps.json b/apps.json index 51cca784b..102def062 100644 --- a/apps.json +++ b/apps.json @@ -4009,6 +4009,23 @@ {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] }, + { + "id": "ffcniftyb", + "name": "Nifty-B Clock", + "version": "0.02", + "description": "A nifty clock (series B) with time, date and color configuration", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"ffcniftyb.app.js","url":"app.js"}, + {"name":"ffcniftyb.img","url":"app-icon.js","evaluate":true}, + {"name":"ffcniftyb.settings.js","url":"settings.js"} + ], + "data": [{"name":"ffcniftyb.json"}] + }, { "id": "stopwatch", "name": "Stopwatch Touch", diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index f6516c6de..dedd31452 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -1 +1,2 @@ 0.01: New Clock Nifty B +0.02: Added configuration \ No newline at end of file diff --git a/apps/ffcniftyb/app-icon.js b/apps/ffcniftyb/app-icon.js index f0a2393b1..1aac04351 100644 --- a/apps/ffcniftyb/app-icon.js +++ b/apps/ffcniftyb/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file +require("heatshrink").decompress(atob("mEwwkB/4A/AH4ARgMRBA3xBBIJCAYIFDAAYHGCAYJBDYQABj4PD+AXFCwgXGCAg9ECwwJBJQooGCxAXCIYQpBAgg9IC5yPCCw4XKBYIsFPwUBXQQXHAYREIF5ZEC+MfWQYXODQYTGC5ZDEOw0QMAIXMPggvSC44vRL5b8EAYIACC5i0FCwaOBC5C0DA4ZLCC5hfC/4DBIwwXKCInwgAWEKIwXJAA4XXCxYXCEwR2EgJeLR5LbCGRYXIAgzvKh7zGZg4XGIYisBA4JJCC6B5DAoYXWF6xfRC4fwAgMBC6cBU5I6CC5AECCo0QJwQXJaZJHMEYR1JC5QKBXo8QC4oCBAZAwHgKXBTQwSDBIKmGgJ3DEYheEA4ZfJKgkPdJQXHDAQWBC44eIC4QAMDA4A==")) \ No newline at end of file diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 60d76ff0a..b5fc94c32 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,7 +1,8 @@ -// setTimeout(load,100);Bangle.factoryReset(); -console.log('mem', process.memory().usage); const locale = require("locale"); -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; +const storage = require('Storage'); + +const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"]; +const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */; /* Clock *********************************************/ @@ -17,39 +18,10 @@ const center = { y: screen.height / 2, }; -const color = g.toColor(255, 0, 0); -console.log('color', color); - function d02(value) { return ('0' + value).substr(-2); } -// const c = E.compiledC(` -// // void xor(int, int, int) -// void xor(int len, int *dst, int *src){ -// len = len>>2; -// while (len--) { -// *dst ^= *src; -// dst++; -// src++; -// } -// } -// `); - -// function combineLayers(l1, l2) { -// // const l1ptr = E.getAddressOf(l1.buffer, true); -// // const l2ptr = E.getAddressOf(l2.buffer, true); -// // if (l1ptr && l2ptr) { -// // c.xor(l1.buffer.length, l1ptr, l2ptr); -// // } -// if (l1 && l1.buffer && l2 && l2.buffer) { -// for (let i = 0; i < l1.buffer.length; i++) { -// l1.buffer[i] ^= l2.buffer[i]; -// } -// } -// return l1; -// } - function renderEllipse(g) { g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); } @@ -78,15 +50,13 @@ function renderText(g) { g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); } +const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { + msb: true +}); + function draw() { - const s = new Date().getTime(); - console.log('mem.b', process.memory().usage); - let buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { - msb: true - }); - - let img = { + const img = { width: screen.width, height: screen.height, transparent: 0, @@ -108,12 +78,6 @@ function draw() { renderEllipse(buf.setColor(1)); renderText(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); - - buf = undefined; - img = undefined; - - console.log('mem.e', process.memory().usage); - console.log('draw', new Date().getTime() - s); } @@ -140,7 +104,6 @@ function startTick(run) { g.clear(); startTick(draw); -// Stop updates when LCD is off, restart when on Bangle.on('lcdPower', (on) => { if (on) { startTick(draw); @@ -149,10 +112,7 @@ Bangle.on('lcdPower', (on) => { } }); -// Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed Bangle.setUI("clock"); -console.log('mem', process.memory().usage); \ No newline at end of file diff --git a/apps/ffcniftyb/app.png b/apps/ffcniftyb/app.png index 1cd8a49b7ba951670fb8c007055a64f6d29dfc98..a6acf01213bece9c3692d0d075b0dd5f5c8eb6b4 100644 GIT binary patch literal 1218 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyacjS1AIbUfr6ue)*-Or5z`Z(9c3j!e!&c^OrMww*%&jwrSppw-(zPHo+k0* zAaC<6g{U^qnRCU3E}IBG&f{)ilzUm&!+*818f$*<`w;w9&%27tZIN_um(iqO zGLklG+M3@_-TiZP|Bj#MpX*<}6q69)utCHwSx4@r)tO>-qLTzV|`Zj+2@cq zD`glMm?}M8978;gKb=}yecM3b*nNwQcaQR!Z&CA=)+>MO6)hPiV{B~v^0R-DO0?e- z)8EzKZKjo8`nzOuX*t_Z_k;C3JTG_E-LzkPykx%JjNYRwrZP`?;Ndp!{DsEAg)Pa7 z9ErP6a7|}jcCPG~Igi7(oi8=hZNq0CiPX<#s%tP)o4wL2dWN1stDd5GjPp zL@$5;Qy<-rvNuAR7e7kwFMM#;Zk3xBSFtm1fvi1e$R*~UfZ9h-jx1|#|I}N{QsyJN zps+c<|7R- zuFz(&RR7lm)ql?GcOO__{^P<{`vW`VH$=aEzW&FoZAyDC^2&t1>3#V5PTDSGv&T34 zOI5kJYaeXg{vyb3{F%@a zZdZAIho9kG<*CrL<6+liN_=^@WX{$T$@~0w-i7c|&Wc-qV_6SedeYUg^mR+lb-sC) ze|4SILJ#baS-X0X-ZvND0;Zi4y?+<~W&W%b{hRfaZrEkp%~yjPd|Nm>tG^jeNl=*0 z#T3WD*6CHZM)B)Yb~&Gwjpuxyr5v9f-`H)d=i0C${8e~UlfKS-14pY?&th-8>3eH$ z*%-MBmWrj>UFY2S?AQjLCzH~wCi`0ir{21k`{K+|7Sq)i%5!&Lyl{HuGVzBqUnV8W zoqSh+NB2>vU6^5~mHeTkg^5B1yKN=Q&tFnvThFw#Ls_c!%;Kv3mb(9c{r$wAUUyke T)qzP6l+`?4{an^LB{Ts5zomlw delta 2187 zcmV;62z2+t35*et8Gix*007uvZqNV#2vbQ!K~z|U?O1zIQ&$>4xe0;XBtTpUk8%T9 zNDz4yovDfgC|d~uw1Ox*Wm%E7l!B!=LP4#Sl3LfHHe|fFaLb^yT9+;Uw_Vb&pF?B!3(^=bAn_W z&R{S|Boc&hetsT8=zM&4cXt4QPODo`FD!p9=nQ&&L%mEcQz#Up6Qc|U!x0n1u!A2Q z9R6{5Rz540OQ&Y0n$9=zc)aCQlfFIur~5y_|H1$b=-=C7^_taV<73bs-Y$H58Kii! zJWte~Fc1IW;eP`Np+qKOGMRSy-`)JqOqR$b5}8D$RiR6zN=d_MmgK6Bt2QQXM7EEQ zPf}7603bLdcvIpgGMSv3mda!@Jv=?uu3P&!W>exOG~B?@Kzu@c)A=SRf?iqs3L?+S zXXE1In2!)Z1YG&@3i6*hcgB*h(x?u6Z~)of-rh(5aDP;EL{wZ-Tz$Nn6UZqjDERdF zr^O}3xB6}&4g~<<Aq6EUjKgi`%b2_N>qi6w6wGv*KZ(OsZ_qU<+WY8 zyT}+>p;TD%hlht}XJ?TeKm_#P>Gxs#Xtmm!<25m>V`ii?gF}N8;}dc5agPN_CDNij zMYShtk$;O!COZ*CqnR?I)oE{Bzrp2lgM)(s01qEN?Ck73d*-Z@Kmb6+{t7xy-??Mw z-hF#F3pR5I&W@csa(G!BIv3#Z}X5#jUm^R^tyQ%fN& z9)EP4E(OxZ15qe?gB}22_b~_|AtB>qRi#on5=5quV`F1+IvyDnxhi~>U5;sn0)ql~=kA688jJ=2KxA~J0UBsDn&^l~ zqtTEtG6|5*Hl7_F83h1Tepp#rR!VoHzfHF~v=poDL}Sz#puy6!b=y`mzpT8>?j{5wyw-lrYH-Va zsP=SiZeA`y5ScqN>p!ozE5?*EgTY|srIM)Fli9T7R3EF(*_G4X(``vmsZ}3|KFWDL z$I*t#+Opc0-a({JowdI(WjIW<>iZBHNXfzt_NX9S{AT_r%-~ac0 zt6Le{Gd$cqATa&JV)5A6m~D7pe_!p1+EcZs&?Se%nVy~|ID}5Co1C1qor_-_|D&Vh zmxJ{_6cHJbk)DBJm`bgx`D2YnqjB_TcMtdM*R$DdwhGKxhks?(Szb|Y zghum{aaa!kP+VL*H8W+k#7>?%$qD3GF`qVNiwF)0{`UH}mZLkVsSi~ia(;L`U;6v|n*$LZbsbWv-C(OS?~|jSs1_EBLyb;T_m{ef z@rj>}9UL4~C={r$R4Y?p zL16)o<4J;~#NQ-3;@C+!TK1-IEB0z?L9!r-6QtAY#2w<%k4=z`OVGEA*V{E zLNPdwpE_--wtwaq=7q^ZbaVuV1UH^*#NAATh&#lVBDh?xr>7^fJH#C^t7Go;--(Ef zs5nsZ+40Y)R4Ro+;qiF`cLyHFY>3;SP$=XIIr{oEubEft)eE`>saz_NN#qK-LZOJ? z7;om^?7V4t21{fT^QBUyv}%Lu8tS%e-Gc0G8QVNPJ%59^L4ssK`Tp|lncE41@bdEV z_49pVCT)v+TUZwPd_KSTcCRf*E|=%Mndc~9ON1$9Gcz*-0|Q22M6IV4C}au-u<7Y( zvwLW0C^|AaJ3D(%@g4?)0RhAS*4EZ05{YE9vgZ`^0x$6Y1pvT*0K4_2MY0wP+5i9m N07*qoL () => { - settings.color = color; - save(settings); - showSettings(); + const saveColor = (color) => () => { + settings.color = color; + save(settings); + back(); + }; + + function showMenu(items, opt) { + items[''] = opt || {}; + items['< Back'] = back; + E.showMenu(items); + } + + showMenu( + Object.keys(colors).reduce((menu, color) => { + menu[colors[color]] = saveColor(color); + return menu; + }, {}), + { + title: 'Color', + selected: Object.keys(colors).indexOf(settings.color) } - - E.showMenu({ - '': { 'title': 'Colors' }, - '< Back': showSettings, - 'White': saveColor(65535), - 'Red': saveColor(63488), - 'Yellow': saveColor(65504), - 'Cyan': saveColor(2047), - 'Green': saveColor(2016), - 'Blue': saveColor(31), - 'Black': saveColor(0), - }) - } - - function showSettings() { - E.showMenu({ - '': { 'title': 'Nifty B Clock' }, - '< Back': back, - 'Color': showColors, - }) - } - - showColors(); -}) + ); +}); From 727c57241b7210adbd19df8d71c953ba6aed50d8 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Tue, 26 Oct 2021 01:45:26 +0100 Subject: [PATCH 189/325] Nifty Clock B - Added readme, screenshot and some small linting fix --- apps/ffcniftyb/README.md | 9 +++++++++ apps/ffcniftyb/app.js | 4 ++-- apps/ffcniftyb/screenshot.png | Bin 0 -> 4343 bytes 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 apps/ffcniftyb/README.md create mode 100644 apps/ffcniftyb/screenshot.png diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md new file mode 100644 index 000000000..e04243a0b --- /dev/null +++ b/apps/ffcniftyb/README.md @@ -0,0 +1,9 @@ +# Nifty Series B Clock + +- Display Time and Date +- Color Configuration + +## + +![](screenshot.png) + diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index b5fc94c32..58bf0b24b 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -68,13 +68,13 @@ function draw() { g.clearRect(0, 24, g.getWidth(), g.getHeight()); // render outside text with ellipse - buf.clear() + buf.clear(); renderText(buf.setColor(1)); renderEllipse(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); // render ellipse with inside text - buf.clear() + buf.clear(); renderEllipse(buf.setColor(1)); renderText(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); diff --git a/apps/ffcniftyb/screenshot.png b/apps/ffcniftyb/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f7d3fd678ea6cb11743065871efd2116df7e70aa GIT binary patch literal 4343 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 SJn-Y^*UZSuutDD|^?v}R)7t0& literal 0 HcmV?d00001 From 9cbcff08ac20efe55ceb6b5e45addc7f29397ebe Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 09:19:02 +0100 Subject: [PATCH 190/325] launchb2 0.04: Reduce code duplication and use new E.showScroller --- apps.json | 2 +- apps/launchb2/ChangeLog | 1 + apps/launchb2/app.js | 85 +++++++++++++---------------------------- 3 files changed, 29 insertions(+), 59 deletions(-) diff --git a/apps.json b/apps.json index 0136a744a..a0185c8db 100644 --- a/apps.json +++ b/apps.json @@ -114,7 +114,7 @@ "id": "launchb2", "name": "Launcher (Bangle.js 2 default)", "shortName": "Launcher", - "version": "0.03", + "version": "0.04", "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", "icon": "app.png", "type": "launch", diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog index a96ee84e1..a84587b7e 100644 --- a/apps/launchb2/ChangeLog +++ b/apps/launchb2/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fix occasional missed image when scrolling up 0.03: Text wrapping, better font +0.04: Reduce code duplication and use new E.showScroller diff --git a/apps/launchb2/app.js b/apps/launchb2/app.js index 371326498..8b66247c5 100644 --- a/apps/launchb2/app.js +++ b/apps/launchb2/app.js @@ -7,73 +7,42 @@ apps.sort((a,b)=>{ if (a.name>b.name) return 1; return 0; }); -var APPH = 64; -var menuScroll = 0; -var menuShowing = false; -var w = g.getWidth(); -var h = g.getHeight(); -var n = Math.ceil((h-24)/APPH); -var menuScrollMax = APPH*apps.length - (h-24); -// FIXME: not needed after 2v11 -var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; - apps.forEach(app=>{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); -if (g.wrapString) { // FIXME: check not needed after 2v11 +// FIXME: not needed after 2v11 +var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; +// FIXME: check not needed after 2v11 +if (g.wrapString) { g.setFont(font); apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n")); } -function drawApp(i) { - var y = 24+i*APPH-menuScroll; - var app = apps[i]; - if (!app || y<-APPH || y>=g.getHeight()) return; - g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32); - if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){} -} - -function drawMenu() { - g.reset().clearRect(0,24,w-1,h-1); - g.setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - for (var i=0;i{ - var dy = e.dy; - if (menuScroll - dy < 0) - dy = menuScroll; - if (menuScroll - dy > menuScrollMax) - dy = menuScroll - menuScrollMax; - if (!dy) return; - g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - g.scroll(0,dy); - menuScroll -= dy; - if (e.dy < 0) { - drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-1); - if (e.dy <= -APPH) drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-2); - } else { - drawApp(Math.floor((menuScroll+24)/APPH)); - if (e.dy >= APPH) drawApp(Math.floor((menuScroll+24)/APPH)+1); - } - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); -}); -Bangle.on("touch",(_,e)=>{ - if (e.y<20) return; - var i = Math.floor((e.y+menuScroll-24) / APPH); +function drawApp(i, r) { var app = apps[i]; if (!app) return; - if (!app.src || require("Storage").read(app.src)===undefined) { - E.showMessage("App Source\nNot found"); - setTimeout(drawMenu, 2000); - } else { - E.showMessage("Loading..."); - load(app.src); - } -}); + g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1); + g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32); + if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){} +} + +g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + +E.showScroller({ + h : 64, c : apps.length, + draw : drawApp, + select : i => { + var app = apps[i]; + if (!app) return; + if (!app.src || require("Storage").read(app.src)===undefined) { + E.showMessage("App Source\nNot found"); + setTimeout(drawMenu, 2000); + } else { + E.showMessage("Loading..."); + load(app.src); + } + } +}); From 481f3c7965746d75f2e9c69121ad90185e851af0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 09:19:13 +0100 Subject: [PATCH 191/325] include ID widget in bangle2 --- bin/firmwaremaker_c.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 2cb993d00..15092ced7 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -30,7 +30,7 @@ if (DEVICE=="BANGLEJS") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" + "about","alarm","widlock","widbat","widbt","widid" ]; } else { console.log("USAGE:"); From d0c2211727e4c0a487ba9063914a41b950920dc2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 10:23:39 +0100 Subject: [PATCH 192/325] boot 0.34: Use Storage.hash if available + Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) --- apps.json | 2 +- apps/boot/ChangeLog | 2 ++ apps/boot/bootupdate.js | 17 ++++++++++++++--- apps/setting/settings-icon.js | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index a0185c8db..83f08f47e 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.33", + "version": "0.34", "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/boot/ChangeLog b/apps/boot/ChangeLog index 104afed63..d3a241d7c 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -35,3 +35,5 @@ improve g.stringMetrics polyfill Fix issue where re-running bootupdate could disable existing polyfills 0.33: Add E.showScroller polyfill +0.34: Use Storage.hash if available + Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index c0f494726..c96cc8e83 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -5,15 +5,22 @@ E.showMessage("Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; var isB2 = process.env.HWVERSION; // Is Bangle.js 2 var boot = ""; -var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/)); -boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))!=${CRC}) { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; +if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed + var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/); + boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`; +} else { + var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/)); + boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`; +} +boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; boot += `E.setFlags({pretokenise:1});\n`; +boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; if (s.ble!==false) { if (s.HID) { // Human interface device if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));` else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`; - boot += `NRF.setServices({}, {uart:true, hid:Bangle.HID});\n`; + boot += `bleServiceOptions.hid=Bangle.HID;\n`; } } if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth @@ -178,12 +185,16 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } // Append *.boot.js files +// These could change bleServices/bleServiceOptions if needed require('Storage').list(/\.boot\.js/).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! boot += require('Storage').read(bootFile)+";\n"; }); +// update ble +boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; +// write file require('Storage').write('.boot0',boot); delete boot; E.showMessage("Reloading..."); diff --git a/apps/setting/settings-icon.js b/apps/setting/settings-icon.js index 7b68f80c0..abc7a3060 100644 --- a/apps/setting/settings-icon.js +++ b/apps/setting/settings-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ")) +require("heatshrink").decompress(atob("mEw4UA///+Nj5lCt9TH+cBqtVoALWqALTgoLUiALFgoLDqoBBAAQGCHAdRBYdFKwZECqv614ECGQQsCr2q1W1BYkVAoPqBYOrAoNUBYdXBQIAB6oLDEQgkEBYdaBYelBYt6BYetBYYvBtWq0EK1WpF4ZfBIwIFBJATCDBY6PDiuq1AEBlWqBdA7KKZZrLQZabNWZLLLcZb7LBYVV/WvAgRfCNYNRBAVVoq/FJQRECR4gnBEwQEBggLDGQg4CBag4DBaBWBBaoATA")) From cc821be6efc3a96b3af8d4fbee80533c4b9ec449 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 12:38:58 +0100 Subject: [PATCH 193/325] make running this less destructive --- bin/create_app_supports_field.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js index 6908591a5..b901b0448 100644 --- a/bin/create_app_supports_field.js +++ b/bin/create_app_supports_field.js @@ -31,6 +31,7 @@ try{ } apps = apps.map((app,appIdx) => { + if (app.supports) return app; // already sorted var tags = []; if (app.tags) tags = app.tags.split(",").map(t=>t.trim()); var supportsB1 = true; @@ -62,17 +63,21 @@ var KEY_ORDER = [ var JS = JSON.stringify; var json = "[\n "+apps.map(app=>{ - var keys = KEY_ORDER.filter(k=>k in app); + /*var keys = KEY_ORDER.filter(k=>k in app); Object.keys(app).forEach(k=>{ if (!KEY_ORDER.includes(k)) throw new Error(`Key named ${k} not known!`); - }); - + });*/ + var keys = Object.keys(app); // don't re-order return "{\n "+keys.map(k=>{ var js = JS(app[k]); - if (k=="storage") - js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + if (k=="storage") { + if (app.storage.length) + js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + else + js = "[]"; + } return JS(k)+": "+js; }).join(",\n ")+"\n }"; }).join(",\n ")+"\n]\n"; From 8b9d43deced6b325e4d1855c9817098275c9b0c3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 12:40:08 +0100 Subject: [PATCH 194/325] automatic thumbnailing --- bin/thumbnailer.js | 141 +++++++++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 42 deletions(-) diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index 922217fb2..db658b01e 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -2,15 +2,18 @@ var EMULATOR = "banglejs1"; -var appId; +var singleAppId; -if (process.argv.length!=3) { +if (process.argv.length!=3 && process.argv.length!=2) { console.log("USAGE:"); - console.log(" bin/thumbnailer.jd APP_ID"); + console.log(" bin/thumbnailer.js"); + console.log(" - all thumbnails"); + console.log(" bin/thumbnailer.js APP_ID"); + console.log(" - just one app"); process.exit(1); } -appId = process.argv[2]; -imageFn = "out.png"; +if (process.argv.length==3) + singleAppId = process.argv[2]; if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { console.log("You need to:"); @@ -33,52 +36,106 @@ eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toS eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); -var app = apps.find(a=>a.id==appId); -if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); -if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); +/* we factory reset ONCE, get this, then we can use it to reset +state quickly for each new app */ +var factoryFlashMemory = new Uint8Array(FLASH_SIZE); jsRXCallback = function() {}; jsUpdateGfx = function() {}; +function ERROR(s) { + console.error(s); + process.exit(1); +} + +function getThumbnail(appId, imageFn) { + console.log("Thumbnail for "+appId); + var app = apps.find(a=>a.id==appId); + if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); + if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + + return new Promise(resolve => { + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, settings : SETTINGS}).then(files => { + console.log("AppInfo returned");//, files); + flashMemory.set(factoryFlashMemory); + jsTransmitString("reset()\n"); + console.log("Uploading..."); + jsTransmitString("g.clear()\n"); + var command = files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + jsTransmitString(command); + console.log("Done."); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + var rgba32 = new Uint32Array(rgba.buffer); + var firstPixel = rgba32[0]; + var blankImage = rgba32.every(col=>col==firstPixel) + + if (!blankImage) { + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + resolve(true); + }); + }); + } else { + console.log("Image is empty"); + resolve(false); + } + + }); + }); +} + +var screenshots = []; + // wait until loaded... setTimeout(function() { console.log("Loaded..."); jsInit(); - jsIdle(true); // not automatic - - AppInfo.getFiles(app, { - fileGetter:function(url) { - console.log(__dirname+"/"+url); - return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, settings : SETTINGS}).then(files => { - //console.log(files); - var command = "Bangle.factoryReset()\n"; - command += files.map(f=>f.cmd).join("\n")+"\n"; - command += `load("${appId}.app.js")\n`; - //console.log(command); - console.log("Uploading..."); - jsTransmitString(command); - console.log("Done."); - jsIdle(); - jsIdle(); - jsIdle(); - jsStopIdle(); - - var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); - jsGetGfxContents(rgba); - - var Jimp = require("jimp"); - let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { - if (err) throw err; - let buffer = image.bitmap.data; - buffer.set(rgba); - image.write(imageFn, (err) => { - if (err) throw err; - console.log("Image written as "+imageFn); + jsIdle(); + console.log("Factory reset"); + jsTransmitString("Bangle.factoryReset()\n"); + factoryFlashMemory.set(flashMemory); + console.log("Ready!"); + + if (singleAppId) { + getThumbnail(singleAppId, "screenshots/"+singleAppId+".png") + + return; + } + + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom).map(app=>app.id); + // TODO: Work out about Bangle.js 1 or 2 + var promise = Promise.resolve(); + appList.forEach(appId => { + promise = promise.then(() => { + return getThumbnail(appId, "screenshots/"+appId+".png").then(ok => { + screenshots.push({ + id : appId, + url : "screenshots/"+appId+".png" }); }); -}); - - + }); + }); + + promise.then(function() { + console.log("Complete!"); + require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + }); + + }); From 0161b1a8d26dd98cc249fd631badec02663c1805 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 13:15:21 +0100 Subject: [PATCH 195/325] Added screenshot display in app list (automatic screenshots should follow soonish) --- README.md | 1 + apps.json | 104 ++++++++++++++++++------------- bin/create_app_supports_field.js | 19 ++++-- core | 2 +- css/main.css | 15 ++++- loader.js | 22 +++++++ 6 files changed, 113 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 3da301dba..d60d46fd3 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,7 @@ and which gives information about the app for the Launcher. "version": "0v01", // the version of this app "description": "...", // long description (can contain markdown) "icon": "icon.png", // icon in apps/ + "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app "type":"...", // optional(if app) - // 'app' - an application // 'widget' - a widget diff --git a/apps.json b/apps.json index c483de6e8..3a9925650 100644 --- a/apps.json +++ b/apps.json @@ -10,9 +10,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", "customConnect": true, - "storage": [ - - ], + "storage": [], "sortorder": -20 }, { @@ -65,7 +63,6 @@ ], "sortorder": -8 }, - { "id": "moonphase", "name": "Moonphase", @@ -456,10 +453,11 @@ "version": "0.15", "description": "An Analog Clock", "icon": "clock-analog.png", + "screenshots": [{"url":"screenshot_analog.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, @@ -472,10 +470,11 @@ "version": "0.05", "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", "icon": "clock2x3.png", + "screenshots": [{"url":"screenshot_pixel.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, @@ -504,9 +503,10 @@ "version": "0.04", "description": "T-Rex game in the style of Chrome's offline game", "icon": "trex.png", + "screenshots": [{"url":"screenshot_trex.png"}], "tags": "game", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"trex.app.js","url":"trex.js"}, @@ -691,6 +691,7 @@ "version": "0.10", "description": "Show Gadgetbridge weather report", "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], "tags": "widget,outdoors", "supports": ["BANGLEJS"], "readme": "readme.md", @@ -797,6 +798,7 @@ "version": "0.02", "description": "Show a warning when the battery runs low.", "icon": "widget.png", + "screenshots": [{"url":"screenshot.png"}], "type": "widget", "tags": "tool,battery", "supports": ["BANGLEJS"], @@ -876,15 +878,16 @@ {"name":"widhrm.wid.js","url":"widget.js"} ] }, - { "id": "bthrm", + { + "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", - "shortName":"BT HRM", - "version":"0.01", + "shortName": "BT HRM", + "version": "0.01", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", - "tags": "health,bluetooth", "type": "boot", - "supports" : ["BANGLEJS","BANGLEJS2"], + "tags": "health,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"bthrm.boot.js","url":"boot.js"}, @@ -1094,10 +1097,11 @@ "version": "0.03", "description": "A simple 7 segment Clock with date", "icon": "icon.png", + "screenshots": [{"url":"screenshot_s7segment.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"s7clk.app.js","url":"app.js"}, @@ -1315,9 +1319,10 @@ "version": "0.05", "description": "A Flappy Bird game clone", "icon": "app.png", + "screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}], "tags": "game", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"flappy.app.js","url":"app.js"}, @@ -1348,9 +1353,7 @@ "tags": "tool,outdoors,agps", "supports": ["BANGLEJS"], "custom": "custom.html", - "storage": [ - - ] + "storage": [] }, { "id": "pomodo", @@ -1389,10 +1392,11 @@ "version": "0.05", "description": "Simple, readable and practical clock", "icon": "bold_clock.png", + "screenshots": [{"url":"screenshot_bold.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"boldclk.app.js","url":"bold_clock.js"}, @@ -1626,6 +1630,7 @@ "version": "0.08", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], @@ -3229,45 +3234,43 @@ ], "data": [{"name":"speedalt.json"}] }, - { "id": "speedalt2", + { + "id": "speedalt2", "name": "GPS Adventure Sports II", - "shortName":"GPS Adv Sport II", - "icon": "app.png", - "version":"0.07", + "shortName": "GPS Adv Sport II", + "version": "0.07", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "icon": "app.png", + "type": "app", "tags": "tool,outdoors", "supports": ["BANGLEJS"], - "type":"app", - "allow_emulator":true, "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"speedalt2.app.js","url":"app.js"}, {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, {"name":"speedalt2.settings.js","url":"settings.js"} ], - "data": [ - {"name":"speedalt2.json"} - ] + "data": [{"name":"speedalt2.json"}] }, - { "id": "slomoclock", + { + "id": "slomoclock", "name": "SloMo Clock", - "shortName":"SloMo Clock", - "icon": "watch.png", - "version":"0.10", + "shortName": "SloMo Clock", + "version": "0.10", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", + "icon": "watch.png", + "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], - "type":"clock", - "allow_emulator":true, "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, {"name":"slomoclock.settings.js","url":"settings.js"} ], - "data": [ - {"name":"slomoclock.json"} - ] + "data": [{"name":"slomoclock.json"}] }, { "id": "de-stress", @@ -3579,6 +3582,7 @@ "version": "0.05", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}], "type": "app", "tags": "tools,bluetooth,gadgetbridge,music", "supports": ["BANGLEJS"], @@ -3652,6 +3656,7 @@ "version": "0.02", "description": "Automatically turn Quiet Mode on or off at set times", "icon": "app.png", + "screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}], "tags": "tool,widget", "supports": ["BANGLEJS"], "readme": "README.md", @@ -3702,6 +3707,7 @@ "version": "0.01", "description": "An Omnitrix Showpiece", "icon": "omnitrix.png", + "screenshots": [{"url":"screenshot.png"}], "tags": "game", "supports": ["BANGLEJS"], "readme": "README.md", @@ -3717,6 +3723,7 @@ "version": "0.02", "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", "icon": "bat-clock.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], @@ -3970,6 +3977,7 @@ "version": "0.02", "description": "A simple clock using the bold Anton font.", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3985,10 +3993,11 @@ "version": "0.02", "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, @@ -4001,10 +4010,11 @@ "version": "0.01", "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "icon": "app.png", + "screenshots": [{"url":"screenshot_floral.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"floralclk.app.js","url":"app.js"}, @@ -4017,6 +4027,7 @@ "version": "0.01", "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", "icon": "score.app.png", + "screenshots": [{"url":"screenshot_score.png"}], "type": "app", "tags": "", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4048,10 +4059,11 @@ "version": "0.01", "description": "A nifty clock with time and date", "icon": "app.png", + "screenshots": [{"url":"screenshot_nifty.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"ffcniftya.app.js","url":"app.js"}, @@ -4064,6 +4076,7 @@ "version": "0.02", "description": "A nifty clock (series B) with time, date and color configuration", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4081,6 +4094,7 @@ "version": "0.01", "description": "A touch based stop watch for Bangle JS 2", "icon": "stopwatch.png", + "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], "tags": "tools,app,b2", "supports": ["BANGLEJS2"], "readme": "README.md", @@ -4089,14 +4103,15 @@ {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} ] }, - { "id": "vernierrespirate", + { + "id": "vernierrespirate", "name": "Vernier Go Direct Respiration Belt", - "shortName":"Respiration Belt", - "version":"0.01", + "shortName": "Respiration Belt", + "version": "0.01", "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", "icon": "app.png", "tags": "health,bluetooth", - "supports" : ["BANGLEJS","BANGLEJS2"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"vernierrespirate.app.js","url":"app.js"}, @@ -4110,6 +4125,7 @@ "version": "0.01", "description": "A touch based GPS watch, shows OS map reference", "icon": "gpstouch.png", + "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}], "tags": "tools,app", "supports": ["BANGLEJS2"], "readme": "README.md", @@ -4127,7 +4143,7 @@ "icon": "swiperclocklaunch.png", "type": "boot", "tags": "system", - "supports": ["BANGLEJS", "BANGLEJS2"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"swiperclocklaunch.boot.js","url":"boot.js"} ] diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js index b901b0448..d6aada357 100644 --- a/bin/create_app_supports_field.js +++ b/bin/create_app_supports_field.js @@ -55,20 +55,31 @@ apps = apps.map((app,appIdx) => { return app; }); +// search for screenshots +apps = apps.map((app,appIdx) => { + if (app.screenshots) return app; // already sorted + + var files = require("fs").readdirSync(__dirname+"/../apps/"+app.id); + var screenshots = files.filter(fn=>fn.startsWith("screenshot") && fn.endsWith(".png")); + if (screenshots.length) + app.screenshots = screenshots.map(fn => ({url:fn})); + return app; +}); + var KEY_ORDER = [ - "id","name","shortName","version","description","icon","type","tags","supports", + "id","name","shortName","version","description","icon","screenshots","type","tags","supports", "dependencies", "readme", "custom", "customConnect", "interface", "allow_emulator", "storage", "data", "sortorder" ]; var JS = JSON.stringify; var json = "[\n "+apps.map(app=>{ - /*var keys = KEY_ORDER.filter(k=>k in app); + var keys = KEY_ORDER.filter(k=>k in app); Object.keys(app).forEach(k=>{ if (!KEY_ORDER.includes(k)) throw new Error(`Key named ${k} not known!`); - });*/ - var keys = Object.keys(app); // don't re-order + }); + //var keys = Object.keys(app); // don't re-order return "{\n "+keys.map(k=>{ var js = JS(app[k]); diff --git a/core b/core index 8bbdf6992..df02cd052 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 8bbdf699210ab4d265a29a2bb0fd823cb5bca78a +Subproject commit df02cd0529d81439fb859b553576398a445ef1b8 diff --git a/css/main.css b/css/main.css index 9b4bd3d84..60a760905 100644 --- a/css/main.css +++ b/css/main.css @@ -29,12 +29,26 @@ .sort-nav { float: right; } + +.app-tile { + position: relative; + border-bottom: 1px solid #EEE; + margin-bottom: 4px; +} + .tile-content { position: relative; } .link-github { position:absolute; top: 36px; left: -24px; } +.tile-screenshot { + position:absolute;bottom:1em;right:1em; + width:4em;height:4em; + padding:2px;border:1px solid black; + cursor:pointer; +} + .btn.btn-favourite { color: red; } .btn.btn-favourite:hover { color: red; } @@ -53,4 +67,3 @@ .icon.icon-interface::before { content: "\01F5AB"; } - diff --git a/loader.js b/loader.js index 45ec87df3..c4d8d5972 100644 --- a/loader.js +++ b/loader.js @@ -161,3 +161,25 @@ window.addEventListener('load', (event) => { }); }); }); + +function onAppJSONLoaded() { + return new Promise(resolve => { + httpGet("screenshots.json").then(screenshotJSON=>{ + var screenshots = []; + try { + screenshots = JSON.parse(screenshotJSON); + } catch(e) { + console.error("Screenshot JSON Corrupted", e); + } + screenshots.forEach(s => { + var app = appJSON.find(a=>a.id==s.id); + if (!app) return; + if (!app.screenshots) app.screenshots = []; + app.screenshots.push({url:s.url}); + }) + }).catch(err=>{ + console.log("No screenshots.json found"); + resolve(); + }); + }); +} From 762b1e6be31c5313b7e45852d0a796f54806a94e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 20:58:27 +0100 Subject: [PATCH 196/325] Bangle.js 2 welcome --- apps.json | 21 +++- apps/welcome2/ChangeLog | 17 +++ apps/welcome2/app-icon.js | 1 + apps/welcome2/app.js | 256 ++++++++++++++++++++++++++++++++++++++ apps/welcome2/app.png | Bin 0 -> 1939 bytes apps/welcome2/boot.js | 9 ++ apps/welcome2/settings.js | 18 +++ 7 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 apps/welcome2/ChangeLog create mode 100644 apps/welcome2/app-icon.js create mode 100644 apps/welcome2/app.js create mode 100644 apps/welcome2/app.png create mode 100644 apps/welcome2/boot.js create mode 100644 apps/welcome2/settings.js diff --git a/apps.json b/apps.json index 3a9925650..53478f638 100644 --- a/apps.json +++ b/apps.json @@ -183,7 +183,8 @@ }, { "id": "welcome", - "name": "Welcome", + "name": "Welcome (Bangle.js 1)", + "shortName": "Welcome", "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", @@ -216,6 +217,24 @@ ], "data": [{"name":"mywelcome.json"}] }, + { + "id": "welcome2", + "name": "Welcome (Bangle.js 2)", + "shortName": "Welcome", + "version": "0.13", + "description": "Appears at first boot and explains how to use Bangle.js 2", + "icon": "app.png", + "tags": "start,welcome", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"welcome2.boot.js","url":"boot.js"}, + {"name":"welcome2.app.js","url":"app.js"}, + {"name":"welcome2.settings.js","url":"settings.js"}, + {"name":"welcome2.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"welcome2.json"}] + }, { "id": "gbridge", "name": "Gadgetbridge", diff --git a/apps/welcome2/ChangeLog b/apps/welcome2/ChangeLog new file mode 100644 index 000000000..f72f77a4b --- /dev/null +++ b/apps/welcome2/ChangeLog @@ -0,0 +1,17 @@ +0.01: New App! +0.02: Animate balloon intro +0.03: BTN3 now won't restart when at the end +0.04: Fix regression after tweaks to Storage.readJSON +0.05: Move configuration into App/widget settings +0.06: Move loader into welcome.boot.js +0.07: Run again when updated + Don't run again when settings app is updated (or absent) + Add "Run Now" option to settings +0.08: Don't overwrite existing settings on app update +0.09: Allow welcome to run after a fresh install + More useful app menu + BTN2 now goes to menu on release +0.10: Tweaks to reduce memory usage +0.11: Fix initial screen fill colour +0.12: Fix swipe direction (#800) +0.13: Mods for Bangle.js 2 diff --git a/apps/welcome2/app-icon.js b/apps/welcome2/app-icon.js new file mode 100644 index 000000000..5c1373e17 --- /dev/null +++ b/apps/welcome2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/welcome2/app.js b/apps/welcome2/app.js new file mode 100644 index 000000000..d9a967d8a --- /dev/null +++ b/apps/welcome2/app.js @@ -0,0 +1,256 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var c = g.getColor(); + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + g.setColor(c); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(col, callback) { + var n = 0; + function f() {"ram" + g.setColor(col); + for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i); + g.flip(); + n++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var SCENE_COUNT=10; +function getScene(n) { + if (n==0) return function() { + g.reset().setBgColor(0).clearRect(0,0,176,176); + g.setFont("6x15"); + var n=0; + var l = Bangle.getLogo(); + var im = g.imageMetrics(l); + var i = setInterval(function() { + n+=0.1; + g.setColor(n,n,n); + g.drawImage(l,(176-im.width)/2,(176-im.height)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",44,104), 500); + setTimeout(()=>g.drawString("Hackable",44,116), 1000); + setTimeout(()=>g.drawString("Smart Watch",44,128), 1500); + } + },50); + }; + if (n==1) return function() { + var img = require("heatshrink").decompress(atob("ptR4n/j/4gH+8H5wl+jOukVVoHZ8dt/n//n37OtgH9sHhwHp4H5xmkGiH72MRje/LL/7iIAEE7sPEgoAC+AlagIlIiMQErPxDwUYxAABwIHCj8N7nOl3uEqa6BEggnFjfM5nCkUil3gEq5KDAAQmC6QmBE4JxSEhIABiQmB8QmSXoQlCYRMdEwIlCAAIlNhYlOiO85nNEyMPEoZwIAAcsYIYmPXoYlMiKaFExX/u9VEqLBBOYrCH+czmtVqJyDEpiaCOYsgSYszmc3qtTEqMR7hzG8AlGmd1OQglOOY6aEgYlCmmZoJMCTBrnD6SaIEoU/zOUuolSjbnBJgqaCEoU5zOXX4RyQYBBzCS4X5zNDqqZCJiERJg5zBEoVJEoM1JgYlQjhMHc4JLEmZMEEp6ZIJgPzS4WTmZMVTILmFYAK+BmglCmd1JgUYJiPNEorABEIOZygDBm5MCiJMQlhMH8ByBXwIlBJgUxJiMd5nOTIzlBTAK+BAANVq4jPAAS/HJgJyCTATAEACC/B4S/IJgIlCYAgAPiS/Kn5yEYANTEyPc5niOQxMB/LlCOapyJJgbpBYAZzROQK/Gl0ATIWfEoZzBc6IlB6SYGgBJBJgpzSlhyH8EAh5MBTIjnCuIlOjjlHTAJzC/LmDTSSYIEoTABOYIlETSKYHXwIABOYM0yYmETSCYHEobnDOYqaBExu8TAwlEc4U5EoiaCmK+NTAolFEwX0TQzBMXwXiEpTBCAAomNEoS+EEo4mIYIImKEoS+EEpDoBEyUbEo3gEo4mJdAImIJY4lJEycdEoPOOBYmPuIlE+HcJYhKKTZ1fhYkB2EAhnNcYMuEhomMr8A3YABEoJyB5gjOAAYmHm9VgELEoJMBEoXAEyXzE45YBJgXwEqx1I+ByDOYJyVJw5yCgEB3cQGgJMWJwQnCu6/CgFBigDB13S/glVAAf1qomCglEoADB1QDBADEPEoNVqEAolEgEKolKErJMDYAJMD0lE0AmaEoNaAgJMCFIYAahV/IgIiDOTgABNYJMEOToiCIoJMCOTzfCN4RMBOTxsDJIRyfIwZMBKQZzfJgRyfOYZMBOUBzCJgNKOT5zDJgLoCADxKBOAIABOT6aCAARyfOYRyjOYRyjOYlKEsBzEEsBzEOUJzDOUIABOUiaDOURzCOUZzCEscKCiY")); + var im = g.imageMetrics(img); + g.reset(); + g.setBgColor("#ff00ff"); + var y = 176, speed = 5; + function balloon(callback) { + y-=speed; + var x = (176-im.width)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>30) setTimeout(balloon,0,callback); + else callback(); + } + fade("#ff00ff", function() { + balloon(function() { + g.setColor(-1).setFont("6x15:2").setFontAlign(0,0); + g.drawString("Welcome.",88,130); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=4; + g.scroll(0,-4); + if (n>150) + clearInterval(i); + },20); + },3500); + + }; + if (n==2) return function() { + g.reset(); + g.setBgColor("#ffff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 70, y = 25, h=25; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has one",x,y+=h), + ()=>g.drawString("button",x,y+=h), + ()=>{g.setFont("12x20:2").setFontAlign(0,0,1).drawString("HERE!",150,88);} + ],200); + }; + if (n==3) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To wake the\nscreen up, or to\nselect", 88,60); + }; + if (n==4) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Long Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To go back to\nthe clock", 88,60); + }; + if (n==5) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFontAlign(0,0).setFont("12x20"); + g.drawString("If Bangle.js ever\nstops, hold the\nbutton for\nten seconds.\n\nBangle.js will\nthen reboot.", 88,78); + }; + if (n==6) return function() { + g.reset(); + g.setBgColor("#0000ff").setColor(-1).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -20, h=60; + animate([ + ()=>{g.drawString("Bangle.js has a\nfull touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("Drag up and down\nto scroll and\ntap to select",x,y+=h);}, + ],300); + }; + if (n==7) return function() { + g.reset(); + g.setBgColor("#00ff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -35, h=80; + animate([ + ()=>{g.drawString("Bangle.js comes\nwith a few\napps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit\nbanglejs.com/apps",x,y+=h);}, + ],400); + }; + if (n==8) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88; + g.drawString("You can also make\nyour own apps!",x,30); + g.drawString("Check out\nbanglejs.com",x,130); + + var rx = 0, ry = 0; + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [88 + 60*x/z, 78+ 60*y/z]; + } + + var a; + // draw a series of lines to make up our cube + var s = 30; + g.clearRect(88-s,78-s,88+s,78+s); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,-1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.moveTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.moveTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + } + + setInterval(draw,50); + }; + if (n==9) return function() { + g.reset(); + g.setBgColor("#ffffff");g.clear(); + g.setFontAlign(0,0); + g.setFont("12x20"); + + var x = 88, y = 10, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*2); + g.drawString("the button",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +} + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end + sceneNumber = (sceneNumber+dir)%SCENE_COUNT; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + getScene(sceneNumber)(); + if (sceneNumber>1) { + var l = SCENE_COUNT; + for (var i=0;i move(dir)); +setWatch(()=>{ + if (sceneNumber == SCENE_COUNT-1) + load(); + else + move(1); +}, BTN1, {repeat:true}); + +(function migrateSettings(){ + let global_settings = require('Storage').readJSON('setting.json', 1) + if (global_settings) { + delete global_settings.welcomed + require('Storage').write('setting.json', global_settings) + } +})() + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/welcome2/app.png b/apps/welcome2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ebbf254bd7c3546e8337c97648a7eb56747c81ac GIT binary patch literal 1939 zcmV;E2WP)^Vse?M*ABwfS z8MZ{CGrE{9Te8V!(51`XGM!;roJ%xW2E^g2NO{qXsim}(VOBaof>d5Dolx3xFQxb1 z-nZxMkEd_+KDRC9c8eL`e{OQldCvKM@9+H1@Ao`#6F2ey4asCOZNf#^U4iltNGVVP zD9{Fa2%!dFY=)EdIoSdkU1+ff<1#70}x@Hu6Kz{*xG?E@iS0CV6Z_UXTXX+c`#EfepLM z7bkj*!-~A7=*pg+Sbi*K%7RHf4giWO!Qq3`KOGl&fb97ejd1$m`W-Ff7CVP?6!NP@ z*#dZSJOwP6bwBxEm_^gcdS*L+X9eY#LFhPi?iv?)0Qct&s7(@2rVYtLc?9_Na7Wq| z*s!a7VL$mhO77$FB^z-&y=>gMfp-sY$1lBBpl3RC?8G{+#s}vNi$-#!6vBPB&)OMR zSFd&$bZt`bEWGtWzIxkQ4xXu@;q+gK#Us}SYaD|6$H%;%Ti;I9kp;}PGuy7fJm2Nj z3iFC{=TP>!hdI^J%>L8W066-F^;LCQ!M&eAbbP(vkK`q5{1_wcNTtxuZ2ySr8h}N& zRszc2md$8ii@03Y-n|s>8yCR?kc`--87pEZEdlP41PEPrX4^*;%A5{2vnS8zR7W$x z$ly8R&9b`gL{GM5xU>zNlg7h)0KF}J0vsa*DW~nsDFU8oDs=&cd09m`4HsuS|8}Go}cbc4m$MYKWCB5WaYg-fnm1w~?N@s{QIvQxc8y;|@aI_lzb}w~BisS# zemenx-!nLkY_L)4u$>fXwvAaIiTBWc+v}7`&~p6!uCs3uaov50-F-3g3k#a`wiTU zvP%9=C|wR)0Y87>{T;xe;}`am?ajxK)a*r5vJpf0+j$=?(bQEM4dHi_T)m(OP>_0! z1_+^4NFyyH<*{D@K^5csiY97XHdE-Io)Y4AJ18jdq6!;N6w__G$4oxaYSOXO>ixVd z+ws{6d|mcaH(IW=leb~(nby14&Nh=~ygVGcVjJW0xrjs(bVp2%hH_X~(1Taf-^c)x z7fdd^7$=di#^z*S%ALL$;a+Lr=xF3e6aY{@cU@z0T5E~G9qD5HeiuEVI9(y3DL9Gw z1yOPg`+ITpuxVa+DK3e)^{?OJ2V;=^WrSx4^Q0WKKXvUe>I+R(Rh1DhZFp7C$I`_l zlS*4#H@%Sr4uO?(LjPWs*V=<2)&jK*IPU`jS>z$Ju_P`2WvTIvXn zmH3zi^y9hR%brk`mjK)IQWZKoBLssHEiBBw=l9z94dC&#M}GJIl*eYU7cZ~QEIAx*jx#4V*6tXFt75D0`S3i z9}Y}9+K^{NLmzuXR~F6skih>$z;LX3{!6Pxw0a<^>ZsE89`|RJp-`2xV9n22`K4{uPal12c#LV76{gFJ8nP!- zaw36avkT_Uy!ZAWT)neo#k6g;jWdVG2vqd_X5^b_KQbcVPgUo7pa0hDpH9h{^Jkwc z-!McT8S3Bl{(_CIEp)Rdry0B}FR$L8xE8Y*}X}Yi3ly zimxsC2XF#(_RiV^WkXzj?IRg&AIPu(06{kcC_FmEycEFvEC@)5a|@6}ST)4#1`by) zyX#!0>t$TP%4Lh%RHy~~L+v8eWV#kifB-A0ZW?Y&s`=Rw5cL>u!8JF_b1TZrj!Z7V#$X$n89sf7V!{MeE?v?nqF**mb8fyw8z-vHP29u{ Z;y>o9o!*JS4-fzV002ovPDHLkV1gG+pke?3 literal 0 HcmV?d00001 diff --git a/apps/welcome2/boot.js b/apps/welcome2/boot.js new file mode 100644 index 000000000..07b7386f1 --- /dev/null +++ b/apps/welcome2/boot.js @@ -0,0 +1,9 @@ +(function() { + let s = require('Storage').readJSON('welcome2.json', 1) || {}; + if (!s.welcomed) { + setTimeout(() => { + require('Storage').write('welcome2.json', {welcomed: true}) + load('welcome2.app.js') + }) + } +})() diff --git a/apps/welcome2/settings.js b/apps/welcome2/settings.js new file mode 100644 index 000000000..d87cf4b55 --- /dev/null +++ b/apps/welcome2/settings.js @@ -0,0 +1,18 @@ +(function(back) { + let settings = require('Storage').readJSON('welcome2.json', 1) + || require('Storage').readJSON('setting.json', 1) || {} + E.showMenu({ + '': { 'title': 'Welcome App' }, + 'Run next boot': { + value: !settings.welcomed, + format: v => v ? 'Yes' : 'No', + onchange: v => require('Storage').write('welcome2.json', {welcomed: !v}), + }, + 'Run Now': () => load('welcome2.app.js'), + 'Turn off & run next': () => { + require('Storage').write('welcome2.json', {welcomed: false}); + Bangle.off(); + }, + '< Back': back, + }) +}) From 641cd01230947f706518064acd54fa55c4bf4da5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:42:54 +0100 Subject: [PATCH 197/325] Option to update device time automatically. And always fix it if time has obviously never been set --- core | 2 +- index.html | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core b/core index df02cd052..a7d82825d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit df02cd0529d81439fb859b553576398a445ef1b8 +Subproject commit a7d82825d3a43e1da7919591ed6fa776f1f0545a diff --git a/index.html b/index.html index 0185f1bae..e7c7c31cd 100644 --- a/index.html +++ b/index.html @@ -136,6 +136,10 @@ Pretokenise apps before upload (smaller, faster apps) +

From d771a6a732100a4cf0934faef1f58928292cb372 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:49:53 +0100 Subject: [PATCH 198/325] boot 0.35: Add Bangle.appRect polyfill Don't set beep vibration up on Bangle.js 2 (built in) settings 0.31: Remove Bangle 1 settings when running on Bangle 2 --- apps.json | 4 ++-- apps/boot/ChangeLog | 2 ++ apps/boot/bootupdate.js | 9 +++++++-- apps/setting/ChangeLog | 3 ++- apps/setting/settings.js | 16 +++++++++++----- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apps.json b/apps.json index 53478f638..d1ebe5249 100644 --- a/apps.json +++ b/apps.json @@ -16,7 +16,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.34", + "version": "0.35", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -272,7 +272,7 @@ { "id": "setting", "name": "Settings", - "version": "0.30", + "version": "0.31", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index d3a241d7c..6dc2a3577 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -37,3 +37,5 @@ 0.33: Add E.showScroller polyfill 0.34: Use Storage.hash if available Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) +0.35: Add Bangle.appRect polyfill + Don't set beep vibration up on Bangle.js 2 (built in) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index c96cc8e83..dfd745de2 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -3,7 +3,7 @@ recalculates, but this avoids us doing a whole bunch of reconfiguration most of the time. */ E.showMessage("Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; -var isB2 = process.env.HWVERSION; // Is Bangle.js 2 +var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 var boot = ""; if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/); @@ -55,7 +55,7 @@ boot += `E.setTimeZone(${s.timezone});`; if (!Bangle.F_BEEPSET) { if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n` if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n` - else if (s.beep=="vib") boot += `Bangle.beep = function (time, freq) { + else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) { return new Promise(function(resolve) { if ((0|freq)<=0) freq=4000; if ((0|time)<=0) time=200; @@ -182,6 +182,11 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } return lines; };\n`; +}; +delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted +if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares + boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); + (lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`; } // Append *.boot.js files diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 49915ee21..0890cf510 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -32,4 +32,5 @@ 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) 0.29: Add Customize to Theme menu -0.30: Move '< Back' to the top of menus \ No newline at end of file +0.30: Move '< Back' to the top of menus +0.31: Remove Bangle 1 settings when running on Bangle 2 diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a0e535df7..0c2930086 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,6 +1,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +const BANGLEJS2 = process.env.HWVERSION==2; const storage = require('Storage'); let settings; @@ -71,8 +72,8 @@ if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this alway const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { - var beepV = [false, true, "vib"]; - var beepN = ["Off", "Piezo", "Vibrate"]; + var beepV = BANGLEJS2 ? [false,true] : [false, true, "vib"]; + var beepN = BANGLEJS2 ? ["Off","On"] : ["Off", "Piezo", "Vibrate"]; const mainmenu = { '': { 'title': 'Settings' }, '< Back': ()=>load(), @@ -119,6 +120,7 @@ function showMainMenu() { 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, }; + return E.showMenu(mainmenu); } @@ -356,7 +358,10 @@ function showLCDMenu() { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } - }, + } + }; + if (!BANGLEJS2) + Object.assign(lcdMenu, { 'Wake on BTN2': { value: settings.options.wakeOnBTN2, format: boolFormat, @@ -372,7 +377,8 @@ function showLCDMenu() { settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; updateOptions(); } - }, + }}); + Object.assign(lcdMenu, { 'Wake on FaceUp': { value: settings.options.wakeOnFaceUp, format: boolFormat, @@ -427,7 +433,7 @@ function showLCDMenu() { updateOptions(); } } - } + }); return E.showMenu(lcdMenu) } function showQuietModeMenu() { From f87f0a19f44df0e75a5ffc64eb4f5dba4819ea3c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:50:16 +0100 Subject: [PATCH 199/325] Layout now uses appRect (should be fine with the new polyFill) --- modules/Layout.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index 319f6901e..8a5b0a0a5 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -80,7 +80,6 @@ function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; options = options || {}; this.lazy = options.lazy || false; @@ -131,7 +130,7 @@ function Layout(layout, options) { if (this.b[btn].cb) this.b[btn].cb(e); } // enough physical buttons - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -344,10 +343,6 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; - var l = this._l; - var w = g.getWidth(); - var y = this.yOffset; - var h = g.getHeight()-y; // update sizes function updateMin(l) {"ram" cb[l.type](l); @@ -396,18 +391,19 @@ Layout.prototype.update = function() { if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; } }; + + var l = this._l; updateMin(l); - // center - if (l.fillx || l.filly) { - l.w = w; - l.h = h; - l.x = 0; - l.y = y; - } else { + if (l.fillx || l.filly) { // fill all + l.w = Bangle.appRect.w; + l.h = Bangle.appRect.h; + l.x = Bangle.appRect.x; + l.y = Bangle.appRect.y; + } else { // or center l.w = l._w; l.h = l._h; - l.x = (w-l.w)>>1; - l.y = y+((h-l.h)>>1); + l.x = (Bangle.appRect.w-l.w)>>1; + l.y = Bangle.appRect.y+((Bangle.appRect.h-l.h)>>1); } // layout children this.layout(l); From 375cbf3166150a8d4d41d3e57cb80787495cde54 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:51:09 +0100 Subject: [PATCH 200/325] set minimum height for apps now we need room for the screenshot --- css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/main.css b/css/main.css index 60a760905..82f6fbcfe 100644 --- a/css/main.css +++ b/css/main.css @@ -34,6 +34,7 @@ position: relative; border-bottom: 1px solid #EEE; margin-bottom: 4px; + min-height: 8em; } .tile-content { position: relative; } From 9e404c625296d008314a547ff8e798767f65e426 Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 12:25:05 -0400 Subject: [PATCH 201/325] Customize --- README.md | 7 ++++--- index.html | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d60d46fd3..a620121b8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -Bangle.js App Loader (and Apps) +labarks' testing Bangle.js App Loader (and Apps) ================================ [![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) -* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try my **development version** at [github.io](https://labarks.github.io/BangleApps/) +* Try the official **release version** at [banglejs.com/apps](https://banglejs.com/apps) +* Try the official **development version** at [github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, diff --git a/index.html b/index.html index 0185f1bae..a5aa94e6a 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ - Bangle.js App Loader + labarks' testing Bangle.js App Loader
- + + From 6d72c4480dedb029b92bbac3dddf4e31d385765c Mon Sep 17 00:00:00 2001 From: qucchia Date: Wed, 27 Oct 2021 18:54:06 +0200 Subject: [PATCH 202/325] Add Q Alarm app --- apps.json | 19 +++ apps/qalarm/ChangeLog | 1 + apps/qalarm/app-icon.js | 1 + apps/qalarm/app.js | 278 +++++++++++++++++++++++++++++++++++++ apps/qalarm/app.png | Bin 0 -> 1531 bytes apps/qalarm/boot.js | 1 + apps/qalarm/qalarm.js | 157 +++++++++++++++++++++ apps/qalarm/qalarmcheck.js | 42 ++++++ apps/qalarm/widget.js | 22 +++ 9 files changed, 521 insertions(+) create mode 100644 apps/qalarm/ChangeLog create mode 100644 apps/qalarm/app-icon.js create mode 100644 apps/qalarm/app.js create mode 100644 apps/qalarm/app.png create mode 100644 apps/qalarm/boot.js create mode 100644 apps/qalarm/qalarm.js create mode 100644 apps/qalarm/qalarmcheck.js create mode 100644 apps/qalarm/widget.js diff --git a/apps.json b/apps.json index d1ebe5249..72de1a4ad 100644 --- a/apps.json +++ b/apps.json @@ -4166,5 +4166,24 @@ "storage": [ {"name":"swiperclocklaunch.boot.js","url":"boot.js"} ] + }, + { + "id": "qalarm", + "name": "Q Alarm and Timer", + "shortName": "Q Alarm", + "icon": "app.png", + "version": "0.01", + "description": "Alarm and timer app with days of week and 'hard' option.", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + { "name": "qalarm.app.js", "url": "app.js" }, + { "name": "qalarm.boot.js", "url": "boot.js" }, + { "name": "qalarm.js", "url": "qalarm.js" }, + { "name": "qalarmcheck.js", "url": "qalarmcheck.js" }, + { "name": "qalarm.img", "url": "app-icon.js", "evaluate": true }, + { "name": "qalarm.wid.js", "url": "widget.js" } + ], + "data": [{ "name": "qalarm.json" }] } ] diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog new file mode 100644 index 000000000..4022f485c --- /dev/null +++ b/apps/qalarm/ChangeLog @@ -0,0 +1 @@ +0.01: First version! diff --git a/apps/qalarm/app-icon.js b/apps/qalarm/app-icon.js new file mode 100644 index 000000000..1a014b796 --- /dev/null +++ b/apps/qalarm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("/wA/AH4A/AH4AF0WiF1wwtF73GB53MAAgkY4wABFqIxPEhQuXGB4vUFxYwMEpBpGBwouNGAwfFF5I1KF6ZQHGAwNLFx4wHF/4v/F/4v/AoYGDF6gaFF5AwHL7QuMBJQvWEpwvxBQ4uRGBAkJT4wuWGBIuIRjKRNF8wwXFy4wWFzIwU53NFzPN5wuR5/PGK4tBDYSNQ5wVCCwIzBAAQoIAAQWGSJ5HFDYYAQIYTCRKRIeBAAYmDAAZsJMCQAbeCAybFiQ0XFTQAIzgAGFcYvz0QAGF84wGF1AwFF1QA/AH4A/ADQ=")) diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js new file mode 100644 index 000000000..4d27739cf --- /dev/null +++ b/apps/qalarm/app.js @@ -0,0 +1,278 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; +/* +Alarm format: +{ + on : true, + t : 23400000, // Time of day since midnight in ms + msg : "Eat chocolate", // (optional) Must be set manually from the IDE + last : 0, // Last day of the month we alarmed on - so we don't alarm twice in one day! + rp : true, // Repeat + as : false, // Auto snooze + hard: true, // Whether the alarm will be like HardAlarm or not + timer : 300, // (optional) If set, this is a timer and it's the time in seconds + daysOfWeek: [true,true,true,true,true,true,true] // What days of the week the alarm is on. First item is Sunday, 2nd is Monday, etc. +} +*/ + +function formatTime(t) { + mins = 0 | (t / 60000) % 60; + hrs = 0 | (t / 3600000); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function formatTimer(t) { + mins = 0 | (t / 60) % 60; + hrs = 0 | (t / 3600); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function showMainMenu() { + const menu = { + "": { title: "Alarms" }, + "New Alarm": () => showEditAlarmMenu(-1), + "New Timer": () => showEditTimerMenu(-1), + }; + alarms.forEach((alarm, idx) => { + let txt = + (alarm.timer ? "TIMER " : "ALARM ") + + (alarm.on ? "on " : "off ") + + (alarm.timer ? formatTimer(alarm.timer) : formatTime(alarm.t)); + menu[txt] = function () { + if (alarm.timer) showEditTimerMenu(idx); + else showEditAlarmMenu(idx); + }; + }); + menu["< Back"] = () => { + load(); + }; + + if (WIDGETS["qalarm"]) WIDGETS["qalarm"].reload(); + return E.showMenu(menu); +} + +function showEditAlarmMenu(alarmIndex, alarm) { + const newAlarm = alarmIndex < 0; + + if (!alarm) { + if (newAlarm) { + alarm = { + t: 43200000, + on: true, + rp: true, + as: false, + hard: false, + daysOfWeek: new Array(7).fill(true), + }; + } else { + alarm = Object.assign({}, alarms[alarmIndex]); // Copy object in case we don't save it + } + } + + let hrs = 0 | (alarm.t / 3600000); + let mins = 0 | (alarm.t / 60000) % 60; + let secs = 0 | (alarm.t / 1000) % 60; + + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Repeat: { + value: alarm.rp, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.rp = v), + }, + "Auto snooze": { + value: alarm.as, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.as = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.hard = v), + }, + "Days of week": () => showDaysMenu(alarmIndex, getAlarm()), + }; + + function getAlarm() { + alarm.t = hrs * 3600000 + mins * 60000 + secs * 1000; + + alarm.last = 0; + // If alarm is for tomorrow not today (eg, in the past), set day + if (alarm.t < getCurrentTime()) alarm.last = new Date().getDate(); + + return alarm; + } + + menu["> Save"] = function () { + if (newAlarm) alarms.push(getAlarm()); + else alarms[alarmIndex] = getAlarm(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(alarmIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +function showDaysMenu(alarmIndex, alarm) { + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + "< Back": () => showEditAlarmMenu(alarmIndex, alarm), + }; + + [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ].forEach((dayOfWeek, i) => { + menu[dayOfWeek] = { + value: alarm.daysOfWeek[i], + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.daysOfWeek[i] = v), + }; + }); + + return E.showMenu(menu); +} + +function showEditTimerMenu(timerIndex) { + var newAlarm = timerIndex < 0; + + let alarm; + if (newAlarm) { + alarm = { + timer: 300, + on: true, + rp: false, + as: false, + hard: false, + }; + } else { + alarm = alarms[timerIndex]; + } + + let hrs = 0 | (alarm.timer / 3600); + let mins = 0 | (alarm.timer / 60) % 60; + let secs = (0 | alarm.timer) % 60; + + const menu = { + "": { title: "Timer" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.hard = v), + }, + }; + function getTimer() { + alarm.timer = hrs * 3600 + mins * 60 + secs; + alarm.t = (getCurrentTime() + alarm.timer * 1000) % 86400000; + return alarm; + } + menu["> Save"] = function () { + if (newAlarm) alarms.push(getTimer()); + else alarms[timerIndex] = getTimer(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(timerIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/qalarm/app.png b/apps/qalarm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..14edf415069c0df4b92316a58719ad356affa395 GIT binary patch literal 1531 zcmVYq`X4DteAXTyb(wC0gV-yH-YTe z9r4F1jgQS2-fWq+5-C*qSHNTeP!AlFr5wu>OcT`&j4n!kSF8cp3|dz)A+!!*v$n=2 z71eHFw5Eyne3zFwj&K~P=OXh;7vQkSn{D;UO%@R4ux|1@dp_%`BcG>PkCDH!z0yt0 zBg$d#ViqX)*_$mY6bH}1OwV|K!1-yE@i;8@iQNOcuzK=&H0sWHb0F8VUaqGC@`C_~ z$I*ko0AZ>pJsxm=#FOs~*uQkM%zI;N%JyjKOvmK;D@kSmW06;gfmE>nF@0NP>%Y|{n+k(KIxO% z>>^k?v0Tx7h%ot+iwFJeRPKEVHo#DR zL|bcSVHmrC+MA+QIXZEiMp>9UREcp#1!(KA(HPkSz{CMjs~rE3i_DeBZ(Qilu-^jo z&~pjm<_S7}OOid`pLsZlj)9>Mo~7HqPafKmDQ6lfrvxA|VmFC~BtvII*BR*i6EY{k zx*8f^m&r&N(g3wnA(jM4VxHU$%r56vb_KL8-iTSdgW>aqVfTZ?dX-iOWY0nF667v} za|5glXa<}dI9>i=MjVV9FcM%Uz^sMHD5x3j5;hB-sM$U^3rI{+f} z5WN#3bx<=B%sPl9+*OhYh}pYh+6`)hTJGG+fPDpWSHZdh+5f<5FU7(tN3nbXR*APHKJN}X*n5s|1Nig;_LHaIE>+HIJeMzkHI9XJPi3OI#u zg!9=G!H2cb3+NBqS0)^%#6y`O=%P4D&7{qOk{{g3Sz02%OoB#j-002ovPDHLkV1h(U(Ov)m literal 0 HcmV?d00001 diff --git a/apps/qalarm/boot.js b/apps/qalarm/boot.js new file mode 100644 index 000000000..6713ad9e1 --- /dev/null +++ b/apps/qalarm/boot.js @@ -0,0 +1 @@ +eval(require("Storage").read("qalarmcheck.js")); diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js new file mode 100644 index 000000000..38571987e --- /dev/null +++ b/apps/qalarm/qalarm.js @@ -0,0 +1,157 @@ +// This file shows the alarm + +print("Starting alarm"); + +function formatTime(t) { + let hrs = Math.floor(t / 3600000); + let mins = Math.round((t / 60000) % 60); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +function getRandomFromRange( + lowerRangeMin, + lowerRangeMax, + higherRangeMin, + higherRangeMax +) { + let lowerRange = lowerRangeMax - lowerRangeMin; + let higherRange = higherRangeMax - higherRangeMin; + let fullRange = lowerRange + higherRange; + let randomNum = getRandomInt(fullRange); + if (randomNum <= lowerRangeMax - lowerRangeMin) { + return randomNum + lowerRangeMin; + } else { + return randomNum + (higherRangeMin - lowerRangeMax); + } +} + +function showNumberPicker(currentGuess, randomNum) { + if (currentGuess == randomNum) { + E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum); + } else { + E.showMessage("" + currentGuess, "Get to " + randomNum); + } +} + +function showPrompt(msg, buzzCount, alarm) { + E.showPrompt(msg, { + title: alarm.timer ? "TIMER!" : "ALARM!", + buttons: { Sleep: true, Ok: false }, // default is sleep so it'll come back in 10 mins + }).then(function (sleep) { + buzzCount = 0; + if (sleep) { + if (alarm.ohr === undefined) alarm.ohr = alarm.t; + alarm.t += 10 / 60; // 10 minutes + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } else { + alarm.last = new Date().getDate(); + if (alarm.ohr !== undefined) { + alarm.t = alarm.ohr; + delete alarm.ohr; + } + if (!alarm.rp) alarm.on = false; + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } + }); +} + +function showAlarm(alarm) { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) return; // total silence + let msg = formatTime(alarm.t); + let buzzCount = 20; + if (alarm.msg) msg += "\n" + alarm.msg + "!"; + + if (alarm.hard) { + let okClicked = false; + let currentGuess = 10; + let randomNum = getRandomFromRange(0, 7, 13, 20); + showNumberPicker(currentGuess, randomNum); + setWatch( + (o) => { + if (!okClicked && currentGuess < 20) { + currentGuess = currentGuess + 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN1, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (currentGuess == randomNum) { + okClicked = true; + showPrompt(msg, buzzCount, alarm); + } + }, + BTN2, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (!okClicked && currentGuess > 0) { + currentGuess = currentGuess - 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN3, + { repeat: true, edge: "rising" } + ); + } else { + showPrompt(msg, buzzCount, alarm); + } + + function buzz() { + Bangle.buzz(500).then(() => { + setTimeout(() => { + Bangle.buzz(500).then(function () { + setTimeout(() => { + Bangle.buzz(2000).then(function () { + if (buzzCount--) setTimeout(buzz, 2000); + else if (alarm.as) { + // auto-snooze + buzzCount = 20; + setTimeout(buzz, 600000); // 10 minutes + } + }); + }, 100); + }); + }, 100); + }); + } + buzz(); +} + +let time = new Date(); +let t = getCurrentTime(); +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; + +let active = alarms.filter( + (alarm) => + alarm.on && + alarm.t < t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) +); + +print(active); + +if (active.length) { + showAlarm(active.sort((a, b) => a.t - b.t)[0]); +} diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js new file mode 100644 index 000000000..de3db68ab --- /dev/null +++ b/apps/qalarm/qalarmcheck.js @@ -0,0 +1,42 @@ +/** + * This file checks for upcoming alarms and schedules qalarm.js to deal with them and itself to continue doing these checks. + */ + +print("Checking for alarms..."); + +clearInterval(); + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +let time = new Date(); +let t = getCurrentTime(); + +let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) + .filter( + (alarm) => + alarm.on && + alarm.t > t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) + ) + .sort((a, b) => a.t - b.t); + +if (nextAlarms[0]) { + print("Found alarm, scheduling...", nextAlarms[0].t - t); + setTimeout(() => { + load("qalarm.js"); + eval(require("Storage").read("qalarmcheck.js")); + }, 3600000 * (nextAlarms[0].t - t)); +} else { + print("No alarms found. Will re-check at midnight."); + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + }, 86400000 - t); +} diff --git a/apps/qalarm/widget.js b/apps/qalarm/widget.js new file mode 100644 index 000000000..f80aff653 --- /dev/null +++ b/apps/qalarm/widget.js @@ -0,0 +1,22 @@ +WIDGETS["qalarm"] = { + area: "tl", + width: 0, + draw: function () { + if (this.width) + g.reset().drawImage( + atob( + "GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA" + ), + this.x, + this.y + ); + }, + reload: function () { + WIDGETS["qalarm"].width = ( + require("Storage").readJSON("qalarm.json", 1) || [] + ).some((alarm) => alarm.on) + ? 24 + : 0; + }, +}; +WIDGETS["qalarm"].reload(); From d10adb0684dfdf6c36c1e0ef81842d44f9c3668e Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 13:17:15 -0400 Subject: [PATCH 203/325] Revert "Customize" This reverts commit 9e404c625296d008314a547ff8e798767f65e426. --- README.md | 7 +++---- index.html | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a620121b8..d60d46fd3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -labarks' testing Bangle.js App Loader (and Apps) +Bangle.js App Loader (and Apps) ================================ [![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) -* Try my **development version** at [github.io](https://labarks.github.io/BangleApps/) -* Try the official **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the official **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) +* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, diff --git a/index.html b/index.html index a5aa94e6a..0185f1bae 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ - labarks' testing Bangle.js App Loader + Bangle.js App Loader
- - + From 48ecdc5614b0eb9151dfcd0cdc530abf206d78cb Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 13:25:22 -0400 Subject: [PATCH 204/325] Update widver to 0.02 Display "Rel" (Release) instead of 'undefined' when there is no Build number. --- apps.json | 2 +- apps/widver/ChangeLog | 1 + apps/widver/widget.js | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 3a9925650..7338c438c 100644 --- a/apps.json +++ b/apps.json @@ -1614,7 +1614,7 @@ { "id": "widver", "name": "Firmware Version Widget", - "version": "0.01", + "version": "0.02", "description": "Display the version of the installed firmware in the top widget section.", "icon": "widget.png", "type": "widget", diff --git a/apps/widver/ChangeLog b/apps/widver/ChangeLog index adb5b038a..30581c35b 100644 --- a/apps/widver/ChangeLog +++ b/apps/widver/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget +0.02: Display "Rel" (Release) instead of 'undefined' when there is no Build number. diff --git a/apps/widver/widget.js b/apps/widver/widget.js index 5da66444f..eb751ca23 100644 --- a/apps/widver/widget.js +++ b/apps/widver/widget.js @@ -2,6 +2,10 @@ (() => { var width = 28, ver = process.env.VERSION.split('.'); + + // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number + if(typeof ver[1] === 'undefined'){ver[1] = "Rel";} + function draw() { g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); g.drawString(ver[0], this.x + 2, this.y + 4, true); From eefa209af467fbb993a59db57a7104eecb78ed07 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 12:14:02 +0100 Subject: [PATCH 205/325] support for 'supports:["DEVICEID"]` for files in apps, merge launchb2 and launch --- README.md | 3 ++ apps.json | 26 +++-------- apps/launch/ChangeLog | 3 +- apps/launch/{app.js => app-bangle1.js} | 0 .../app.js => launch/app-bangle2.js} | 0 apps/launchb2/ChangeLog | 4 -- apps/launchb2/app.png | Bin 899 -> 0 bytes bin/firmwaremaker.js | 4 +- bin/firmwaremaker_c.js | 5 ++- bin/sanitycheck.js | 18 +++++--- bin/thumbnailer.js | 41 ++++++++++++++---- core | 2 +- 12 files changed, 63 insertions(+), 43 deletions(-) rename apps/launch/{app.js => app-bangle1.js} (100%) rename apps/{launchb2/app.js => launch/app-bangle2.js} (100%) delete mode 100644 apps/launchb2/ChangeLog delete mode 100644 apps/launchb2/app.png diff --git a/README.md b/README.md index d60d46fd3..531114a34 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,9 @@ and which gives information about the app for the Launcher. // (eg it's evaluated as JS) "noOverwrite":true // if supplied, this file will not be overwritten if it // already exists + "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device + // types named in the array. This allows different versions of + // the app to be uploaded for different platforms }, ] "data": [ // list of files the app writes to diff --git a/apps.json b/apps.json index d1ebe5249..7593a4b63 100644 --- a/apps.json +++ b/apps.json @@ -94,31 +94,17 @@ }, { "id": "launch", - "name": "Launcher (Bangle.js 1 default)", + "name": "Launcher", "shortName": "Launcher", - "version": "0.07", - "description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", + "version": "0.08", + "description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"launch.app.js","url":"app.js"} - ], - "sortorder": -10 - }, - { - "id": "launchb2", - "name": "Launcher (Bangle.js 2 default)", - "shortName": "Launcher", - "version": "0.04", - "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", - "icon": "app.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"launchb2.app.js","url":"app.js"} + {"name":"launch.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]}, + {"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]} ], "sortorder": -10 }, @@ -4114,7 +4100,7 @@ "description": "A touch based stop watch for Bangle JS 2", "icon": "stopwatch.png", "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], - "tags": "tools,app,b2", + "tags": "tools,app", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 09569d8da..bd8a9bd03 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -4,4 +4,5 @@ 0.04: Now displays widgets 0.05: Use g.theme for colours 0.06: Use Bangle.setUI for buttons -0.07: Theme colours fix \ No newline at end of file +0.07: Theme colours fix +0.08: Merge Bangle.js 1 and 2 launchers diff --git a/apps/launch/app.js b/apps/launch/app-bangle1.js similarity index 100% rename from apps/launch/app.js rename to apps/launch/app-bangle1.js diff --git a/apps/launchb2/app.js b/apps/launch/app-bangle2.js similarity index 100% rename from apps/launchb2/app.js rename to apps/launch/app-bangle2.js diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog deleted file mode 100644 index a84587b7e..000000000 --- a/apps/launchb2/ChangeLog +++ /dev/null @@ -1,4 +0,0 @@ -0.01: New App! -0.02: Fix occasional missed image when scrolling up -0.03: Text wrapping, better font -0.04: Reduce code duplication and use new E.showScroller diff --git a/apps/launchb2/app.png b/apps/launchb2/app.png deleted file mode 100644 index 8b4e6caa2fe4720a32f492bd00c6e68751fabfbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 899 zcmV-}1AP36P)EZi8a;XNfX0KK)EG5t2&jmXSR!CR3JOvSF^w(VT@Oq6O4&}^ElAua zX|l63`{wtXcjoO3*x1-CYovub^cOBY?cfcO1>;+VoWa;>Pk;!SG_WW*Es5gZ1-`R@ z4t>oKYJ)f#xYia)IV)#&xZ*BHYck+F1K9eED0&f|F;Lm{VL;rb?(c)W{8d& zzrH4`vJJ?>K!fln4K4LjpAx19%+|UjgL{LF?3gt^suTXG8HN@KQv>tY{cLjA%Q)j? zIX0ma_G_?6n>drF(WulAaitj}A^+b$v5k$*zr})O^uogfX;+bphn_9#OZ}pdk^!&c zqrUVgUd3m%o}@|o!cmyJiIaP-*BlK-1F7-kNoN^X5h5L~tnN`_& zI3(jUhvcNZU>jbg3|-gg8hTDln@m+>N(dP^vh_T*8x8>Qb*ytvU&Y#;lz3_Z*tBLl zEghFFmS~QUfhzCr>E}|Fxr&yKOBoXz4vqjF zlzKwtw{idZM95W(Tbb#rO0Wkqaj6$F@MWZp>h=2o=nr;LTzCQiH!$$4i=w<5W87%F zs6NXOGI0OH6?#VB0k9%#HO2Wg(|z6FTj}`r1kmXWJk5wmGlUFGsuA7}JOW^&y9!O$ zkR=&S*XaHEp1^o_Mn#&D^ig6k6`9rJMEHEc@fMjg8GR Z=Px>?AD_-bQjY)t002ovPDHLkV1j2qp7Q_z diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4e22dd168..4bc2a70b2 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -12,6 +12,7 @@ var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; +var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","widbat","widbt","welcome" @@ -61,7 +62,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 15092ced7..7fb842755 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,7 +29,7 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", + "boot","launch","s7clk","setting", "about","alarm","widlock","widbat","widbt","widid" ]; } else { @@ -132,7 +132,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index dbce9c855..ef795871d 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -50,12 +50,12 @@ try{ } const APP_KEYS = [ - 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', + 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', - 'supports', 'allow_emulator', + 'supports', 'allow_emulator', 'dependencies' ]; -const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite']; +const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; @@ -107,6 +107,13 @@ apps.forEach((app,appIdx) => { if (!app.description) ERROR(`App ${app.id} has no description`); if (!app.icon) ERROR(`App ${app.id} has no icon`); if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); + if (app.screenshots) { + if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`); + app.screenshots.forEach(screenshot => { + if (!fs.existsSync(appDir+screenshot.url)) + ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`); + }); + } if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`); if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`); if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`); @@ -128,13 +135,14 @@ apps.forEach((app,appIdx) => { if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) if (fileNames.includes(file.name)) ERROR(`App ${app.id} file ${file.name} is a duplicate`); - fileNames.push(file.name); + if (!file.supports) fileNames.push(file.name); // assume that there aren't duplicates if 'supports' is set allFiles.push({app: app.id, file: file.name}); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); var fileContents = ""; if (file.content) fileContents = file.content; if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); + if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`); if (file.evaluate) { try { acorn.parse("("+fileContents+")"); @@ -165,7 +173,7 @@ apps.forEach((app,appIdx) => { } } for (const key in file) { - if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`); + if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`); } }); let dataNames = []; diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index db658b01e..b6862741a 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -1,6 +1,11 @@ #!/usr/bin/node +/* +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; +*/ var EMULATOR = "banglejs1"; +var DEVICEID = "BANGLEJS"; var singleAppId; @@ -40,6 +45,10 @@ var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); /* we factory reset ONCE, get this, then we can use it to reset state quickly for each new app */ var factoryFlashMemory = new Uint8Array(FLASH_SIZE); +// Log of messages from app +var appLog = ""; +// List of apps that errored +var erroredApps = []; jsRXCallback = function() {}; jsUpdateGfx = function() {}; @@ -49,6 +58,10 @@ function ERROR(s) { process.exit(1); } +function onConsoleOutput(txt) { + appLog += txt + "\n"; +} + function getThumbnail(appId, imageFn) { console.log("Thumbnail for "+appId); var app = apps.find(a=>a.id==appId); @@ -61,7 +74,10 @@ function getThumbnail(appId, imageFn) { fileGetter:function(url) { console.log(__dirname+"/"+url); return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, settings : SETTINGS}).then(files => { + }, + settings : SETTINGS, + device : { id : DEVICEID } + }).then(files => { console.log("AppInfo returned");//, files); flashMemory.set(factoryFlashMemory); jsTransmitString("reset()\n"); @@ -69,6 +85,7 @@ function getThumbnail(appId, imageFn) { jsTransmitString("g.clear()\n"); var command = files.map(f=>f.cmd).join("\n")+"\n"; command += `load("${appId}.app.js")\n`; + appLog = ""; jsTransmitString(command); console.log("Done."); jsStopIdle(); @@ -79,6 +96,9 @@ function getThumbnail(appId, imageFn) { var firstPixel = rgba32[0]; var blankImage = rgba32.every(col=>col==firstPixel) + if (appLog.indexOf("Uncaught")>=0) + erroredApps.push( { id : app.id, log : appLog } ); + if (!blankImage) { var Jimp = require("jimp"); let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { @@ -113,20 +133,22 @@ setTimeout(function() { console.log("Ready!"); if (singleAppId) { - getThumbnail(singleAppId, "screenshots/"+singleAppId+".png") - + getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); return; } - var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom).map(app=>app.id); - // TODO: Work out about Bangle.js 1 or 2 + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom); + appList = appList.filter(app => !app.screenshots && app.supports.includes(DEVICEID)); + var promise = Promise.resolve(); - appList.forEach(appId => { + appList.forEach(app => { promise = promise.then(() => { - return getThumbnail(appId, "screenshots/"+appId+".png").then(ok => { + var imageFile = "screenshots/"+app.id+"-"+EMULATOR+".png"; + return getThumbnail(app.id, imageFile).then(ok => { screenshots.push({ - id : appId, - url : "screenshots/"+appId+".png" + id : app.id, + url : imageFile, + version: app.version }); }); }); @@ -135,6 +157,7 @@ setTimeout(function() { promise.then(function() { console.log("Complete!"); require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + console.log("Errored Apps", erroredApps); }); diff --git a/core b/core index a7d82825d..70b49d8db 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit a7d82825d3a43e1da7919591ed6fa776f1f0545a +Subproject commit 70b49d8dbd2afa76f4485aadf679dc75e0a8b4ac From cbf69eeaa2adfac6ec5996cc6e4319be349fae92 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 12:14:14 +0100 Subject: [PATCH 206/325] fix lint errors --- apps/boot/bootupdate.js | 2 +- apps/gpstouch/gpstouch.app.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index dfd745de2..8ad61f763 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -182,7 +182,7 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } return lines; };\n`; -}; +} delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..4e49dd1e5 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -24,7 +24,7 @@ function resetLastFix() { function processFix(fix) { last_fix.time = fix.time; log_debug(fix); - + if (fix.fix) { if (!last_fix.fix) { // we dont need to suppress this in quiet mode as it is user initiated @@ -78,7 +78,7 @@ function drawInfo() { g.setColor("#fff"); else g.setColor("#000"); - + g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); } } @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); From 3c5de5ff701ebd71af4fffa0838b29af7b858e6b Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 28 Oct 2021 14:45:29 +0200 Subject: [PATCH 207/325] fix: add semicolumn --- apps/gpstouch/gpstouch.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..cac19cdef 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); From 2e252814a85432e394088f10a6913ba49a6545f8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:23:29 +0100 Subject: [PATCH 208/325] Add first draft of more flexible 'recorder' app for GPS/etc recording --- apps.json | 21 +- apps/recorder/ChangeLog | 1 + apps/recorder/README.md | 27 +++ apps/recorder/app-icon.js | 1 + apps/recorder/app-settings.json | 6 + apps/recorder/app.js | 406 ++++++++++++++++++++++++++++++++ apps/recorder/app.png | Bin 0 -> 1530 bytes apps/recorder/interface.html | 179 ++++++++++++++ apps/recorder/settings.js | 4 + apps/recorder/widget.js | 222 +++++++++++++++++ 10 files changed, 866 insertions(+), 1 deletion(-) create mode 100644 apps/recorder/ChangeLog create mode 100644 apps/recorder/README.md create mode 100644 apps/recorder/app-icon.js create mode 100644 apps/recorder/app-settings.json create mode 100644 apps/recorder/app.js create mode 100644 apps/recorder/app.png create mode 100644 apps/recorder/interface.html create mode 100644 apps/recorder/settings.js create mode 100644 apps/recorder/widget.js diff --git a/apps.json b/apps.json index 7593a4b63..8c7db5bda 100644 --- a/apps.json +++ b/apps.json @@ -631,6 +631,25 @@ ], "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}] }, + { + "id": "recorder", + "name": "Recorder (BETA)", + "shortName": "Recorder", + "version": "0.01", + "description": "Record GPS position, heart rate and more in the background, then download to your PC.", + "icon": "app.png", + "tags": "tool,outdoors,gps,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"recorder.app.js","url":"app.js"}, + {"name":"recorder.img","url":"app-icon.js","evaluate":true}, + {"name":"recorder.wid.js","url":"widget.js"}, + {"name":"recorder.settings.js","url":"settings.js"} + ], + "data": [{"name":"recorder.json"},{"wildcard":"recorder.log?.csv","storageFile":true}] + }, { "id": "gpsnav", "name": "GPS Navigation", @@ -1352,7 +1371,7 @@ "id": "assistedgps", "name": "Assisted GPS Update (AGPS)", "version": "0.01", - "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", "icon": "app.png", "type": "RAM", "tags": "tool,outdoors,agps", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/recorder/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/recorder/README.md b/apps/recorder/README.md new file mode 100644 index 000000000..ba53a99f2 --- /dev/null +++ b/apps/recorder/README.md @@ -0,0 +1,27 @@ +# Recorder + +![icon](app.png) + +This app allows you to record data every few seconds - it can run in background. + +Usually you'd record GPS (but this is not required). The data can later be exported as CSV, KML or GPX files via the Download button in the Bangle.js App Store entry for Recorder. + +## Usage + +First run the `Recorder` app, here you can configure what you want to record, how often, +and you can start and stop recordings. + +You can record + +* **Time** The current time +* **GPS** GPS Latitude, Longitude and Altitude +* **Steps** Steps counted by the step counter +* **HR** Heart rate + +**Note:** It is possible for other apps to record information using this app +as well. They need to define a `foobar.recorder.js` file - see the `getRecorders` +function in `widget.js` for more information. + +## Tips + +When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. diff --git a/apps/recorder/app-icon.js b/apps/recorder/app-icon.js new file mode 100644 index 000000000..4181d2b12 --- /dev/null +++ b/apps/recorder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///vPWvN8kvkuu14/s3OMjN0Kf4AQ2vaCB0Ftu3oARfgdt23AGsO2NaHQT+MB2XJCJ1MyXJsAQMgUky3JkARMjIRBpIRNvMl2VJlAQLldvtmSpN+CJcbt2ECJsBregggRBv4RRdJfbgEkyVLq3ACJ1Jq3dCBMKBYIRBpFW7d0CJUDsgRBhdbtvQCJHYgUTm1IgEttuwCI8GCIMSpMwA4MVEZPoCIUkaxj7BoQRPiQRCwARNpARByARNpMJCJyNBgKjBCJy1CCJ79BfZYNDCJoxBBoQnCABBVFN4IRJPIoRLV4sCpMgCJTbECJYKFCJUJBQsJfpoA/A")) diff --git a/apps/recorder/app-settings.json b/apps/recorder/app-settings.json new file mode 100644 index 000000000..4a3117a17 --- /dev/null +++ b/apps/recorder/app-settings.json @@ -0,0 +1,6 @@ +{ + "recording":false, + "file":"record.log0.csv", + "period":10, + "record" : ["gps"] +} diff --git a/apps/recorder/app.js b/apps/recorder/app.js new file mode 100644 index 000000000..9c8380c07 --- /dev/null +++ b/apps/recorder/app.js @@ -0,0 +1,406 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings; + +var osm; +try { // if it's installed, use the OpenStreetMap module + osm = require("openstmap"); +} catch (e) {} + +function loadSettings() { + settings = require("Storage").readJSON("recorder.json",1)||{}; + var changed = false; + if (!settings.file) { + changed = true; + settings.file = "record.log0.csv"; + } + if (!Array.isArray(settings.record)) { + settings.record = ["gps"]; + changed = true; + } + if (changed) + require("Storage").writeJSON("recorder.json", settings); +} +loadSettings(); + +function updateSettings() { + require("Storage").writeJSON("recorder.json", settings); + if (WIDGETS["recorder"]) + WIDGETS["recorder"].reload(); +} + +function getTrackNumber(filename) { + return parseInt(filename.match(/^record\.log(.*)\.csv$/)[1]||0); +} + +function showMainMenu() { + function boolFormat(v) { return v?"Yes":"No"; } + function menuRecord(id) { + return { + value: settings.record.includes(id), + format: boolFormat, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.record = settings.record.filter(r=>r!=id); + if (v) settings.record.push(id); + updateSettings(); + } + }; + } + const mainmenu = { + '': { 'title': 'GPS Record' }, + '< Back': ()=>{load();}, + 'RECORD': { + value: !!settings.recording, + format: v=>v?"On":"Off", + onchange: v => { + setTimeout(function() { + E.showMenu(); + WIDGETS["recorder"].setRecording(v).then(function() { + print("Complete"); + loadSettings(); + print(settings.recording); + showMainMenu(); + }); + }, 1); + } + }, + 'File #': { + value: getTrackNumber(settings.file), + min: 0, + max: 99, + step: 1, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.file = "record.log"+v+".csv"; + updateSettings(); + } + }, + 'View Tracks': ()=>{viewTracks();}, + 'Time Period': { + value: settings.period||10, + min: 1, + max: 120, + step: 1, + format: v=>v+"s", + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.period = v; + updateSettings(); + } + } + }; + var recorders = WIDGETS["recorder"].getRecorders(); + Object.keys(recorders).forEach(id=>{ + mainmenu["Log "+recorders[id]().name] = menuRecord(id); + }); + return E.showMenu(mainmenu); +} + + + +function viewTracks() { + const menu = { + '': { 'title': 'GPS Tracks' } + }; + var found = false; + require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + found = true; + menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); + }); + if (!found) + menu["No Tracks found"] = function(){}; + menu['< Back'] = () => { showMainMenu(); }; + return E.showMenu(menu); +} + +function getTrackInfo(filename) { + "ram" + var minLat = 90; + var maxLat = -90; + var minLong = 180; + var maxLong = -180; + var starttime, duration=0; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + var fields, timeIdx, latIdx, lonIdx; + var nl = 0, c, n; + if (l!==undefined) { + fields = l.trim().split(","); + timeIdx = fields.indexOf("Time"); + latIdx = fields.indexOf("Latitude"); + lonIdx = fields.indexOf("Longitude"); + l = f.readLine(f); + } + if (l!==undefined) { + c = l.split(","); + starttime = parseInt(c[timeIdx]); + } + // pushed this loop together to try and bump loading speed a little + while(l!==undefined) { + ++nl;c=l.split(","); + n = +c[latIdx];if(n>maxLat)maxLat=n;if(nmaxLong)maxLong=n;if(nylen ? screenSize/xlen : screenSize/ylen; + return { + fn : getTrackNumber(filename), + fields : fields, + filename : filename, + time : new Date(starttime*1000), + records : nl, + minLat : minLat, maxLat : maxLat, + minLong : minLong, maxLong : maxLong, + lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2, + lfactor : lfactor, + scale : scale, + duration : Math.round(duration) + }; +} + +function asTime(v){ + var mins = Math.floor(v/60); + var secs = v-mins*60; + return ""+mins.toString()+"m "+secs.toString()+"s"; +} + +function viewTrack(filename, info) { + if (!info) { + E.showMessage("Loading...","GPS Track "+getTrackNumber(filename)); + info = getTrackInfo(filename); + } + console.log(info); + const menu = { + '': { 'title': 'GPS Track '+info.fn } + }; + if (info.time) + menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; + menu["Duration"] = { value : asTime(info.duration)}; + menu["Records"] = { value : ""+info.records }; + if (info.fields.includes("Latitude")) + menu['Plot Map'] = function() { + info.qOSTM = false; + plotTrack(info); + }; + if (osm && info.fields.includes("Latitude")) + menu['Plot OpenStMap'] = function() { + info.qOSTM = true; + plotTrack(info); + } + if (info.fields.includes("Altitude")) + menu['Plot Alt.'] = function() { + plotGraph(info, "Altitude"); + }; + menu['Plot Speed'] = function() { + plotGraph(info, "Speed"); + }; + // TODO: steps, heart rate? + menu['Erase'] = function() { + E.showPrompt("Delete Track?").then(function(v) { + if (v) { + settings.recording = false; + updateSettings(); + var f = require("Storage").open(filename,"r"); + f.erase(); + viewTracks(); + } else + viewTrack(n, info); + }); + }; + menu['< Back'] = () => { viewTracks(); }; + return E.showMenu(menu); +} + +function plotTrack(info) { + "ram" + + function distance(lat1,long1,lat2,long2) { "ram" + var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); + var y = lat2 - lat1; + return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; + } + + // Function to convert lat/lon to XY + var getMapXY; + if (info.qOSTM) { + getMapXY = osm.latLonToXY.bind(osm); + } else { + getMapXY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } + + E.showMenu(); // remove menu + E.showMessage("Drawing...","GPS Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var cx = g.getWidth()/2; + var cy = 24 + (g.getHeight()-24)/2; + g.setColor(1,0.5,0.5); + g.setFont("Vector",16); + g.drawString("Track"+info.fn.toString()+" - Loading",10,220); + g.setColor(0,0,0); + g.fillRect(0,220,239,239); + if (!info.qOSTM) { + g.setColor(1, 0, 0); + g.fillRect(9,80,11,120); + g.fillPoly([9,60,19,80,0,80]); + g.setColor(1,1,1); + g.drawString("N",2,40); + g.setColor(1,1,1); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + osm.draw(); + g.setColor(0, 0, 0); + } + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + g.drawString(asTime(info.duration),10,220); + var f = require("Storage").open(info.filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var ox=0; + var oy=0; + var olat,olong,dist=0; + var i=0; + var c = l.split(","); + var lat = +c[latIdx]; + var long = +c[lonIdx]; + var mp = getMapXY(lat, long); + g.moveTo(mp.x,mp.y); + g.setColor(0,1,0); + g.fillCircle(mp.x,mp.y,5); + if (info.qOSTM) g.setColor(1,0,0.55); + else g.setColor(1,1,1); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + lat = +c[latIdx]; + long = +c[lonIdx]; + mp = getMapXY(lat, long); + g.lineTo(mp.x,mp.y); + if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible + var d = distance(olat,olong,lat,long); + if (!isNaN(d)) dist+=d; + olat = lat; + olong = long; + ox = mp.x; + oy = mp.y; + l = f.readLine(f); + } + g.setColor(1,0,0); + g.fillCircle(ox,oy,5); + if (info.qOSTM) g.setColor(0, 0, 0); + else g.setColor(1,1,1); + g.drawString(require("locale").distance(dist),120,220); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.fn, info); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); + g.flip(); +} + +function plotGraph(info, style) { + "ram" + E.showMenu(); // remove menu + E.showMessage("Calculating...","GPS Track "+info.fn); + var filename = info.filename; + var infn = new Float32Array(80); + var infc = new Uint16Array(80); + var title; + var lt = 0; // last time + var tn = 0; // count for each time period + var strt, dur = info.duration; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var nl = 0, c, i; + var timeIdx = info.fields.indexOf("Time"); + if (l!==undefined) { + c = l.split(","); + strt = c[timeIdx]; + } + if (style=="Altitude") { + title = "Altitude (m)"; + var altIdx = info.fields.indexOf("Altitude"); + while(l!==undefined) { + ++nl;c=l.split(","); + i = Math.round(80*(c[timeIdx] - strt)/dur); + infn[i]+=+c[altIdx]; + infc[i]++; + l = f.readLine(f); + } + } else if (style=="Speed") { + title = "Speed (m/s)"; + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; + while(l!==undefined) { + ++nl;c=l.split(","); + t = c[timeIdx]; + i = Math.round(80*(t - strt)/dur); + p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); + dx = p.x-lp.x; + dy = p.y-lp.y; + d = Math.sqrt(dx*dx+dy*dy); + if (t!=lt) { + infn[i]+=d / (t-lt); // speed + infc[i]++; + } + lp = p; + lt = t; + l = f.readLine(f); + } + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) infn[i]/=infc[i]; + var n = infn[i]; + if (n>max) max=n; + if (n 8) { + grid*=2; + } + // draw + g.clear(1).setFont("6x8",1); + var r = require("graph").drawLine(g, infn, { + x:4,y:24, + width: g.getWidth()-24, + height: g.getHeight()-(24+8), + axes : true, + gridy : grid, + gridx : 50, + title: title, + xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + }); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.filename, info); + }, global.BTN3||BTN1); + g.flip(); +} + +showMainMenu(); diff --git a/apps/recorder/app.png b/apps/recorder/app.png new file mode 100644 index 0000000000000000000000000000000000000000..036f5d132181624c52b034f07732d39a958903fa GIT binary patch literal 1530 zcmV*0sE5goeq8BC!bd2RBEJ`$A<#X(ZKv9FiT; z;*uCW9uG-1z*)G#XmK+iJsuB9HDGbtcSno!F6%C%G?KpxeYCJb^aaW#QF54h^C0iC z?n+;KsxsNSqx@eO{sNjj4VHs@}e3_BNyS3^mYDRek&q zdCnpMw^zxDbO|@u3@`#x<&Ng^t;?S_Qqycik-EByl;E{V|D2a+yGgUKStx)WHU$7c z4DeD$p%Hpo5?)4+)ofeVbuGL9FXV+a4*+BEH$s|UgE$)wytBPDQB5rg8Ss8sQ1-|` z^HDPiVUJ=ebz?kTK_`1KY`&~0@c3QZ6VZ$$ZUenF^S|=-`l=y`rYA#t;Uo`u2$)0{ zWgkn!O*U;rjvy$wE@WmU)NurH8Sv~Ycq`!XF9Qu-JJ64(_|I<4;f}59U^7A#DvKnD?%@Kw zsebsL!j{I$v%=?JlU9W_=~b9U7wr8XKUSc>-I3@v;PrN_BxUH~=gcO>U3%iQdXE@p z#>x{+X$%o;~@$c|Tp4ueS#+`hNkLR;XjnX6aJLW!3F20Pd(s0EnT% z;;?)K?F)eBH1ak{tjCr;XsuBuvdz0090V!_`i%Pg{eP)Q13+11R_IE7%zFi`&FNXo5A> z-j;L&Hka*0&>LK2b=zAV8@}&Q|@N<}sL>`8=#CQB5rg8z7>U5uw4N;^NwzoSZ)at^^F>Z#P2z zg|lZ?z#tMb(A3mWN(^CNE{f*QIx8=)k$^WM^Ayl0iRfyH+dyMuV?JcIPrGCipt!xh zE!uh6bMoH>P#=~|7i+E=m`rl zrGkhOJ{8fw8znLcgU&Vp_V74P78Mt7yhGmlh>-o<*)ws!fmL)HINEslClID+W5jU% z*YoEezf)fJ+<6TEyR3~d!rJ!sc9-5BwCFO>c=&KNBNq836|gz{xfK-^(tqXiob-Iy zlEh#R_InQK=^+wb1{lN3+G_$7#l$TsEPUbS-Q+)`Yh#C(dn28QBW*_XK$t7s*~7$QkMaOsS*q_7?VJ#EGjOp)hn2tz4!_Py$*r|KxPUI gJ*gXIlzWYT0cWfgM6O1!vj6}907*qoM6N<$f+S4TzyJUM literal 0 HcmV?d00001 diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html new file mode 100644 index 000000000..2ae1c3e71 --- /dev/null +++ b/apps/recorder/interface.html @@ -0,0 +1,179 @@ + + + + + + THIS IS NOT CURRENTLY IMPLEMENTED +
+ + + + + diff --git a/apps/recorder/settings.js b/apps/recorder/settings.js new file mode 100644 index 000000000..2a9a7a0d8 --- /dev/null +++ b/apps/recorder/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app - we need all the memory + load("record.app.js"); +})(); diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js new file mode 100644 index 000000000..38b1d69d2 --- /dev/null +++ b/apps/recorder/widget.js @@ -0,0 +1,222 @@ +(() => { + var storageFile; // file for GPS track + var entriesWritten = 0; + var activeRecorders = []; + var writeInterval; + + function loadSettings() { + var settings = require("Storage").readJSON("recorder.json",1)||{}; + settings.period = settings.period||10; + if (!settings.file || !settings.file.startsWith("record.log")) + settings.recording = false; + return settings; + } + + function getRecorders() { + var recorders = { + gps:function() { + var lat = 0; + var lon = 0; + var alt = 0; + var samples = 0; + var hasFix = 0; + function onGPS(f) { + hasFix = f.fix; + if (!hasFix) return; + lat += fix.lat; + lon += fix.lon; + alt += fix.alt; + samples++; + } + return { + name : "GPS", + fields : ["Latitude","Longitude","Altitude"], + getValues : () => { + var r = ["","",""]; + if (samples) + r = [(lat/samples).toFixed(6),(lon/samples).toFixed(6),Math.round(alt/samples)]; + samples = 0; lat = 0; lon = 0; alt = 0; + return r; + }, + start : () => { + hasFix = false; + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1,"recorder"); + }, + stop : () => { + hasFix = false; + Bangle.removeListener('GPS', onGPS); + Bangle.setGPSPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) + }; + }, + hrm:function() { + var bpm = 0, bpmConfidence = 0; + var hasBPM = false; + function onHRM(h) { + if (h.confidence >= bpmConfidence) { + bpmConfidence = h.confidence; + bpm = h.bpm; + if (bpmConfidence) hasBPM = true; + } + } + return { + name : "HR", + fields : ["Heartrate"], + getValues : () => { + var r = [bpmConfidence?bpm:""]; + bpm = 0; bpmConfidence = 0; + return r; + }, + start : () => { + hasBPM = false; + Bangle.on('HRM', onHRM); + Bangle.setHRMPower(1,"recorder"); + }, + stop : () => { + hasBPM = false; + Bangle.removeListener('HRM', onHRM); + Bangle.setHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) + }; + }, + steps:function() { + var lastSteps = 0; + return { + name : "Steps", + fields : ["Steps"], + getValues : () => { + var c = Bangle.getStepCount(), r=[c-lastSteps]; + lastSteps = c; + return r; + }, + start : () => { lastSteps = Bangle.getStepCount(); }, + stop : () => {}, + draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y) + }; + } + // TODO: recAltitude from pressure sensor + }; + /* eg. foobar.recorder.js + (function(recorders) { + recorders.foobar = { + name : "Foobar", + fields : ["foobar"], + getValues : () => [123], + start : () => {}, + stop : () => {}, + draw (x,y) => {} // draw 12x12px status image + } + }) + */ + require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders)); + return recorders; + } + + function writeLog() { + entriesWritten++; + WIDGETS["recorder"].draw(); + try { + var fields = [Math.round(getTime())]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.getValues())); + if (storageFile) storageFile.write(fields.join(",")+"\n"); + } catch(e) { + // If storage.write caused an error, disable + // GPS recording so we don't keep getting errors! + console.log("recorder: error", e); + var settings = loadSettings(); + settings.recording = false; + require("Storage").write("recorder.json", settings); + reload(); + } + } + + // Called by the GPS app to reload settings and decide what to do + function reload() { + var settings = loadSettings(); + if (writeInterval) clearInterval(writeInterval); + writeInterval = undefined; + + activeRecorders.forEach(rec => rec.stop()); + activeRecorders = []; + entriesWritten = 0; + + if (settings.recording) { + // set up recorders + var recorders = getRecorders(); // TODO: order?? + settings.record.forEach(r => { + var recorder = recorders[r]; + if (!recorder) { + console.log("Recorder for "+E.toJS(r)+"+not found"); + return; + } + var activeRecorder = recorder(); + activeRecorder.start(); + activeRecorders.push(activeRecorder); + // TODO: write field names? + }); + WIDGETS["recorder"].width = 15 + ((activeRecorders.length+1)>>1)*12; // 12px per recorder + // open/create file + if (require("Storage").list(settings.file).length) { // Append + storageFile = require("Storage").open(settings.file,"a"); + // TODO: what if loaded modules are different?? + } else { + storageFile = require("Storage").open(settings.file,"w"); + // New file - write headers + var fields = ["Time"]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.fields)); + storageFile.write(fields.join(",")+"\n"); + } + // start recording... + WIDGETS["recorder"].draw(); + writeInterval = setInterval(writeLog, settings.period*1000); + } else { + WIDGETS["recorder"].width = 0; + storageFile = undefined; + } + } + // add the widget + WIDGETS["recorder"]={area:"tl",width:0,draw:function() { + if (!writeInterval) return; + g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); + activeRecorders.forEach((recorder,i)=>{ + recorder.draw(this.y+15+(i>>1)*12, this.y+(i&1)*12); + }); + },getRecorders:getRecorders,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + },setRecording:function(isOn) { + var settings = loadSettings(); + if (isOn && !settings.recording && require("Storage").list(settings.file).length) + return E.showPrompt("Overwrite\nLog 0?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ + if (selection=="no") return false; // just cancel + if (selection=="yes") require("Storage").open(settings.file,"r").erase(); + // TODO: Add 'new file' option + return WIDGETS["recorder"].setRecording(1); + }); + settings.recording = isOn; + require("Storage").write("recorder.json", settings); + WIDGETS["recorder"].reload(); + return Promise.resolve(settings.recording); + }/*,plotTrack:function(m) { // m=instance of openstmap module + // if we're here, settings was already loaded + var f = require("Storage").open(settings.file,"r"); + var l = f.readLine(f); + if (l===undefined) return; + var c = l.split(","); + var mp = m.latLonToXY(+c[1], +c[2]); + g.moveTo(mp.x,mp.y); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + mp = m.latLonToXY(+c[1], +c[2]); + g.lineTo(mp.x,mp.y); + g.fillCircle(mp.x,mp.y,2); // make the track more visible + l = f.readLine(f); + } + }*/}; + // load settings, set correct widget width + reload(); +})() From 1b539fee8dbe16dbf9c2984898e1b4e19a25f784 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 28 Oct 2021 15:14:51 +0200 Subject: [PATCH 209/325] fix: app json --- apps.json | 7 ++++--- apps/gpstouch/gpstouch.app.js | 2 +- apps/swiperclocklaunch/icon.js | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 apps/swiperclocklaunch/icon.js diff --git a/apps.json b/apps.json index 440257e3c..a9a3b2f53 100644 --- a/apps.json +++ b/apps.json @@ -4108,11 +4108,12 @@ "version": "0.01", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", - "type": "boot", - "tags": "system", + "type": "bootloader", + "tags": "tools, system", "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ - {"name":"swiperclocklaunch.boot.js","url":"boot.js"} + {"name":"swiperclocklaunch.boot.js","url":"boot.js"}, + {"name":"swiperclocklaunch.img","url":"icon.js","evaluate":true} ] } ] diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..cac19cdef 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); diff --git a/apps/swiperclocklaunch/icon.js b/apps/swiperclocklaunch/icon.js new file mode 100644 index 000000000..c9089ce5c --- /dev/null +++ b/apps/swiperclocklaunch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEoxH+AB8WAAwYQEaQrdEp4pWEyYoRC49kxGs2fX6+z1mIsgpUCQtAxAjCAA+zxFAFCAQFxAkJAAuIFBxMF1oeHgEABI+sFBomEORInJPgJ7EEyonLFAJQJBIh0IE5x6GE47CME5nXsgnGOojmME5p5HJyAnO6+IE5LEKE6JQEE4lkC5gnPUIh2SE6B4EAAesC5oAP1gnHTxpPDAQIAFeJQACH5wnP64nWAA3CBJB3WAA203fQBAp3IY4plENQ4HC2gABkjHNxAnX2nJBYeIEYf+AYVkE5oDGE4e0UgdkEwYnDUAITEACikBTwgnFxAnZFAJ2FE4lAJ7dAE4pQFY6yfCToYmDE4kW1jvX1geEE4YoF2YfFABRzD67EEEwqiGFCAmETg5QJPQYAMTQJ0GE5AoGshSPYQgmKFA72BFJWzxBzEExgoIKYOI1grC2esxBLGExwpKABolPFCwmSFKQlVFZoXP")) \ No newline at end of file From 577bcd39b1a25c41d725655943401717080ae04a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:28:30 +0100 Subject: [PATCH 210/325] Merge bangle 1 and 2 welcome screens --- apps.json | 27 ++++-------------- apps/welcome/ChangeLog | 1 + apps/welcome/{app.js => app-bangle1.js} | 8 ------ .../app.js => welcome/app-bangle2.js} | 8 ------ apps/welcome2/ChangeLog | 17 ----------- apps/welcome2/app-icon.js | 1 - apps/welcome2/app.png | Bin 1939 -> 0 bytes apps/welcome2/boot.js | 9 ------ apps/welcome2/settings.js | 18 ------------ bin/sanitycheck.js | 4 +-- 10 files changed, 8 insertions(+), 85 deletions(-) rename apps/welcome/{app.js => app-bangle1.js} (97%) rename apps/{welcome2/app.js => welcome/app-bangle2.js} (97%) delete mode 100644 apps/welcome2/ChangeLog delete mode 100644 apps/welcome2/app-icon.js delete mode 100644 apps/welcome2/app.png delete mode 100644 apps/welcome2/boot.js delete mode 100644 apps/welcome2/settings.js diff --git a/apps.json b/apps.json index 23769e299..2362c2e9d 100644 --- a/apps.json +++ b/apps.json @@ -169,17 +169,18 @@ }, { "id": "welcome", - "name": "Welcome (Bangle.js 1)", + "name": "Welcome", "shortName": "Welcome", - "version": "0.12", + "version": "0.13", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", "tags": "start,welcome", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"welcome.boot.js","url":"boot.js"}, - {"name":"welcome.app.js","url":"app.js"}, + {"name":"welcome.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, + {"name":"welcome.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"welcome.settings.js","url":"settings.js"}, {"name":"welcome.img","url":"app-icon.js","evaluate":true} ], @@ -203,24 +204,6 @@ ], "data": [{"name":"mywelcome.json"}] }, - { - "id": "welcome2", - "name": "Welcome (Bangle.js 2)", - "shortName": "Welcome", - "version": "0.13", - "description": "Appears at first boot and explains how to use Bangle.js 2", - "icon": "app.png", - "tags": "start,welcome", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"welcome2.boot.js","url":"boot.js"}, - {"name":"welcome2.app.js","url":"app.js"}, - {"name":"welcome2.settings.js","url":"settings.js"}, - {"name":"welcome2.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"welcome2.json"}] - }, { "id": "gbridge", "name": "Gadgetbridge", diff --git a/apps/welcome/ChangeLog b/apps/welcome/ChangeLog index 519222c52..f72f77a4b 100644 --- a/apps/welcome/ChangeLog +++ b/apps/welcome/ChangeLog @@ -14,3 +14,4 @@ 0.10: Tweaks to reduce memory usage 0.11: Fix initial screen fill colour 0.12: Fix swipe direction (#800) +0.13: Mods for Bangle.js 2 diff --git a/apps/welcome/app.js b/apps/welcome/app-bangle1.js similarity index 97% rename from apps/welcome/app.js rename to apps/welcome/app-bangle1.js index 047b0cdb2..949750b38 100644 --- a/apps/welcome/app.js +++ b/apps/welcome/app-bangle1.js @@ -290,14 +290,6 @@ setWatch(()=>{ }, BTN2, {repeat:true,edge:"falling"}); setWatch(()=>move(-1), BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome2/app.js b/apps/welcome/app-bangle2.js similarity index 97% rename from apps/welcome2/app.js rename to apps/welcome/app-bangle2.js index d9a967d8a..93d1c5657 100644 --- a/apps/welcome2/app.js +++ b/apps/welcome/app-bangle2.js @@ -243,14 +243,6 @@ setWatch(()=>{ move(1); }, BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome2/ChangeLog b/apps/welcome2/ChangeLog deleted file mode 100644 index f72f77a4b..000000000 --- a/apps/welcome2/ChangeLog +++ /dev/null @@ -1,17 +0,0 @@ -0.01: New App! -0.02: Animate balloon intro -0.03: BTN3 now won't restart when at the end -0.04: Fix regression after tweaks to Storage.readJSON -0.05: Move configuration into App/widget settings -0.06: Move loader into welcome.boot.js -0.07: Run again when updated - Don't run again when settings app is updated (or absent) - Add "Run Now" option to settings -0.08: Don't overwrite existing settings on app update -0.09: Allow welcome to run after a fresh install - More useful app menu - BTN2 now goes to menu on release -0.10: Tweaks to reduce memory usage -0.11: Fix initial screen fill colour -0.12: Fix swipe direction (#800) -0.13: Mods for Bangle.js 2 diff --git a/apps/welcome2/app-icon.js b/apps/welcome2/app-icon.js deleted file mode 100644 index 5c1373e17..000000000 --- a/apps/welcome2/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/welcome2/app.png b/apps/welcome2/app.png deleted file mode 100644 index ebbf254bd7c3546e8337c97648a7eb56747c81ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1939 zcmV;E2WP)^Vse?M*ABwfS z8MZ{CGrE{9Te8V!(51`XGM!;roJ%xW2E^g2NO{qXsim}(VOBaof>d5Dolx3xFQxb1 z-nZxMkEd_+KDRC9c8eL`e{OQldCvKM@9+H1@Ao`#6F2ey4asCOZNf#^U4iltNGVVP zD9{Fa2%!dFY=)EdIoSdkU1+ff<1#70}x@Hu6Kz{*xG?E@iS0CV6Z_UXTXX+c`#EfepLM z7bkj*!-~A7=*pg+Sbi*K%7RHf4giWO!Qq3`KOGl&fb97ejd1$m`W-Ff7CVP?6!NP@ z*#dZSJOwP6bwBxEm_^gcdS*L+X9eY#LFhPi?iv?)0Qct&s7(@2rVYtLc?9_Na7Wq| z*s!a7VL$mhO77$FB^z-&y=>gMfp-sY$1lBBpl3RC?8G{+#s}vNi$-#!6vBPB&)OMR zSFd&$bZt`bEWGtWzIxkQ4xXu@;q+gK#Us}SYaD|6$H%;%Ti;I9kp;}PGuy7fJm2Nj z3iFC{=TP>!hdI^J%>L8W066-F^;LCQ!M&eAbbP(vkK`q5{1_wcNTtxuZ2ySr8h}N& zRszc2md$8ii@03Y-n|s>8yCR?kc`--87pEZEdlP41PEPrX4^*;%A5{2vnS8zR7W$x z$ly8R&9b`gL{GM5xU>zNlg7h)0KF}J0vsa*DW~nsDFU8oDs=&cd09m`4HsuS|8}Go}cbc4m$MYKWCB5WaYg-fnm1w~?N@s{QIvQxc8y;|@aI_lzb}w~BisS# zemenx-!nLkY_L)4u$>fXwvAaIiTBWc+v}7`&~p6!uCs3uaov50-F-3g3k#a`wiTU zvP%9=C|wR)0Y87>{T;xe;}`am?ajxK)a*r5vJpf0+j$=?(bQEM4dHi_T)m(OP>_0! z1_+^4NFyyH<*{D@K^5csiY97XHdE-Io)Y4AJ18jdq6!;N6w__G$4oxaYSOXO>ixVd z+ws{6d|mcaH(IW=leb~(nby14&Nh=~ygVGcVjJW0xrjs(bVp2%hH_X~(1Taf-^c)x z7fdd^7$=di#^z*S%ALL$;a+Lr=xF3e6aY{@cU@z0T5E~G9qD5HeiuEVI9(y3DL9Gw z1yOPg`+ITpuxVa+DK3e)^{?OJ2V;=^WrSx4^Q0WKKXvUe>I+R(Rh1DhZFp7C$I`_l zlS*4#H@%Sr4uO?(LjPWs*V=<2)&jK*IPU`jS>z$Ju_P`2WvTIvXn zmH3zi^y9hR%brk`mjK)IQWZKoBLssHEiBBw=l9z94dC&#M}GJIl*eYU7cZ~QEIAx*jx#4V*6tXFt75D0`S3i z9}Y}9+K^{NLmzuXR~F6skih>$z;LX3{!6Pxw0a<^>ZsE89`|RJp-`2xV9n22`K4{uPal12c#LV76{gFJ8nP!- zaw36avkT_Uy!ZAWT)neo#k6g;jWdVG2vqd_X5^b_KQbcVPgUo7pa0hDpH9h{^Jkwc z-!McT8S3Bl{(_CIEp)Rdry0B}FR$L8xE8Y*}X}Yi3ly zimxsC2XF#(_RiV^WkXzj?IRg&AIPu(06{kcC_FmEycEFvEC@)5a|@6}ST)4#1`by) zyX#!0>t$TP%4Lh%RHy~~L+v8eWV#kifB-A0ZW?Y&s`=Rw5cL>u!8JF_b1TZrj!Z7V#$X$n89sf7V!{MeE?v?nqF**mb8fyw8z-vHP29u{ Z;y>o9o!*JS4-fzV002ovPDHLkV1gG+pke?3 diff --git a/apps/welcome2/boot.js b/apps/welcome2/boot.js deleted file mode 100644 index 07b7386f1..000000000 --- a/apps/welcome2/boot.js +++ /dev/null @@ -1,9 +0,0 @@ -(function() { - let s = require('Storage').readJSON('welcome2.json', 1) || {}; - if (!s.welcomed) { - setTimeout(() => { - require('Storage').write('welcome2.json', {welcomed: true}) - load('welcome2.app.js') - }) - } -})() diff --git a/apps/welcome2/settings.js b/apps/welcome2/settings.js deleted file mode 100644 index d87cf4b55..000000000 --- a/apps/welcome2/settings.js +++ /dev/null @@ -1,18 +0,0 @@ -(function(back) { - let settings = require('Storage').readJSON('welcome2.json', 1) - || require('Storage').readJSON('setting.json', 1) || {} - E.showMenu({ - '': { 'title': 'Welcome App' }, - 'Run next boot': { - value: !settings.welcomed, - format: v => v ? 'Yes' : 'No', - onchange: v => require('Storage').write('welcome2.json', {welcomed: !v}), - }, - 'Run Now': () => load('welcome2.app.js'), - 'Turn off & run next': () => { - require('Storage').write('welcome2.json', {welcomed: false}); - Bangle.off(); - }, - '< Back': back, - }) -}) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index ef795871d..9c5f4c916 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -133,9 +133,9 @@ apps.forEach((app,appIdx) => { if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`); let char = file.name.match(FORBIDDEN_FILE_NAME_CHARS) if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) - if (fileNames.includes(file.name)) + if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set ERROR(`App ${app.id} file ${file.name} is a duplicate`); - if (!file.supports) fileNames.push(file.name); // assume that there aren't duplicates if 'supports' is set + fileNames.push(file.name); allFiles.push({app: app.id, file: file.name}); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); From 5ad849b8670d704725ff8311f83d7b9030a1e8e8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:31:50 +0100 Subject: [PATCH 211/325] naming --- apps/recorder/app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9c8380c07..9b9c06c78 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -49,7 +49,7 @@ function showMainMenu() { }; } const mainmenu = { - '': { 'title': 'GPS Record' }, + '': { 'title': 'Recorder' }, '< Back': ()=>{load();}, 'RECORD': { value: !!settings.recording, @@ -102,7 +102,7 @@ function showMainMenu() { function viewTracks() { const menu = { - '': { 'title': 'GPS Tracks' } + '': { 'title': 'Tracks' } }; var found = false; require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ @@ -174,12 +174,12 @@ function asTime(v){ function viewTrack(filename, info) { if (!info) { - E.showMessage("Loading...","GPS Track "+getTrackNumber(filename)); + E.showMessage("Loading...","Track "+getTrackNumber(filename)); info = getTrackInfo(filename); } console.log(info); const menu = { - '': { 'title': 'GPS Track '+info.fn } + '': { 'title': 'Track '+info.fn } }; if (info.time) menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; @@ -240,7 +240,7 @@ function plotTrack(info) { } E.showMenu(); // remove menu - E.showMessage("Drawing...","GPS Track "+info.fn); + E.showMessage("Drawing...","Track "+info.fn); g.flip(); // on buffered screens, draw a not saying we're busy g.clear(1); var s = require("Storage"); @@ -318,7 +318,7 @@ function plotTrack(info) { function plotGraph(info, style) { "ram" E.showMenu(); // remove menu - E.showMessage("Calculating...","GPS Track "+info.fn); + E.showMessage("Calculating...","Track "+info.fn); var filename = info.filename; var infn = new Float32Array(80); var infc = new Uint16Array(80); From 5bbe209a44e8cd15dbcd293197d8a865f2a62427 Mon Sep 17 00:00:00 2001 From: qucchia Date: Thu, 28 Oct 2021 16:00:43 +0200 Subject: [PATCH 212/325] Update Q Alarm --- apps/qalarm/ChangeLog | 1 + apps/qalarm/app.js | 13 +++---------- apps/qalarm/qalarm.js | 4 ---- apps/qalarm/qalarmcheck.js | 7 +++---- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog index 4022f485c..135e69d23 100644 --- a/apps/qalarm/ChangeLog +++ b/apps/qalarm/ChangeLog @@ -1 +1,2 @@ 0.01: First version! +0.02: Fixed alarms not working and localised days of week. \ No newline at end of file diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js index 4d27739cf..64f601bf6 100644 --- a/apps/qalarm/app.js +++ b/apps/qalarm/app.js @@ -172,21 +172,14 @@ function showDaysMenu(alarmIndex, alarm) { "< Back": () => showEditAlarmMenu(alarmIndex, alarm), }; - [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ].forEach((dayOfWeek, i) => { + for (let i = 0; i < 7; i++) { + let dayOfWeek = require("locale").dow({ getDay: () => i }); menu[dayOfWeek] = { value: alarm.daysOfWeek[i], format: (v) => (v ? "Yes" : "No"), onchange: (v) => (alarm.daysOfWeek[i] = v), }; - }); + } return E.showMenu(menu); } diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js index 38571987e..6b31ba645 100644 --- a/apps/qalarm/qalarm.js +++ b/apps/qalarm/qalarm.js @@ -1,7 +1,5 @@ // This file shows the alarm -print("Starting alarm"); - function formatTime(t) { let hrs = Math.floor(t / 3600000); let mins = Math.round((t / 60000) % 60); @@ -150,8 +148,6 @@ let active = alarms.filter( (alarm.timer || alarm.daysOfWeek[time.getDay()]) ); -print(active); - if (active.length) { showAlarm(active.sort((a, b) => a.t - b.t)[0]); } diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js index de3db68ab..9a3f10d5e 100644 --- a/apps/qalarm/qalarmcheck.js +++ b/apps/qalarm/qalarmcheck.js @@ -29,13 +29,12 @@ let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) .sort((a, b) => a.t - b.t); if (nextAlarms[0]) { - print("Found alarm, scheduling...", nextAlarms[0].t - t); setTimeout(() => { - load("qalarm.js"); eval(require("Storage").read("qalarmcheck.js")); - }, 3600000 * (nextAlarms[0].t - t)); + load("qalarm.js"); + }, nextAlarms[0].t - t); } else { - print("No alarms found. Will re-check at midnight."); + // No alarms found: will re-check at midnight setTimeout(() => { eval(require("Storage").read("qalarmcheck.js")); }, 86400000 - t); From 7ad7fa6c325395539ec5f5d7d8a91daab3e25f2b Mon Sep 17 00:00:00 2001 From: qucchia Date: Thu, 28 Oct 2021 16:03:01 +0200 Subject: [PATCH 213/325] Update Q Alarm --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4d2c4fdb9..12c51ff72 100644 --- a/apps.json +++ b/apps.json @@ -4172,7 +4172,7 @@ "name": "Q Alarm and Timer", "shortName": "Q Alarm", "icon": "app.png", - "version": "0.01", + "version": "0.02", "description": "Alarm and timer app with days of week and 'hard' option.", "tags": "tool,alarm,widget", "supports": ["BANGLEJS", "BANGLEJS2"], From 78ba542aa347f037ebb77b42b6d05dd15ea27377 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 15:11:35 +0100 Subject: [PATCH 214/325] oops - widget fix --- apps/recorder/widget.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 38b1d69d2..785f3b03e 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -23,9 +23,9 @@ function onGPS(f) { hasFix = f.fix; if (!hasFix) return; - lat += fix.lat; - lon += fix.lon; - alt += fix.alt; + lat += f.lat; + lon += f.lon; + alt += f.alt; samples++; } return { From ce08d82367810866ffc0d4d701f463ca9eb73ba0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 15:12:43 +0100 Subject: [PATCH 215/325] location --- apps/recorder/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 785f3b03e..df0be1d20 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -182,7 +182,7 @@ if (!writeInterval) return; g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); activeRecorders.forEach((recorder,i)=>{ - recorder.draw(this.y+15+(i>>1)*12, this.y+(i&1)*12); + recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12); }); },getRecorders:getRecorders,reload:function() { reload(); From b04bd5d158a62f65d2047c1f90c2a298d7349a84 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 16:13:11 +0100 Subject: [PATCH 216/325] 0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) --- apps.json | 5 +- apps/about/ChangeLog | 1 + apps/about/{app.js => app-bangle1.js} | 0 apps/about/app-bangle2.js | 71 +++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) rename apps/about/{app.js => app-bangle1.js} (100%) create mode 100644 apps/about/app-bangle2.js diff --git a/apps.json b/apps.json index a1c3583a8..ed9b9ca5c 100644 --- a/apps.json +++ b/apps.json @@ -111,14 +111,15 @@ { "id": "about", "name": "About", - "version": "0.09", + "version": "0.10", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ - {"name":"about.app.js","url":"app.js"}, + {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, + {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index ccc80148c..9557e448d 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -7,3 +7,4 @@ 0.07: Pressing a button now exits immediately (fix #618) 0.08: Make about (mostly) work on non-240px screens 0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021 +0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) diff --git a/apps/about/app.js b/apps/about/app-bangle1.js similarity index 100% rename from apps/about/app.js rename to apps/about/app-bangle1.js diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js new file mode 100644 index 000000000..8a0be9f3d --- /dev/null +++ b/apps/about/app-bangle2.js @@ -0,0 +1,71 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var W = g.getWidth(), H = g.getHeight(); +var ENV = process.env; +var MEM = process.memory(); +var s = require("Storage"); + +var img = atob("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"); +var imgHeight = g.imageMetrics(img).height; +var imgScroll = Math.floor(Math.random()*imgHeight); + +g.reset().setFont("6x15").setFontAlign(0,0); +g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171); +g.drawImage(img,0,24); + +function getVersion(name,file) { + var j = s.readJSON(file,1); + var v = ("object"==typeof j)?j.version:false; + return v?(name+" "+(v?"v"+v:"Unknown")):"NO "+name; +} + +var versions = [ + getVersion("Bootloader","boot.info"), + getVersion("Launcher","launch.info"), + getVersion("Settings","setting.info") +]; +var logo = E.toArrayBuffer(atob("PBwBAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAD/w+AAAAQAHA4hAAAAQAMAMhAAAAQAYBmhAAAAQAYBGiAAAAQAQCD/H74+R4wGDhoKJCSEwEDgoKJCT8wFDgoKJCSAwHDhoKJCSEQHj/H6I+R4YHmAAAACAAYEGAAABCAAMEMAAAA8AAHA4AAAAAAAD/wAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/g")); + +var imageTop = 24; + +function drawInfo() { + g.reset().clearRect(Bangle.appRect); + g.drawImage(logo,W-60,24); + g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56); + var h=8, y = 24-h; + g.setFont("6x8").setFontAlign(-1,-1); + g.drawString("Powered by Espruino",0,y+=4+h); + g.drawString("Version "+ENV.VERSION,0,y+=h); + g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); + + getVersion("Bootloader","boot.info"); + getVersion("Launcher","launch.info"); + getVersion("Settings","setting.info"); + + g.drawString(MEM.total+" JS Vars",0,y+=h); + g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); + if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); + if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); + imageTop = y+h; + imgScroll = imgHeight-imageTop; + g.reset().setFont("6x15").setFontAlign(0,0); + g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171); + + drawImage(); + setInterval(function() { + drawImage(); + g.flip(); + imgScroll = (imgScroll+1) % imgHeight; + }, 20); +} + +function drawImage() { + g.setClipRect(0,imageTop,W-1,H-14); + g.drawImage(img,0,imageTop-imgScroll); + g.drawImage(img,0,imageTop+imgHeight-imgScroll); + g.setClipRect(0,0,W-1,H-1); +} + +// TODO: a nice little animation before +setTimeout(drawInfo, 1000); From 52ffb2f3c282ab532a0e3a827282724d6f44749d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 28 Oct 2021 21:51:11 +0100 Subject: [PATCH 217/325] added more screenshots and put them into apps.json --- apps.json | 13 ++++++++++--- apps/calculator/screenshot_calculator.png | Bin 0 -> 2733 bytes apps/calendar/screenshot_calendar.png | Bin 0 -> 3866 bytes apps/cliock/screenshot_cli.png | Bin 0 -> 2115 bytes apps/gallifr/screenshot_time.png | Bin 0 -> 3807 bytes apps/pastel/screenshot_pastel.png | Bin 0 -> 4014 bytes apps/sclock/screenshot_simplec.png | Bin 0 -> 2217 bytes apps/simplest/screenshot_simplest.png | Bin 0 -> 2106 bytes apps/wclock/screenshot_word.png | Bin 0 -> 3540 bytes apps/welcome/screenshot_welcome.png | Bin 0 -> 2618 bytes apps/worldclock/screenshot_world.png | Bin 0 -> 2937 bytes 11 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 apps/calculator/screenshot_calculator.png create mode 100644 apps/calendar/screenshot_calendar.png create mode 100644 apps/cliock/screenshot_cli.png create mode 100644 apps/gallifr/screenshot_time.png create mode 100644 apps/pastel/screenshot_pastel.png create mode 100644 apps/sclock/screenshot_simplec.png create mode 100644 apps/simplest/screenshot_simplest.png create mode 100644 apps/wclock/screenshot_word.png create mode 100644 apps/welcome/screenshot_welcome.png create mode 100644 apps/worldclock/screenshot_world.png diff --git a/apps.json b/apps.json index ed9b9ca5c..d52b2c9b5 100644 --- a/apps.json +++ b/apps.json @@ -175,6 +175,7 @@ "version": "0.13", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", + "screenshots": [{"url":"screenshot_welcome.png"}], "tags": "start,welcome", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, @@ -279,6 +280,7 @@ "version": "0.03", "description": "Display Time as Text", "icon": "clock-word.png", + "screenshots": [{"url":"screenshot_word.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1090,6 +1092,7 @@ "version": "0.07", "description": "A Simple Digital Clock", "icon": "clock-simple.png", + "screenshots": [{"url":"screenshot_simplec.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1610,6 +1613,7 @@ "version": "0.14", "description": "Simple CLI-Styled Clock", "icon": "app.png", + "screenshots": [{"url":"screenshot_cli.png"}], "type": "clock", "tags": "clock,cli,command,bash,shell", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1982,6 +1986,7 @@ "version": "0.04", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "icon": "calculator.png", + "screenshots": [{"url":"screenshot_calculator.png"}], "tags": "app,tool", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ @@ -2223,6 +2228,7 @@ "version": "0.02", "description": "Simple calendar", "icon": "calendar.png", + "screenshots": [{"url":"screenshot_calendar.png"}], "tags": "calendar", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", @@ -2371,6 +2377,7 @@ "version": "0.02", "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", "icon": "gallifr.png", + "screenshots": [{"url":"screenshot_time.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -2873,6 +2880,7 @@ "version": "0.05", "description": "Current time zone plus up to four others", "icon": "app.png", + "screenshots": [{"url":"screenshot_world.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3559,6 +3567,7 @@ "version": "0.03", "description": "The simplest working clock, acts as a tutorial piece", "icon": "simplest.png", + "screenshots": [{"url":"screenshot_simplest.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3968,6 +3977,7 @@ "version": "0.05", "description": "A Configurable clock with custom fonts and background", "icon": "pastel.png", + "screenshots": [{"url":"screenshot_pastel.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4005,7 +4015,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, @@ -4022,7 +4031,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"floralclk.app.js","url":"app.js"}, @@ -4039,7 +4047,6 @@ "type": "app", "tags": "", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "storage": [ {"name":"score.app.js","url":"score.app.js"}, {"name":"score.settings.js","url":"score.settings.js"}, diff --git a/apps/calculator/screenshot_calculator.png b/apps/calculator/screenshot_calculator.png new file mode 100644 index 0000000000000000000000000000000000000000..7a259fe2c010fb9c7d2ced201506267537d76077 GIT binary patch literal 2733 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 literal 0 HcmV?d00001 diff --git a/apps/calendar/screenshot_calendar.png b/apps/calendar/screenshot_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..4507d77dd0cbf962e6f435c5d310f481a12f254c GIT binary patch literal 3866 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 Uc?BnRAa?nz%xqBQCSJt<11SArXaE2J literal 0 HcmV?d00001 diff --git a/apps/cliock/screenshot_cli.png b/apps/cliock/screenshot_cli.png new file mode 100644 index 0000000000000000000000000000000000000000..fe1c6299b4fef932353ee02149f65bd9ce18a5f1 GIT binary patch literal 2115 zcmeH}Sv=c`7RTc+MlhYG493!`QEsb6YfW1!_Hyl#P}I^TS~DHdjN1^RVOqMRX|Gay zXnWB_gSHxL{fAC+Rq44A>Xy+{F4VL0vTPkpbQi=8*Y31fu;p(ANt^NLZ*EzFc_>uC<_2 z{r-6L2rIthpalg{={GSHmse^cb9RKqZzvTC#p=%%utPy21G4dg`T5*k$Ryu32ePNo zO`Enegr@QmZM~$v`HI|AGBKxbb-4V)1JFlQ(ALxV8kQ}TNAN9|^L+zmm|!l+Ug#N4 z`bWu1C#8PN46ZnU3pfLvs(BX|Jk8fVZnfOi?#ivrg>r%dc%yxGI5CECVok<&m~_@v1&E|R=D^vJ z0T_Zc%-uJUGco*W;SImKUQP?(FtQLNCt%UoBsB}x2uoKwlgr%F>x=XufIZ-pHWS}| zLzDa-$|M+E>DUh-lkS6H!eOB%wf9_nHKZrfBn5TJsrZ~u1)`FU4A*5)dRt@&v{ANgODsf-nh+~Zp+m$7V} zZj}M*V+Xx1)r0qG^)Ui}7Xo-{@;YnagmA`(=54#5@Vuy&EKXc9Hz)^JF*tTycsh~U zGnwKoO0oOlnxAMg0k&xw`V@QT$$@0L3yQ&-n$ibWnXcG~|x5xog$A?&@IMLuZN35R7*4*2RxV@?mT8>*Yw3~@JcMl75Z+)eG8 zlr$$K!`w%lTB`~`tFIkQiUj^qQ3%Y-sqTx!S6S0Fcc`Q9PiWsBt)0Ouoi(7_JCc_$ z8sxBBbGmnPily{&1YDs;*wE~(@fprqTiZ{%0>HY61#UD8AZw6?~lSylUHS7wr=Mbez}p zv%hD7yQGoi6R}%3;Ix% z<}eCEIGXD)n(>|ft{~Dhlojf+8Qy*b3QcHlhbddHHIZ;nKb%PwRzm(l(QP6)dS8((LQF4(qe~@P zXiBb3VXgJ*;RWk|^Qp<$=ur$r7Lvay_wMdkNXLDeAFMa+$q*?z%V`VjB=_*yxI$Y{ zLV#`uK*+SxnscT-F`HJrH{9wc7y!M>H&jVC&{$jAG3LsXRl_htRj)Y9_}d6~Ks7Z( zPvTXh#fkgRxx6qM&kS?l{I%8;Cs#=Ocr7c8>(S!%#iPXaYTMi_uZGwZbLyWaecp?5 z*yiiS^P;Z=eiWsa%`o&ASVfz0iHOKNdS=5auWw$e>oM}2cGiC` zg7TgE2*rj3R4Kg_?L{|6xsYUlTOq>i+fGUy9ktcyLB?Loe2|sJvRP105|y?VF57ux zn9_`U+P;CMuaHtJ^q7t&D?hjhpYGAY2{ia=mdu$S-I5nE@6aF8;L)D11vMvwt|F-r zK+GP2!U^+o^_pVvFRgssKdwXT>)h)QfL6hD_fF>mq{ z`d)LXzX5(Hr_g(JE|7Pk8egBp`v9=)zPj)cgofSWgG-1If40tEocU3mD(JXBeq4$186cCK!h6rF zCzQDH-$LQzU*5-^0$`ONLtLFSpst$M@g)!-C9`Po|FZur5~bF8ADlgk;1Q+%3Lt@g Lp}tJ-q`W@>Y#QRL literal 0 HcmV?d00001 diff --git a/apps/gallifr/screenshot_time.png b/apps/gallifr/screenshot_time.png new file mode 100644 index 0000000000000000000000000000000000000000..2754138c45a49d8f7aa866525744342cd9b19047 GIT binary patch literal 3807 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 literal 0 HcmV?d00001 diff --git a/apps/sclock/screenshot_simplec.png b/apps/sclock/screenshot_simplec.png new file mode 100644 index 0000000000000000000000000000000000000000..a12db3ec82a8b9443b565eca26eeed33a53b1a79 GIT binary patch literal 2217 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 Ang9R* literal 0 HcmV?d00001 diff --git a/apps/wclock/screenshot_word.png b/apps/wclock/screenshot_word.png new file mode 100644 index 0000000000000000000000000000000000000000..56e0ce98f36e6a1b4152f0eb5b44491c13ec0734 GIT binary patch literal 3540 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 literal 0 HcmV?d00001 From 6668727aca1910a50ca69396bf605d22ffd87264 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 28 Oct 2021 22:34:43 +0100 Subject: [PATCH 218/325] more screenshots and remove some redundant references to README's --- apps.json | 5 +++-- apps/astroid/screenshot_asteroids.png | Bin 0 -> 1498 bytes apps/compass/screenshot_compass.png | Bin 0 -> 2632 bytes apps/matrixclock/screenshot_matrix.png | Bin 0 -> 4990 bytes apps/simplest/screenshot_simplest.png | Bin 2106 -> 2180 bytes 5 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 apps/astroid/screenshot_asteroids.png create mode 100644 apps/compass/screenshot_compass.png create mode 100644 apps/matrixclock/screenshot_matrix.png diff --git a/apps.json b/apps.json index d52b2c9b5..6c444455b 100644 --- a/apps.json +++ b/apps.json @@ -394,6 +394,7 @@ "version": "0.02", "description": "inspired by The Matrix, a clock of the same style", "icon": "matrixclock.png", + "screenshots": [{"url":"screenshot_matrix.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -448,7 +449,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, @@ -512,6 +512,7 @@ "version": "0.03", "description": "Retro asteroids game", "icon": "asteroids.png", + "screenshots": [{"url":"screenshot_asteroids.png"}], "tags": "game", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, @@ -552,6 +553,7 @@ "version": "0.04", "description": "Simple compass that points North", "icon": "compass.png", + "screenshots": [{"url":"screenshot_compass.png"}], "tags": "tool,outdoors", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ @@ -3571,7 +3573,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "storage": [ {"name":"simplest.app.js","url":"app.js"}, {"name":"simplest.img","url":"icon.js","evaluate":true} diff --git a/apps/astroid/screenshot_asteroids.png b/apps/astroid/screenshot_asteroids.png new file mode 100644 index 0000000000000000000000000000000000000000..4474c7a664b37661a5982b2872d541d103de26bd GIT binary patch literal 1498 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-ahlfpvOOu)uKV8GtEy(ObD4m`2az*QK`b0|lvckpVXC&bi;@1$VVGOfBm@*&uvKh9i^ym))Xl*O;Zw}{xb9thm?xx1gsa#!6y z(Y<~uA8x8`+BiQ@(fxXUPX71HoO`S3Q~vrp5+6dr6AU|iv) zv_YJ)U%+1F{riyjUozM3b+OEz+b{k@?cLrj{r}3ZG1gVZv($ZxW_|xj>+fCG{~3+Z zleYEk-z_7$uK$33-HeU{&%^#`)*-Quo~grl$WZ z8voa8Jb8C}_qWE^Cwv}QZNBu9nTchYYbFzmo~_+k;Tg{=4o4pA6IQ)jEyNbMEFkCve)ZubE=Wa;Ncm!89QyhX(=?sn3CF zSms=i6AOn8!?!yB?`5;hwb&TgrU~s3ezmyv?(eso&KXU6>(g*R)?xn1YU{0~dz24N zn##g)hvB+*`ukl|i;HdvGqKbyJO)aekvr!yG;;2_yNFp^V8#LVSKDo89bmbacd=2F z!{-6>*V}Tw3A+Vu>soDKYP5c^y-YmnxV4f_q-Vo{xexXTU+2vFWW>bJ@IA7FF=FyG zp$ef4LrErAB z+%cD6`d&C|-zhy8`63|8DrahK!tVp^C)F+d56D`ak8qm6(AZos^_;Lm!Mu$v3XDwb z54Gm1J3O#S=5%CY;X4#+<==3i@|aK)3x`b0>XLa3jh}l|SvUnOIKp4rFfu)t^yUD@ zkifc2l|X+OPZLmac%Tro^fRz*NY_#HXgJ`w!|ORv^{7Q9^A6Y-xZjg2I~ZIJEUXzk MUHx3vIVCg!06kBM&j0`b literal 0 HcmV?d00001 diff --git a/apps/compass/screenshot_compass.png b/apps/compass/screenshot_compass.png new file mode 100644 index 0000000000000000000000000000000000000000..63579bab72211b0d7d1a69838a6244e9b225285f GIT binary patch literal 2632 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 literal 2106 zcmeH}`%}^h7sl}`RI+QPCX?o^P0=z}*G4lLMXml;tB*FsCBUM~ub7NNpA^s@S-NS3FO!lebfQy)Q=rwdie$(^ ztz@-UaD2uFbBzH8P#@MTBDP40-`z;^SLciLwQW4bSdh#ncl0S*)zzKwg;VZ(@A5H*?7rgSRSTx=X?_)pCXl!mG4<>;WCCp+kB4=;=&3PkJ*sz-V*93Rbi2y-e4 z!B12Rnp0I__&bpKjb0R@1ob}@h;3Q7_k}A+I_R25h)#06*%{m#_j05y5rEfxJ&?|W zZWmPYFZM#W`&vKzkZj_b=1x?UZNMw?<)B3*h#9-@Y#9kAHdHn_H;8zh$B!Z#M7=2Y zn4>6qN>+8{F%-R=j$v2k0YeKu?p1k!i!m?G-3Z41a0%mO1dG^|{5uAK|FhvelWR+# z{#&n$)kO`)2At7-$0e{19#YoTo;abD)OgYA5L7}xS0r-`rTwz7A929%mc%pxNzRaR zQ#|*jO(rI}%x~d5;7CU0utWAkid;7?C8uhbH8xkTU!pdTxO;l{4i^+IdRJRx?}-Ja zUv^)iO@`^x29r+Yp5XW-b?rXCv|vj&s8WT?tce(1=u_FVomJy6;?M`BX-w>Y*mlf1 zye5%g`1q>&oa=jN;gV(T)IQmTAP-u86BF;OzMvF8TeRiEhkgodWebD!ee&3R@x(X5 zZ8M1j)N3fgi`$91d|$8j?(yjT{Ygy`_jd z=a|KL1n>iv;O0p1SWA6;W-?CxhY~=~nYlSi;Xh}u!O6(Wj5^Yv(O`Zj--Oe`9KPN@ z!}6T^%^rtL8??q4wSLp+Jp1|6UN$S|H)iKy@QB#T(aj6Z7vx2uvqzmu4HzS6gY;YE+NY(&L~&yuQ=3o8|2=nL05dTo&{jsweYz5OJ@gd z`|PMzVoshPnU`$W0UzQcsLo=IllDj)1K?lXec7R(0&ZML`F*60`DrV_5%bXa^jgIn zJ?2Jdho~t%+l?M`vy&@wBV#}8J-{>8K&WUs1r$COb)d$MgIGc67&l6@^x&`iY%haNE_+oa(~1G9$jxi~NIeKmIHzJIgMOTDx{^84=t^ zZjcpMs%(c!L>rE-rOBH4lP)e>$VLg3tc?@G9TP#3aqcS9LpwGRfQX5KmtIxf^AaxzX*M(F4%>$rux@pGc@QyX0drb$oRg|}X*MetEN zZDS4gb!{dX&Drk~dJEBY^JXSk1z}V`!cp{ap|Z!s)wB3Dq%038&tMfOIU-Ojw1a;YW{7|%C#+!zfX`qS44)?>aCWf6%l zWAjJ=yl}Tqo>PO!37aww-M#~z`hTN;ZFFBCXN>FuY=)Tu8_xhFJS5^UI~afcFUgqD Ang9R* From 516ff5b609e64a7d312efc93acf6dae53ec9ef91 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:32:42 +0100 Subject: [PATCH 219/325] make firmware update beta --- apps.json | 2 +- apps/fwupdate/custom.html | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ed9b9ca5c..4bc4f802a 100644 --- a/apps.json +++ b/apps.json @@ -1,7 +1,7 @@ [ { "id": "fwupdate", - "name": "Firmware Update", + "name": "Firmware Update (BETA)", "version": "0.01", "description": "Uploads new Espruino firmwares to Bangle.js 2", "icon": "app.png", diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 5286ef062..7230a77a8 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,6 +3,8 @@ +

Firmware updates using the App Loader are only possible on Bangle.js 2. For firmware updates on Bangle.js 1 please From 011b4bc97514d7eb7a05f1a6758f080dca0ddad3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:33:13 +0100 Subject: [PATCH 220/325] Change default apps --- bin/firmwaremaker_c.js | 2 +- defaultapps_banglejs2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 7fb842755..ec49aa19a 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,7 +29,7 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launch","s7clk","setting", + "boot","launch","antonclk","setting","health", "about","alarm","widlock","widbat","widbt","widid" ]; } else { diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json index 2d32d285c..04bd44504 100644 --- a/defaultapps_banglejs2.json +++ b/defaultapps_banglejs2.json @@ -1 +1 @@ -["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"] +["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] From f6ea1b116f1b45b4e1151d88202b3c104859f3b2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:33:31 +0100 Subject: [PATCH 221/325] Fix default app loading for Bangle.js 1 --- loader.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index c4d8d5972..61e2b1880 100644 --- a/loader.js +++ b/loader.js @@ -27,18 +27,23 @@ DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); // When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { - if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { - showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); - } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { - showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); - } + var fwURL = "#"; if (deviceId == "BANGLEJS") { + fwURL = "https://www.espruino.com/Bangle.js#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold BTN3\nto reload'; } if (deviceId == "BANGLEJS2") { + fwURL = "https://www.espruino.com/Bangle.js2#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { + showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); + } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { + showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + } + + // check against features shown? filterAppsForDevice(deviceId); /* if we'd saved a device ID but this device is different, ensure @@ -149,7 +154,7 @@ window.addEventListener('load', (event) => { document.getElementById("installdefault").addEventListener("click",event=>{ getInstalledApps().then(() => { if (device.id == "BANGLEJS") - return httpGet("defaultapps_banglejs.json"); + return httpGet("defaultapps_banglejs1.json"); if (device.id == "BANGLEJS2") return httpGet("defaultapps_banglejs2.json"); throw new Error("Unknown device "+device.id); From 2a97f49ded79407ab519426ea523e6b15cf52c37 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 10:34:14 +0100 Subject: [PATCH 222/325] health 0.02: Modified data format to include daily summaries --- apps.json | 116 +++++++++++++++++++++--------------------- apps/health/ChangeLog | 1 + apps/health/README.md | 1 - apps/health/app.js | 40 ++++++++++++++- apps/health/boot.js | 39 +++++++++++--- apps/health/lib.js | 32 ++++++++++-- 6 files changed, 158 insertions(+), 71 deletions(-) diff --git a/apps.json b/apps.json index 4bc4f802a..024fb1d54 100644 --- a/apps.json +++ b/apps.json @@ -49,7 +49,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.01", + "version": "0.02", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system", @@ -61,36 +61,7 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder": -8 - }, - { - "id": "moonphase", - "name": "Moonphase", - "version": "0.02", - "description": "Shows current moon phase. Now with GPS function.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"moonphase.app.js","url":"app.js"}, - {"name":"moonphase.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "daysl", - "name": "Days left", - "version": "0.03", - "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "allow_emulator": false, - "storage": [ - {"name":"daysl.app.js","url":"app.js"}, - {"name":"daysl.img","url":"app-icon.js","evaluate":true}, - {"name":"daysl.wid.js","url":"widget.js"} - ] + "sortorder": -2 }, { "id": "launch", @@ -108,6 +79,22 @@ ], "sortorder": -10 }, + { + "id": "setting", + "name": "Settings", + "version": "0.31", + "description": "A menu for setting up Bangle.js", + "icon": "settings.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"setting.app.js","url":"settings.js"}, + {"name":"setting.img","url":"settings-icon.js","evaluate":true} + ], + "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], + "sortorder": -5 + }, { "id": "about", "name": "About", @@ -123,6 +110,24 @@ {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, + { + "id": "alarm", + "name": "Default Alarm & Timer", + "shortName": "Alarms", + "version": "0.13", + "description": "Set and respond to alarms and timers", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"alarm.app.js","url":"app.js"}, + {"name":"alarm.boot.js","url":"boot.js"}, + {"name":"alarm.js","url":"alarm.js"}, + {"name":"alarm.img","url":"app-icon.js","evaluate":true}, + {"name":"alarm.wid.js","url":"widget.js"} + ], + "data": [{"name":"alarm.json"}] + }, { "id": "locale", "name": "Languages", @@ -240,38 +245,33 @@ "sortorder": -9 }, { - "id": "setting", - "name": "Settings", - "version": "0.31", - "description": "A menu for setting up Bangle.js", - "icon": "settings.png", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", + "id": "moonphase", + "name": "Moonphase", + "version": "0.02", + "description": "Shows current moon phase. Now with GPS function.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - {"name":"setting.app.js","url":"settings.js"}, - {"name":"setting.img","url":"settings-icon.js","evaluate":true} - ], - "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], - "sortorder": -2 + {"name":"moonphase.app.js","url":"app.js"}, + {"name":"moonphase.img","url":"app-icon.js","evaluate":true} + ] }, { - "id": "alarm", - "name": "Default Alarm & Timer", - "shortName": "Alarms", - "version": "0.13", - "description": "Set and respond to alarms and timers", + "id": "daysl", + "name": "Days left", + "version": "0.03", + "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", "icon": "app.png", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS","BANGLEJS2"], + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": false, "storage": [ - {"name":"alarm.app.js","url":"app.js"}, - {"name":"alarm.boot.js","url":"boot.js"}, - {"name":"alarm.js","url":"alarm.js"}, - {"name":"alarm.img","url":"app-icon.js","evaluate":true}, - {"name":"alarm.wid.js","url":"widget.js"} - ], - "data": [{"name":"alarm.json"}] + {"name":"daysl.app.js","url":"app.js"}, + {"name":"daysl.img","url":"app-icon.js","evaluate":true}, + {"name":"daysl.wid.js","url":"widget.js"} + ] }, { "id": "wclock", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 5560f00bc..acf786e65 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Modified data format to include daily summaries diff --git a/apps/health/README.md b/apps/health/README.md index 0ba0d8228..456143844 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -28,7 +28,6 @@ to grab historical health info. ## TODO -* **Extend file format to include combined data for each day (to make graphs faster)** * `interface` page for desktop to allow data to be viewed and exported in common formats * More features in app: * Step counting goal (ensure pedometers use this) diff --git a/apps/health/app.js b/apps/health/app.js index f2df52972..e6fea91b2 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -11,7 +11,8 @@ function menuStepCount() { E.showMenu({ "":{title:"Step Counting"}, "< Back":()=>menuMain(), - "per hour":()=>stepsPerHour() + "per hour":()=>stepsPerHour(), + "per day":()=>stepsPerDay() }); } @@ -19,7 +20,8 @@ function menuMovement() { E.showMenu({ "":{title:"Movement"}, "< Back":()=>menuMain(), - "per hour":()=>movementPerHour() + "per hour":()=>movementPerHour(), + "per day":()=>movementPerDay(), }); } @@ -40,6 +42,23 @@ function stepsPerHour() { Bangle.setUI("updown", ()=>menuStepCount()); } +function stepsPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 5, + gridy : 2000 + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + function movementPerHour() { E.showMessage("Loading..."); var data = new Uint16Array(24); @@ -57,6 +76,23 @@ function movementPerHour() { Bangle.setUI("updown", ()=>menuStepCount()); } +function movementPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 5, + ylabel : null + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + Bangle.loadWidgets(); Bangle.drawWidgets(); menuMain(); diff --git a/apps/health/boot.js b/apps/health/boot.js index d6b84ce98..0da4af086 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -4,7 +4,7 @@ Bangle.on("health", health => { const DB_RECORD_LEN = 4; const DB_RECORDS_PER_HR = 6; - const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; + const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/; const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; const DB_HEADER_LEN = 8; const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; @@ -17,6 +17,12 @@ Bangle.on("health", health => { (DB_RECORDS_PER_HR*d.getHours()) + (0|(d.getMinutes()*DB_RECORDS_PER_HR/60)); } + function getRecordData(health) { + return String.fromCharCode( + health.steps>>8,health.steps&255, // 16 bit steps + health.bpm, // 8 bit bpm + Math.min(health.movement / 8, 255)); // movement + } var rec = getRecordIdx(d); var fn = getRecordFN(d); @@ -30,9 +36,30 @@ Bangle.on("health", health => { } else { require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header } - var recordData = String.fromCharCode( - health.steps>>8,health.steps&255, // 16 bit steps - health.bpm, // 8 bit bpm - Math.min(health.movement / 8, 255)); // movement - require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN); + var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN); + require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN); + if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-1) return; + // we're at the end of the day. Read in all of the data for the day and sum it up + var sumPos = recordPos + DB_RECORD_LEN; // record after the current one is the sum + if (f.substr(sumPos, DB_RECORD_LEN)!="\xFF\xFF\xFF\xFF") { + print("HEALTH ERR: Daily summary already written!"); + return; + } + health = { steps:0, bpm:0, movement:0, records:0}; + var records = DB_RECORDS_PER_HR*24; + for (var i=0;i Date: Fri, 29 Oct 2021 13:55:57 +0100 Subject: [PATCH 223/325] banglejs2 support for widgets --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index a3c77ff00..edc70c8ee 100644 --- a/apps.json +++ b/apps.json @@ -1424,7 +1424,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widclk.wid.js","url":"widget.js"} ] @@ -1633,7 +1633,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,tool,system", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widver.wid.js","url":"widget.js"} ] @@ -1788,7 +1788,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,tools", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widmp.wid.js","url":"widget.js"} ] @@ -3439,7 +3439,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widgps.wid.js","url":"widget.js"} @@ -3965,7 +3965,7 @@ "icon": "widclkbttm.png", "type": "widget", "tags": "widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} From f2f500e474109e91b8f83d5d2e76ee79bb09c194 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 29 Oct 2021 18:35:55 +0200 Subject: [PATCH 224/325] menusmall: add `wrap` option --- apps.json | 2 +- apps/menusmall/ChangeLog | 1 + apps/menusmall/boot.js | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index edc70c8ee..ab56b2429 100644 --- a/apps.json +++ b/apps.json @@ -4059,7 +4059,7 @@ { "id": "menusmall", "name": "Small Menus", - "version": "0.01", + "version": "0.02", "description": "Replace Bangle.js 2's menus with a version that contains smaller text", "icon": "app.png", "type": "boot", diff --git a/apps/menusmall/ChangeLog b/apps/menusmall/ChangeLog index 5560f00bc..2f2e25462 100644 --- a/apps/menusmall/ChangeLog +++ b/apps/menusmall/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: add `wrap` option \ No newline at end of file diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 59e47b178..7ee3dfda1 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -100,8 +100,10 @@ E.showMenu = function(items) { if (l.selectEdit) { var item = l.selectEdit; item.value -= (dir||1)*(item.step||1); - if (item.min!==undefined && item.valueitem.max) item.value = item.max; + if (item.min!==undefined && item.valueitem.max) + item.value = (item.wrap && item.min!==undefined) ? item.min : item.max; if (item.onchange) item.onchange(item.value); l.draw(options.selected,options.selected); } else { From 310bf60ed76bd7e36380ca6725a12c4ad6dc2efd Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 29 Oct 2021 18:45:30 +0200 Subject: [PATCH 225/325] menusmall: use Bangle.appRect --- apps/menusmall/ChangeLog | 2 +- apps/menusmall/boot.js | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/menusmall/ChangeLog b/apps/menusmall/ChangeLog index 2f2e25462..6de3d41f4 100644 --- a/apps/menusmall/ChangeLog +++ b/apps/menusmall/ChangeLog @@ -1,2 +1,2 @@ 0.01: New App! -0.02: add `wrap` option \ No newline at end of file +0.02: add `wrap` option, use Bangle.appRect \ No newline at end of file diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 7ee3dfda1..805413e2b 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -1,28 +1,23 @@ "";//not entirely sure why we need this - related to how bootupdate adds these to .boot0 E.showMenu = function(items) { - g.clear(1).flip(); // clear screen if no menu supplied - Bangle.drawWidgets(); + g.clearRect(Bangle.appRect); // clear screen if no menu supplied if (!items) { Bangle.setUI(); return; } - var w = g.getWidth(); - var h = g.getHeight(); + var menuItems = Object.keys(items); var options = items[""]; if (options) menuItems.splice(menuItems.indexOf(""),1); if (!(options instanceof Object)) options = {}; - options.fontHeight=14; - options.x=0; - options.x2=w-1; - options.y=24; - options.y2=h-12; + options.fontHeight = options.fontHeight|14; if (options.selected === undefined) options.selected = 0; - var x = 0|options.x; - var x2 = options.x2||(g.getWidth()-1); - var y = 0|options.y; - var y2 = options.y2||(g.getHeight()-1); + var ar = Bangle.appRect; + var x = ar.x; + var x2 = ar.x2; + var y = ar.y; + var y2 = ar.y2 - 11; // padding at end for arrow if (options.title) y += 15; var loc = require("locale"); From aeaf508897cf5c6434b947a3291ae479ce6ed330 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 20:21:12 +0100 Subject: [PATCH 226/325] general widget fixes and tweaks --- apps.json | 6 +++--- apps/widgps/ChangeLog | 1 + apps/widgps/widget.js | 6 +++--- apps/widmp/ChangeLog | 2 ++ apps/widmp/widget.js | 41 +++++++++++++++++------------------------ apps/widver/ChangeLog | 1 + apps/widver/widget.js | 20 +++++++------------- 7 files changed, 34 insertions(+), 43 deletions(-) diff --git a/apps.json b/apps.json index edc70c8ee..7316c7802 100644 --- a/apps.json +++ b/apps.json @@ -1628,7 +1628,7 @@ { "id": "widver", "name": "Firmware Version Widget", - "version": "0.02", + "version": "0.03", "description": "Display the version of the installed firmware in the top widget section.", "icon": "widget.png", "type": "widget", @@ -1783,7 +1783,7 @@ { "id": "widmp", "name": "Moon Phase Widget", - "version": "0.01", + "version": "0.02", "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", "icon": "widget.png", "type": "widget", @@ -3434,7 +3434,7 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.02", + "version": "0.03", "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", "icon": "widget.png", "type": "widget", diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index d80e09912..57bb53bb7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -1,2 +1,3 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) +0.03: Fix positioning diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 19be2abaf..6ef55e27b 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -4,11 +4,11 @@ function draw() { g.reset(); if (Bangle.isGPSOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor("#FD0"); // on = amber } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor("#888"); // off = grey } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), 10+this.x, 2+this.y); + g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); } var timerInterval; diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 5560f00bc..3996d9e74 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Fix position and overdraw bugs + Better memory usage, theme support diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index cebdb60f5..d8abc3a9c 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,20 +1,6 @@ -/* jshint esversion: 6 */ -(() => { - - const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09; - var r = 12, mx = 0, my = 0; - - var moon = { - 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, - 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, - 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, - 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, - 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);}, - 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} - }; +WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { + const MC = 29.5305882, NM = 694039.09; + var r = 11, mx = this.x + 12; my = this.y + 12; function moonPhase(d) { var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); @@ -23,11 +9,18 @@ return Math.round(((tmp - (tmp | 0)) * 7)+1); } - function draw() { - mx = this.x; my = this.y + 12; - moon[moonPhase(Date())](); - } + const BLACK = g.theme.bg, MOON = 0x41f; + var moon = { + 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, + 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, + 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, + 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, + 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r, my + r);}, + 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} + }; + moon[moonPhase(Date())](); +} }; - WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw }; - -})(); diff --git a/apps/widver/ChangeLog b/apps/widver/ChangeLog index 30581c35b..06bf45fbd 100644 --- a/apps/widver/ChangeLog +++ b/apps/widver/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget 0.02: Display "Rel" (Release) instead of 'undefined' when there is no Build number. +0.03: Add theme support, lower memory usage diff --git a/apps/widver/widget.js b/apps/widver/widget.js index eb751ca23..b6a8b7432 100644 --- a/apps/widver/widget.js +++ b/apps/widver/widget.js @@ -1,15 +1,9 @@ -/* jshint esversion: 6 */ -(() => { - var width = 28, - ver = process.env.VERSION.split('.'); - +WIDGETS["version"] = { area: "tr", width: 28, draw: function() { + var ver = process.env.VERSION.split('.'); // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number - if(typeof ver[1] === 'undefined'){ver[1] = "Rel";} + if(typeof ver[1] === 'undefined') ver[1] = "Rel"; - function draw() { - g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); - g.drawString(ver[0], this.x + 2, this.y + 4, true); - g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + width / 2, this.y + 14, true); - } - WIDGETS["version"] = { area: "tr", width: width, draw: draw }; -})(); + g.reset().setColor((g.getBPP()<8)?(g.theme.dark?"#0ff":"#00f"):"#08f").setFont("6x8"); + g.drawString(ver[0], this.x + 2, this.y + 4, true); + g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + this.width / 2, this.y + 14, true); +} }; From 6d27159cddb910921217cb2c4ca74fa14cb5d887 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 20:50:51 +0100 Subject: [PATCH 227/325] health: Settings to turn HRM on --- apps.json | 2 +- apps/health/ChangeLog | 1 + apps/health/README.md | 10 +++++++++- apps/health/app.js | 25 ++++++++++++++++++++++++- apps/health/boot.js | 17 +++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 7316c7802..e3e65a296 100644 --- a/apps.json +++ b/apps.json @@ -49,7 +49,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.02", + "version": "0.03", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index acf786e65..9ce15a982 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Modified data format to include daily summaries +0.03: Settings to turn HRM on diff --git a/apps/health/README.md b/apps/health/README.md index 456143844..c69e2e45b 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -14,10 +14,18 @@ To view data, run the `Health` app from your watch. Stores: -* Heart rate (TODO) +* Heart rate * Step count * Movement +## Settings + +* **Heart Rt** - Whether to monitor heart rate or not + * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget + * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found + * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours) + + ## Technical Info Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and diff --git a/apps/health/app.js b/apps/health/app.js index e6fea91b2..6cc46298b 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,9 +1,32 @@ +function getSettings() { + return require("Storage").readJSON("health.json",1)||{}; +} + +function setSettings(s) { + require("Storage").writeJSON("health.json",s); +} + function menuMain() { E.showMenu({ "":{title:"Health Tracking"}, "< Back":()=>load(), "Step Counting":()=>menuStepCount(), - "Movement":()=>menuMovement() + "Movement":()=>menuMovement(), + "Settings":()=>menuSettings() + }); +} + +function menuSettings() { + var s=getSettings(); + E.showMenu({ + "":{title:"Health Tracking"}, + "< Back":()=>load(), + "Heart Rt":{ + value : 0|s.hrm, + min : 0, max : 2, + format : v=>["Off","10 mins","Always"][v], + onchange : v => { s.hrm=v;setSettings(s); } + } }); } diff --git a/apps/health/boot.js b/apps/health/boot.js index 0da4af086..200a89f67 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -1,3 +1,20 @@ +(function(){ + var settings = require("Storage").readJSON("health.json",1)||{}; + var hrm = 0|settings.hrm; + Bangle.setHRMPower(hrm!=0, "health"); + if (hrm==1) { + function onHealth() { + Bangle.setHRMPower(1, "health"); + setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes + } + Bangle.on("health", onHealth); + Bangle.on('HRM', h => { + if (h.confidence>80) Bangle.setHRMPower(0, "health"); + }); + onHealth(); + } +})(); + Bangle.on("health", health => { // ensure we write health info for *last* block var d = new Date(Date.now() - 590000); From 77a74fbeb7a2f7e446ab09d3451cfe7ef6e7aa4d Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Fri, 29 Oct 2021 17:37:33 -0400 Subject: [PATCH 228/325] Update README.md Updated links to travis-ci.org to app.travis-ci.com per "ORG Shutdown" https://blog.travis-ci.com/2021-05-07-orgshutdown --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 531114a34..21f5dbff9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ Bangle.js App Loader (and Apps) ================================ -[![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) +[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps) * Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, From c0c8f36573af70a78572f38d59f061ad4a42f272 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 1 Nov 2021 09:54:28 +0000 Subject: [PATCH 229/325] health 0.04: Add HRM graph view Don't restart HRM when changing apps if we've already got a good BPM value --- apps.json | 2 +- apps/health/ChangeLog | 2 ++ apps/health/app.js | 65 +++++++++++++++++++++++++++++++++++++++---- apps/health/boot.js | 4 +-- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/apps.json b/apps.json index bdc9cc7bf..494b98766 100644 --- a/apps.json +++ b/apps.json @@ -49,7 +49,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.03", + "version": "0.04", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 9ce15a982..a21d76fd1 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Modified data format to include daily summaries 0.03: Settings to turn HRM on +0.04: Add HRM graph view + Don't restart HRM when changing apps if we've already got a good BPM value diff --git a/apps/health/app.js b/apps/health/app.js index 6cc46298b..4d692e638 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -12,6 +12,7 @@ function menuMain() { "< Back":()=>load(), "Step Counting":()=>menuStepCount(), "Movement":()=>menuMovement(), + "Heart Rate":()=>menuHRM(), "Settings":()=>menuSettings() }); } @@ -20,7 +21,7 @@ function menuSettings() { var s=getSettings(); E.showMenu({ "":{title:"Health Tracking"}, - "< Back":()=>load(), + "< Back":()=>menuMain(), "Heart Rt":{ value : 0|s.hrm, min : 0, max : 2, @@ -48,6 +49,16 @@ function menuMovement() { }); } +function menuHRM() { + E.showMenu({ + "":{title:"Heart Rate"}, + "< Back":()=>menuMain(), + "per hour":()=>hrmPerHour(), + "per day":()=>hrmPerDay(), + }); +} + + function stepsPerHour() { E.showMessage("Loading..."); var data = new Uint16Array(24); @@ -67,7 +78,7 @@ function stepsPerHour() { function stepsPerDay() { E.showMessage("Loading..."); - var data = new Uint16Array(24); + var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); g.clear(1); Bangle.drawWidgets(); @@ -82,6 +93,50 @@ function stepsPerDay() { Bangle.setUI("updown", ()=>menuStepCount()); } +function hrmPerHour() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + var cnt = new Uint8Array(23); + require("health").readDay(new Date(), h=>{ + data[h.hr]+=h.bpm; + if (h.bpm) cnt[h.hr]++; + }); + data.forEach((d,i)=>data[i] = d/cnt[i]); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 6, + gridy : 20 + }); + Bangle.setUI("updown", ()=>menuHRM()); +} + +function hrmPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(31); + var cnt = new Uint8Array(31); + require("health").readDailySummaries(new Date(), h=>{ + data[h.day]+=h.bpm; + if (h.bpm) cnt[h.day]++; + }); + data.forEach((d,i)=>data[i] = d/cnt[i]); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 5, + gridy : 20 + }); + Bangle.setUI("updown", ()=>menuHRM()); +} + function movementPerHour() { E.showMessage("Loading..."); var data = new Uint16Array(24); @@ -96,12 +151,12 @@ function movementPerHour() { gridx : 6, ylabel : null }); - Bangle.setUI("updown", ()=>menuStepCount()); + Bangle.setUI("updown", ()=>menuMovement()); } function movementPerDay() { E.showMessage("Loading..."); - var data = new Uint16Array(24); + var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); g.clear(1); Bangle.drawWidgets(); @@ -113,7 +168,7 @@ function movementPerDay() { gridx : 5, ylabel : null }); - Bangle.setUI("updown", ()=>menuStepCount()); + Bangle.setUI("updown", ()=>menuMovement()); } Bangle.loadWidgets(); diff --git a/apps/health/boot.js b/apps/health/boot.js index 200a89f67..17fbaaaa8 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -1,7 +1,6 @@ (function(){ var settings = require("Storage").readJSON("health.json",1)||{}; var hrm = 0|settings.hrm; - Bangle.setHRMPower(hrm!=0, "health"); if (hrm==1) { function onHealth() { Bangle.setHRMPower(1, "health"); @@ -11,8 +10,9 @@ Bangle.on('HRM', h => { if (h.confidence>80) Bangle.setHRMPower(0, "health"); }); + if (Bangle.getHealthStatus().bpmConfidence) return; onHealth(); - } + } else Bangle.setHRMPower(hrm!=0, "health"); })(); Bangle.on("health", health => { From ed9daa139afe6076b4d65ec499da3383aa826d52 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 1 Nov 2021 11:38:58 +0000 Subject: [PATCH 230/325] ensure non-ascii is converted properly --- bin/firmwaremaker_c.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index ec49aa19a..28fbd2f05 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -102,7 +102,13 @@ function fileGetter(url) { fs.writeFileSync(url, code); } } - return Promise.resolve(fs.readFileSync(url).toString("binary")); + var blob = fs.readFileSync(url); + var data; + if (url.endsWith(".js") || url.endsWith(".json")) + data = blob.toString(); // allow JS/etc to be written in UTF-8 + else + data = blob.toString("binary") + return Promise.resolve(data); } // If file should be evaluated, try and do it... From 6f5b902adb13c97186a67ea27a07b8fc1f568e5e Mon Sep 17 00:00:00 2001 From: jeffmer Date: Mon, 1 Nov 2021 17:46:50 +0000 Subject: [PATCH 231/325] multiclock - new bangle 2 compatible --- apps.json | 18 ++-- apps/multiclock/ChangeLog | 23 +++-- apps/multiclock/README.md | 52 ----------- apps/multiclock/{ana.js => ana.face.js} | 46 +++++----- apps/multiclock/anaface.jpg | Bin 44568 -> 0 bytes apps/multiclock/apps_entry.json | 19 ---- apps/multiclock/big.face.js | 31 +++++++ apps/multiclock/big.js | 32 ------- apps/multiclock/bigface.jpg | Bin 40453 -> 0 bytes .../{multiclock-icon.js => clock-icon.js} | 2 +- apps/multiclock/clock.img | Bin 0 -> 1156 bytes apps/multiclock/clock.info | 1 + apps/multiclock/clock.js | 69 -------------- apps/multiclock/{multiclock.png => clock.png} | Bin apps/multiclock/digi.face.js | 38 ++++++++ apps/multiclock/digi.js | 33 ------- apps/multiclock/digiface.jpg | Bin 48073 -> 0 bytes apps/multiclock/dk.face.js | 40 +++++++++ apps/multiclock/multiclock.app.js | 85 ++++++++++++++++++ apps/multiclock/nifty.face.js | 55 ++++++++++++ apps/multiclock/ped.js | 41 --------- apps/multiclock/timdat.js | 47 ---------- apps/multiclock/{txt.js => txt.face.js} | 37 ++++---- apps/multiclock/txtface.jpg | Bin 51801 -> 0 bytes 24 files changed, 316 insertions(+), 353 deletions(-) delete mode 100644 apps/multiclock/README.md rename apps/multiclock/{ana.js => ana.face.js} (59%) delete mode 100644 apps/multiclock/anaface.jpg delete mode 100644 apps/multiclock/apps_entry.json create mode 100644 apps/multiclock/big.face.js delete mode 100644 apps/multiclock/big.js delete mode 100644 apps/multiclock/bigface.jpg rename apps/multiclock/{multiclock-icon.js => clock-icon.js} (90%) create mode 100644 apps/multiclock/clock.img create mode 100644 apps/multiclock/clock.info delete mode 100644 apps/multiclock/clock.js rename apps/multiclock/{multiclock.png => clock.png} (100%) create mode 100644 apps/multiclock/digi.face.js delete mode 100644 apps/multiclock/digi.js delete mode 100644 apps/multiclock/digiface.jpg create mode 100644 apps/multiclock/dk.face.js create mode 100644 apps/multiclock/multiclock.app.js create mode 100644 apps/multiclock/nifty.face.js delete mode 100644 apps/multiclock/ped.js delete mode 100644 apps/multiclock/timdat.js rename apps/multiclock/{txt.js => txt.face.js} (56%) delete mode 100644 apps/multiclock/txtface.jpg diff --git a/apps.json b/apps.json index 494b98766..edf21dd2d 100644 --- a/apps.json +++ b/apps.json @@ -2683,22 +2683,22 @@ { "id": "multiclock", "name": "Multi Clock", - "version": "0.13", + "version": "0.07", "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", "icon": "multiclock.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "storage": [ - {"name":"multiclock.app.js","url":"clock.js"}, - {"name":"big.face.js","url":"big.js"}, - {"name":"ana.face.js","url":"ana.js"}, - {"name":"digi.face.js","url":"digi.js"}, - {"name":"txt.face.js","url":"txt.js"}, - {"name":"timdat.face.js","url":"timdat.js"}, - {"name":"ped.face.js","url":"ped.js"}, + {"name":"multiclock.app.js","url":"multiclock.app.js"}, + {"name":"big.face.js","url":"big.face.js"}, + {"name":"ana.face.js","url":"ana.face.js"}, + {"name":"digi.face.js","url":"digi.face.js"}, + {"name":"txt.face.js","url":"txt.face.js"}, + {"name":"dk.face.js","url":"dk.face.js"}, + {"name":"nifty.face.js","url":"nifty.face.js"}, {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} ] }, diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index 2f27f7f28..f40f04031 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -1,13 +1,10 @@ -0.01: New App! -0.02: Separate *.face.js files for faces -0.03: Renaming -0.04: Bug Fixes -0.05: Add README -0.06: Add txt clock -0.07: Add Time Date clock and fix font sizes -0.08: Add pinned clock face -0.09: Added Pedometer clock -0.10: Added GPS and Grid Ref clock faces -0.11: Updated Pedometer clock to retrieve steps from either wpedom or activepedom -0.12: Removed GPS and Grid Ref clock faces, superceded by GPS setup and Walkers Clock -0.13: Localised digi.js and timdat.js \ No newline at end of file +0.01: Initial version +0.02: Add pinned clock facility +0.03: Lnng touch switch to night clock - ANCS off, dimmed +0.04: use theme, font heights etc +0.05: make Bangle compatible +0.06: add minute tick for efficiency and nifty A clock +0.07: compatible with Bang;e.js 2 + + + diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md deleted file mode 100644 index b1773b8df..000000000 --- a/apps/multiclock/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Multiclock - -This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast and preserves state such as bluetooth connections. Currently there are four clock faces as shown below. To my eye, these faces look better when widgets are hidden using **widviz**. - -### Analog Clock Face -![](anaface.jpg) - -### Digital Clock Face -![](digiface.jpg) - -### Big Digit Clock Face -![](bigface.jpg) - -### Text Clock Face -![](txtface.jpg) - -### Time and Date Clock Face - - -## Controls -Clock faces are kept in a circular list. - -*BTN1* - switches to the next clock face. - -*BTN2* - switches to the app launcher. - -*BTN3* - switches to the previous clock face. - -## Adding a new face -Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure: - -``` -(() => { - function getFace(){ - function onSecond(){ - //draw digits, hands etc - } - function drawAll(){ - //draw background + initial state of digits, hands etc - } - return {init:drawAll, tick:onSecond}; - } - return getFace; -})(); -``` -For those familiar with the structure of widgets, this is similar, however, there is an additional level of function nesting. This means that although faces are loaded when the clock app starts running they are not instantiated until their `getFace` function is called, i.e. memory is not allocated to structures such as image buffer arrays declared in `getFace` until the face is selected. Consequently, adding a face does not require a lot of extra memory. - -The app at start up loads all files `*.face.js`. The simplest way of adding a face is thus to load it into `Storage` using the WebIDE. Similarly, to remove an unwanted face, simply delete it from `Storage` using the WebIDE. - -## Support - -Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev). \ No newline at end of file diff --git a/apps/multiclock/ana.js b/apps/multiclock/ana.face.js similarity index 59% rename from apps/multiclock/ana.js rename to apps/multiclock/ana.face.js index 4fd5a7251..af1c84c9f 100644 --- a/apps/multiclock/ana.js +++ b/apps/multiclock/ana.face.js @@ -5,54 +5,60 @@ const p = Math.PI/2; const PRad = Math.PI/180; + var cx = g.getWidth()/2; + var cy = 12+g.getHeight()/2; + var scale = (g.getHeight()-24)/(240-24); + scale = scale>=1 ? 1 : scale; + function seconds(angle, r) { const a = angle*PRad; - const x = 120+Math.sin(a)*r; - const y = 134-Math.cos(a)*r; + const x = cx+Math.sin(a)*r; + const y = cy-Math.cos(a)*r; if (angle % 90 == 0) { - g.setColor(0,1,1); + g.setColor(g.theme.fg2); g.fillRect(x-6,y-6,x+6,y+6); } else if (angle % 30 == 0){ - g.setColor(0,1,1); + g.setColor(g.theme.fg); g.fillRect(x-4,y-4,x+4,y+4); } else { - g.setColor(1,1,1); + g.setColor(g.theme.fg); g.fillRect(x-1,y-1,x+1,y+1); } } function hand(angle, r1,r2, r3) { + r1 = scale*r1; r2=scale*r2; r3 = scale*r3; const a = angle*PRad; g.fillPoly([ - 120+Math.sin(a)*r1, - 134-Math.cos(a)*r1, - 120+Math.sin(a+p)*r3, - 134-Math.cos(a+p)*r3, - 120+Math.sin(a)*r2, - 134-Math.cos(a)*r2, - 120+Math.sin(a-p)*r3, - 134-Math.cos(a-p)*r3]); + cx+Math.sin(a)*r1, + cy-Math.cos(a)*r1, + cx+Math.sin(a+p)*r3, + cy-Math.cos(a+p)*r3, + cx+Math.sin(a)*r2, + cy-Math.cos(a)*r2, + cx+Math.sin(a-p)*r3, + cy-Math.cos(a-p)*r3]); } var minuteDate; var secondDate; function onSecond() { - g.setColor(0,0,0); + g.setColor(g.theme.bg); hand(360*secondDate.getSeconds()/60, -5, 90, 3); if (secondDate.getSeconds() === 0) { hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7); hand(360*minuteDate.getMinutes()/60, -16, 86, 7); minuteDate = new Date(); } - g.setColor(1,1,1); + g.setColor(g.theme.fg); hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7); hand(360*minuteDate.getMinutes()/60, -16, 86, 7); - g.setColor(0,1,1); + g.setColor(g.theme.fg2); secondDate = new Date(); hand(360*secondDate.getSeconds()/60, -5, 90, 3); - g.setColor(0,0,0); - g.fillCircle(120,134,2); + g.setColor(g.theme.bg); + g.fillCircle(cx,cy,2); } function drawAll() { @@ -60,11 +66,11 @@ // draw seconds g.setColor(1,1,1); for (let i=0;i<60;i++) - seconds(360*i/60, 100); + seconds(360*i/60, 100*scale); onSecond(); } - return {init:drawAll, tick:onSecond}; + return {init:drawAll, tick:onSecond, tickpersec:true}; } return getFace; diff --git a/apps/multiclock/anaface.jpg b/apps/multiclock/anaface.jpg deleted file mode 100644 index 86aaccd5496cbd435baa8bc522eec38b71e768ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44568 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 GfB)IuGe&O! diff --git a/apps/multiclock/apps_entry.json b/apps/multiclock/apps_entry.json deleted file mode 100644 index 6383609c1..000000000 --- a/apps/multiclock/apps_entry.json +++ /dev/null @@ -1,19 +0,0 @@ -{ "id": "multiclock", - "name": "Multi Clock", - "icon": "multiclock.png", - "version":"0.06", - "description": "Clock with multiple faces - Big, Analogue, Digital, Text.\n Switch between faces with BT1 & BTN3", - "readme": "README.md", - "tags": "clock", - "type":"clock", - "allow_emulator":false, - "storage": [ - {"name":"multiclock.app.js","url":"clock.min.js"}, - {"name":"big.face.js","url":"big.min.js"}, - {"name":"ana.face.js","url":"ana.min.js"}, - {"name":"digi.face.js","url":"digi.min.js"}, - {"name":"txt.face.js","url":"txt.min.js"}, - {"name":"ped.face.js","url":"ped.js"}, - {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} - ] - }, diff --git a/apps/multiclock/big.face.js b/apps/multiclock/big.face.js new file mode 100644 index 000000000..2db4ee4d4 --- /dev/null +++ b/apps/multiclock/big.face.js @@ -0,0 +1,31 @@ +(() => { + + function getFace(){ + + const W = g.getWidth(); + const H = g.getHeight(); + const F = 132*H/240; // reasonable approximation + + function drawTime() { + d = new Date() + g.reset(); + var da = d.toString().split(" "); + var time = da[4].substr(0, 5).split(":"); + var hours = time[0], + minutes = time[1]; + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFont("Vector",F); + g.setFontAlign(0,-1); + g.drawString(hours,W/2,24,true); + g.setColor(g.theme.fg2); + g.drawString(minutes,W/2,12+H/2,true); + } + + + return {init:drawTime, tick:drawTime, tickpersecond:false}; + } + + return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/big.js b/apps/multiclock/big.js deleted file mode 100644 index 2e83d8fb5..000000000 --- a/apps/multiclock/big.js +++ /dev/null @@ -1,32 +0,0 @@ -(() => { - - function getFace(){ - - function drawTime(d) { - g.reset(); - var da = d.toString().split(" "); - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - g.clearRect(0,24,239,239); - g.setColor(1,1,1); - g.setFont("Vector",132); - g.drawString(hours,50,24,true); - g.drawString(minutes,50,132,true); - } - - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; - } - - return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/bigface.jpg b/apps/multiclock/bigface.jpg deleted file mode 100644 index 6857268644e9f09aca81bdd2b8ef32c9633b2f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40453 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 diff --git a/apps/multiclock/multiclock-icon.js b/apps/multiclock/clock-icon.js similarity index 90% rename from apps/multiclock/multiclock-icon.js rename to apps/multiclock/clock-icon.js index 41a59f503..bad6313ba 100644 --- a/apps/multiclock/multiclock-icon.js +++ b/apps/multiclock/clock-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) +require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) \ No newline at end of file diff --git a/apps/multiclock/clock.img b/apps/multiclock/clock.img new file mode 100644 index 0000000000000000000000000000000000000000..57e0a935f35d622d52dfb29a3e2af584d16eb56f GIT binary patch literal 1156 zcmchVy-veG5QNQ7K}V4nu>1mvce z)9La!{o86-w`QbVSCN1>fqCVZO@-spY-D&7!0yd_z2H*?v#=QgDnQN|*&omN`P>w= z_D&J!_9Y?65GOYYm3rjUA)G^9`EYPvGNn^ObDcePAmk^V<22I2iGJi|EB!PU@9(`0 zw#)-%SGIKBF#^Sh!P|X$FACES.push(eval(require("Storage").read(face)))); -var lastface = STOR.readJSON("multiclock.json")||{pinned:0}; -var iface = lastface.pinned; -var face = FACES[iface](); -var intervalRefSec; - -function stopdraw() { - if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} -} - -function startdraw() { - g.clear(); - g.reset(); - Bangle.drawWidgets(); - face.init(); - intervalRefSec = setInterval(face.tick,1000); -} - -function setButtons(){ - function newFace(inc){ - var n = FACES.length-1; - iface+=inc; - iface = iface>n?0:iface<0?n:iface; - stopdraw(); - face = FACES[iface](); - startdraw(); - } - function finish(){ - if (lastface.pinned!=iface){ - lastface.pinned=iface; - STOR.write("multiclock.json",lastface); - } - Bangle.showLauncher(); - } - setWatch(finish, BTN2, {repeat:false,edge:"falling"}); - setWatch(newFace.bind(null,1), BTN1, {repeat:true,edge:"rising"}); - setWatch(newFace.bind(null,-1), BTN3, {repeat:true,edge:"rising"}); -} - -var SCREENACCESS = { - withApp:true, - request:function(){ - this.withApp=false; - stopdraw(); - clearWatch(); - }, - release:function(){ - this.withApp=true; - startdraw(); - setButtons(); - } -}; - -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) { - startdraw(); - } else { - stopdraw(); - } -}); - -g.clear(); -Bangle.loadWidgets(); -startdraw(); -setButtons(); - diff --git a/apps/multiclock/multiclock.png b/apps/multiclock/clock.png similarity index 100% rename from apps/multiclock/multiclock.png rename to apps/multiclock/clock.png diff --git a/apps/multiclock/digi.face.js b/apps/multiclock/digi.face.js new file mode 100644 index 000000000..1a72c1964 --- /dev/null +++ b/apps/multiclock/digi.face.js @@ -0,0 +1,38 @@ +(() => { + +function getFace(){ + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = w/240; + + var buf = Graphics.createArrayBuffer(W,92,1,{msb:true}); + function flip() { + g.setColor(g.theme.fg); + g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,H/2-46); + } + + var W = g.getWidth(); + var H = g.getHeight(); + + function drawTime() { + buf.clear(); + buf.setColor(1); + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4]; + buf.setFont("Vector",54*scale); + buf.setFontAlign(0,-1); + buf.drawString(time,W/2,0); + buf.setFont("6x8",2); + buf.setFontAlign(0,-1); + var date = d.toString().substr(0,15); + buf.drawString(date, W/2, 70); + flip(); + } + return {init:drawTime, tick:drawTime, tickpersec:true}; +} + +return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/digi.js b/apps/multiclock/digi.js deleted file mode 100644 index 0b2ca4aaa..000000000 --- a/apps/multiclock/digi.js +++ /dev/null @@ -1,33 +0,0 @@ -(() => { - -var locale = require("locale"); - -function getFace(){ - - var buf = Graphics.createArrayBuffer(240,92,1,{msb:true}); - function flip() { - g.setColor(1,1,1); - g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,85); - } - - function drawTime() { - buf.clear(); - buf.setColor(1); - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4]; - buf.setFont("Vector",54); - buf.setFontAlign(0,-1); - buf.drawString(time,buf.getWidth()/2,0); - buf.setFont("6x8",2); - buf.setFontAlign(0,-1); - var date = locale.dow(d, 1) + " " + locale.date(d, 1); - buf.drawString(date, buf.getWidth()/2, 70); - flip(); - } - return {init:drawTime, tick:drawTime}; -} - -return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/digiface.jpg b/apps/multiclock/digiface.jpg deleted file mode 100644 index b0323bd55d1e9947e5573171b77d851445c5b8f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48073 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 { + function getFace(){ + + const locale = require("locale"); + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + + function drawClock(){ + var now=Date(); + d=now.toString().split(' '); + var min=d[4].substr(3,2); + var sec=d[4].substr(-2); + var tm=d[4].substring(0,5); + var hr=d[4].substr(0,2); + lastmin=min; + g.reset(); + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFontAlign(0,-1); + g.setFontVector(80*scale); + g.drawString(tm,4+W/2,H/2-80*scale); + g.setFontVector(36*scale); + g.setColor(g.theme.fg2); + d[1] = locale.month(now,3); + d[0] = locale.dow(now,3); + var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3]; + g.drawString(dt,W/2,H/2); + g.flip(); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/multiclock.app.js b/apps/multiclock/multiclock.app.js new file mode 100644 index 000000000..af0ee06b9 --- /dev/null +++ b/apps/multiclock/multiclock.app.js @@ -0,0 +1,85 @@ +var FACES = []; +var STOR = require("Storage"); +STOR.list(/\.face\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face)))); +var lastface = STOR.readJSON("clock.json") || {pinned:0} +var iface = lastface.pinned; +var face = FACES[iface](); +var intervalRefSec; +var intervalRefSec; +var tickTimeout; + +function stopdraw() { + if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} + if(tickTimeout) {tickTimeout=clearTimeout(tickTimeout);} + g.clear(); +} + +function queueMinuteTick(f) { + if (tickTimeout) clearTimeout(drawTimeout); + tickTimeout = setTimeout(function() { + tickTimeout = undefined; + f(); + }, 60000 - (Date.now() % 60000)); +} + +function startdraw() { + g.reset(); + face.init(); + if (face.tickpersec) + intervalRefSec = setInterval(face.tick,1000); + else + queueMinuteTick(face.tick); + wOS.drawWidgets(); +} + +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stopdraw(); + }, + release:function(){ + this.withapp=true; + startdraw(); + setButtons(); + } +}; + +Bangle.on('lcdPower',function(b) { + if (!SCREENACCESS.withApp) return; + if (b) { + startdraw(); + } else { + stopdraw(); + } +}); + +function setButtons(){ + function newFace(inc){ + if (!inc) Bangle.showLauncher(); + else { + var n = FACES.length-1; + iface+=inc; + iface = iface>n?0:iface<0?n:iface; + stopdraw(); + face = FACES[iface](); + startdraw(); + } + } + Bangle.setUI("leftright", newFace); +} + +E.on('kill',()=>{ + if (iface!=lastface.pinned){ + lastface.pinned=iface; + STOR.write("clock.json",lastface); + } +}); + +Bangle.loadWidgets(); +g.clear(); +startdraw(); +setButtons(); + + + diff --git a/apps/multiclock/nifty.face.js b/apps/multiclock/nifty.face.js new file mode 100644 index 000000000..2c2af6063 --- /dev/null +++ b/apps/multiclock/nifty.face.js @@ -0,0 +1,55 @@ +(() => { + function getFace(){ + + const locale = require("locale"); + const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + + const scale = g.getWidth() / 176; + + const widget = 24; + + const viewport = { + width: g.getWidth(), + height: g.getHeight(), + } + + const center = { + x: viewport.width / 2, + y: Math.round(((viewport.height - widget) / 2) + widget), + } + + function d02(value) { + return ('0' + value).substr(-2); + } + + function drawClock() { + g.reset(); + g.clearRect(0, widget, viewport.width, viewport.height); + var now = new Date(); + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 40 * scale, center.y - 62 * scale); + g.drawString(month, center.x + 40 * scale, center.y - 44 * scale); + g.drawString(day, center.x + 40 * scale, center.y - 26 * scale); + g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale); + g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/ped.js b/apps/multiclock/ped.js deleted file mode 100644 index a0f81e2e5..000000000 --- a/apps/multiclock/ped.js +++ /dev/null @@ -1,41 +0,0 @@ -(() => { - - function getFace(){ - - function draw() { - let steps = "-"; - let show_steps = false; - - // only attempt to get steps if activepedom is loaded - if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } - - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4].substr(0,5); - - g.reset(); - g.clearRect(0,24,239,239); - g.setFont("Vector", 80); - g.setColor(1,1,1); // white - g.setFontAlign(0, -1); - g.drawString(time, g.getWidth()/2, 60); - g.setColor(0,255,0); // green - g.setFont("Vector", 60); - g.drawString(steps, g.getWidth()/2, 160); - } - - function onSecond(){ - var t = new Date(); - if ((t.getSeconds() % 5) === 0) draw(); - } - - return {init:draw, tick:onSecond}; - } - - return getFace; - -})(); diff --git a/apps/multiclock/timdat.js b/apps/multiclock/timdat.js deleted file mode 100644 index a4a93a691..000000000 --- a/apps/multiclock/timdat.js +++ /dev/null @@ -1,47 +0,0 @@ -(() => { - var locale = require("locale"); - var dayFirst = ["en_GB", "en_IN", "en_NAV", "de_DE", "nl_NL", "fr_FR", "en_NZ", "en_AU", "de_AT", "en_IL", "es_ES", "fr_BE", "de_CH", "fr_CH", "it_CH", "it_IT", "tr_TR", "pt_BR", "cs_CZ", "pt_PT"]; - var withDot = ["de_DE", "nl_NL", "de_AT", "de_CH", "hu_HU", "cs_CZ", "sl_SI"]; - - function getFace(){ - - var lastmin=-1; - function drawClock(){ - var d=Date(); - if (d.getMinutes()==lastmin) return; - var tm=d.toString().split(' ')[4].substring(0,5); - lastmin=d.getMinutes(); - g.reset(); - g.clearRect(0,24,239,239); - var w=g.getWidth(); - g.setColor(0xffff); - g.setFontVector(80); - g.drawString(tm,4+(w-g.stringWidth(tm))/2,64); - g.setFontVector(36); - g.setColor(0x07ff); - var dt=locale.dow(d, 1) + " "; - if (dayFirst.includes(locale.name)) { - dt+=d.getDate(); - if (withDot.includes(locale.name)) { - dt+="."; - } - dt+=" " + locale.month(d, 1); - } else { - dt+=locale.month(d, 1) + " " + d.getDate(); - } - g.drawString(dt,(w-g.stringWidth(dt))/2,160); - g.flip(); - } - - function drawFirst(){ - lastmin=-1; - drawClock(); - } - - return {init:drawFirst, tick:drawClock}; - } - - return getFace; - -})(); - diff --git a/apps/multiclock/txt.js b/apps/multiclock/txt.face.js similarity index 56% rename from apps/multiclock/txt.js rename to apps/multiclock/txt.face.js index 130455176..ebe5d3ef2 100644 --- a/apps/multiclock/txt.js +++ b/apps/multiclock/txt.face.js @@ -1,8 +1,14 @@ (() => { function getFace(){ - - function drawTime(d) { + + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + var F = 44 * scale; + + function drawTime() { function convert(n){ var t0 = [" ","one","two","three","four","five","six","seven","eight","nine"]; var t1 = ["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]; @@ -13,28 +19,25 @@ return "error"; } g.reset(); - g.clearRect(0,40,239,210); - g.setColor(1,1,1); + g.clearRect(0,24,W-1,H-1); + var d = new Date(); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont("Vector",44); var txt = convert(d.getHours()); - g.drawString(txt.top,120,60); - g.drawString(txt.bot,120,100); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2-2*F); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2-F); txt = convert(d.getMinutes()); - g.drawString(txt.top,120,140); - g.drawString(txt.bot,120,180); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2+F); } - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; + return {init:drawTime, tick:drawTime, tickpersec:false}; } return getFace; diff --git a/apps/multiclock/txtface.jpg b/apps/multiclock/txtface.jpg deleted file mode 100644 index e3834125780c16c697d9fb22d5d3eead3cee2d30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51801 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 From 54514220dbe3c00db30d3f3da14c9174df789de4 Mon Sep 17 00:00:00 2001 From: jeffmer Date: Mon, 1 Nov 2021 18:06:15 +0000 Subject: [PATCH 232/325] fix file names --- apps/multiclock/README.md | 51 ++++++++++++++++++ .../{clock.img => multiclock-icon.img} | Bin .../{clock-icon.js => multiclock-icon.js} | 0 apps/multiclock/{clock.png => multiclock.png} | Bin 4 files changed, 51 insertions(+) create mode 100644 apps/multiclock/README.md rename apps/multiclock/{clock.img => multiclock-icon.img} (100%) rename apps/multiclock/{clock-icon.js => multiclock-icon.js} (100%) rename apps/multiclock/{clock.png => multiclock.png} (100%) diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md new file mode 100644 index 000000000..8e1b32543 --- /dev/null +++ b/apps/multiclock/README.md @@ -0,0 +1,51 @@ +# Multiclock + +This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast. Currently there are four clock faces as shown below. +### Analog Clock Face + + +### Digital Clock Face + + +### Big Digit Clock Face + + +### Text Clock Face + + +### Time and Date Clock Face + + +## Controls +Clock faces are kept in a circular list. + +*BTN1* - switches to the next clock face. + +*BTN2* - switches to the app launcher. + +*BTN3* - switches to the previous clock face. + +## Adding a new face +Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure: + +``` +(() => { + function getFace(){ + function onSecond(){ + //draw digits, hands etc + } + function drawAll(){ + //draw background + initial state of digits, hands etc + } + return {init:drawAll, tick:onSecond}; + } + return getFace; +})(); +``` +For those familiar with the structure of widgets, this is similar, however, there is an additional level of function nesting. This means that although faces are loaded when the clock app starts running they are not instantiated until their `getFace` function is called, i.e. memory is not allocated to structures such as image buffer arrays declared in `getFace` until the face is selected. Consequently, adding a face does not require a lot of extra memory. + +The app at start up loads all files `*.face.js`. The simplest way of adding a face is thus to load it into `Storage` using the WebIDE. Similarly, to remove an unwanted face, simply delete it from `Storage` using the WebIDE. + +## Support + +Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev). \ No newline at end of file diff --git a/apps/multiclock/clock.img b/apps/multiclock/multiclock-icon.img similarity index 100% rename from apps/multiclock/clock.img rename to apps/multiclock/multiclock-icon.img diff --git a/apps/multiclock/clock-icon.js b/apps/multiclock/multiclock-icon.js similarity index 100% rename from apps/multiclock/clock-icon.js rename to apps/multiclock/multiclock-icon.js diff --git a/apps/multiclock/clock.png b/apps/multiclock/multiclock.png similarity index 100% rename from apps/multiclock/clock.png rename to apps/multiclock/multiclock.png From 9f8e451a249527a2c9380549a5cfde80ed5ec4f6 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Mon, 1 Nov 2021 19:50:21 +0100 Subject: [PATCH 233/325] menusmall: follow latest E_showMenu improvements - remove unused variable - remove unused parameter from `select()` - shave a few bytes off `move()` If a numerical item has `item.wrap` we no longer check if `item.min/max` is set, because your menu will be broken either way. Only small optimizations, so skip the version bump. --- apps/menusmall/boot.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 805413e2b..43c66089f 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -46,7 +46,6 @@ E.showMenu = function(items) { rows = 1+rowmax-rowmin; } } - var less = idx>0; while (rows--) { var name = menuItems[idx]; var item = items[name]; @@ -77,7 +76,7 @@ E.showMenu = function(items) { g.setColor((idxitem.max) - item.value = (item.wrap && item.min!==undefined) ? item.min : item.max; + if (item.min!==undefined && item.valueitem.max) item.value = item.wrap ? item.min : item.max; if (item.onchange) item.onchange(item.value); l.draw(options.selected,options.selected); } else { var a=options.selected; - options.selected = (dir+options.selected)%menuItems.length; - if (options.selected<0) options.selected += menuItems.length; + options.selected = (dir+options.selected+menuItems.length)%menuItems.length; l.draw(Math.min(a,options.selected), Math.max(a,options.selected)); } } From 33763110cc4958f47907ec84ff72374beabcc423 Mon Sep 17 00:00:00 2001 From: jeffmer Date: Mon, 1 Nov 2021 19:19:16 +0000 Subject: [PATCH 234/325] tunig layout --- apps/multiclock/digi.face.js | 2 +- apps/multiclock/dk.face.js | 4 ++-- apps/multiclock/multiclock.app.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/multiclock/digi.face.js b/apps/multiclock/digi.face.js index 1a72c1964..50a5c529e 100644 --- a/apps/multiclock/digi.face.js +++ b/apps/multiclock/digi.face.js @@ -4,7 +4,7 @@ function getFace(){ var W = g.getWidth(); var H = g.getHeight(); - var scale = w/240; + var scale = W/240; var buf = Graphics.createArrayBuffer(W,92,1,{msb:true}); function flip() { diff --git a/apps/multiclock/dk.face.js b/apps/multiclock/dk.face.js index bca7cb2f5..a89397a75 100644 --- a/apps/multiclock/dk.face.js +++ b/apps/multiclock/dk.face.js @@ -20,13 +20,13 @@ g.setColor(g.theme.fg); g.setFontAlign(0,-1); g.setFontVector(80*scale); - g.drawString(tm,4+W/2,H/2-80*scale); + g.drawString(tm,4+W/2,H/2+24-80*scale); g.setFontVector(36*scale); g.setColor(g.theme.fg2); d[1] = locale.month(now,3); d[0] = locale.dow(now,3); var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3]; - g.drawString(dt,W/2,H/2); + g.drawString(dt,W/2,H/2+24); g.flip(); } diff --git a/apps/multiclock/multiclock.app.js b/apps/multiclock/multiclock.app.js index af0ee06b9..126802500 100644 --- a/apps/multiclock/multiclock.app.js +++ b/apps/multiclock/multiclock.app.js @@ -29,7 +29,7 @@ function startdraw() { intervalRefSec = setInterval(face.tick,1000); else queueMinuteTick(face.tick); - wOS.drawWidgets(); + Bangle.drawWidgets(); } var SCREENACCESS = { From 26beb8fafdf4eceb5a2265a3507304d3f54034e6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 1 Nov 2021 19:47:57 +0000 Subject: [PATCH 235/325] health 0.05: Fix daily summary calculation --- apps.json | 4 ++-- apps/health/ChangeLog | 1 + apps/health/boot.js | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps.json b/apps.json index 494b98766..35d95d9a7 100644 --- a/apps.json +++ b/apps.json @@ -49,10 +49,10 @@ { "id": "health", "name": "Health Tracking", - "version": "0.04", + "version": "0.05", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", - "tags": "tool,system", + "tags": "tool,system,health", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index a21d76fd1..808282b64 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -3,3 +3,4 @@ 0.03: Settings to turn HRM on 0.04: Add HRM graph view Don't restart HRM when changing apps if we've already got a good BPM value +0.05: Fix daily summary calculation diff --git a/apps/health/boot.js b/apps/health/boot.js index 17fbaaaa8..a8d78f685 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -55,28 +55,29 @@ Bangle.on("health", health => { } var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN); require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN); - if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-1) return; + if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-2) return; // we're at the end of the day. Read in all of the data for the day and sum it up var sumPos = recordPos + DB_RECORD_LEN; // record after the current one is the sum if (f.substr(sumPos, DB_RECORD_LEN)!="\xFF\xFF\xFF\xFF") { print("HEALTH ERR: Daily summary already written!"); return; } - health = { steps:0, bpm:0, movement:0, records:0}; + health = { steps:0, bpm:0, movement:0, movCnt:0, bpmCnt:0}; var records = DB_RECORDS_PER_HR*24; for (var i=0;i Date: Mon, 1 Nov 2021 20:38:22 +0000 Subject: [PATCH 236/325] final release fixes --- apps.json | 2 +- apps/multiclock/README.md | 27 ++++----------------------- apps/multiclock/digi.face.js | 6 +++--- apps/multiclock/txt.face.js | 10 +++++----- 4 files changed, 13 insertions(+), 32 deletions(-) diff --git a/apps.json b/apps.json index edf21dd2d..305e5255b 100644 --- a/apps.json +++ b/apps.json @@ -2684,7 +2684,7 @@ "id": "multiclock", "name": "Multi Clock", "version": "0.07", - "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", + "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 or swipe left-right. For best display set theme Background 2 to cyan or some other bright colour in settings.", "icon": "multiclock.png", "type": "clock", "tags": "clock", diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md index 8e1b32543..e8b8335ea 100644 --- a/apps/multiclock/README.md +++ b/apps/multiclock/README.md @@ -1,29 +1,11 @@ # Multiclock -This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast. Currently there are four clock faces as shown below. +This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast. Currently there are four clock faces as shown below. There are currently an anlog, digital, text, big digit, time and date, and a clone of the Nifty-A-Clock faces. ### Analog Clock Face -### Digital Clock Face - - -### Big Digit Clock Face - - -### Text Clock Face - - -### Time and Date Clock Face - - ## Controls -Clock faces are kept in a circular list. - -*BTN1* - switches to the next clock face. - -*BTN2* - switches to the app launcher. - -*BTN3* - switches to the previous clock face. +Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle. ## Adding a new face Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure: @@ -37,7 +19,7 @@ Clock faces are described in javascript storage files named `name.face.js`. For function drawAll(){ //draw background + initial state of digits, hands etc } - return {init:drawAll, tick:onSecond}; + return {init:drawAll, tick:onSecond, tickpersec:true}; } return getFace; })(); @@ -46,6 +28,5 @@ For those familiar with the structure of widgets, this is similar, however, ther The app at start up loads all files `*.face.js`. The simplest way of adding a face is thus to load it into `Storage` using the WebIDE. Similarly, to remove an unwanted face, simply delete it from `Storage` using the WebIDE. -## Support +If `tickpersec` is false then `tick` is only called each minute as this is more power effcient - especially on the BAngle 2. -Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev). \ No newline at end of file diff --git a/apps/multiclock/digi.face.js b/apps/multiclock/digi.face.js index 50a5c529e..21f339afc 100644 --- a/apps/multiclock/digi.face.js +++ b/apps/multiclock/digi.face.js @@ -9,7 +9,7 @@ function getFace(){ var buf = Graphics.createArrayBuffer(W,92,1,{msb:true}); function flip() { g.setColor(g.theme.fg); - g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,H/2-46); + g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,H/2-34); } var W = g.getWidth(); @@ -24,10 +24,10 @@ function getFace(){ buf.setFont("Vector",54*scale); buf.setFontAlign(0,-1); buf.drawString(time,W/2,0); - buf.setFont("6x8",2); + buf.setFont("6x8",scale<1?1:2); buf.setFontAlign(0,-1); var date = d.toString().substr(0,15); - buf.drawString(date, W/2, 70); + buf.drawString(date, W/2, 70*scale); flip(); } return {init:drawTime, tick:drawTime, tickpersec:true}; diff --git a/apps/multiclock/txt.face.js b/apps/multiclock/txt.face.js index ebe5d3ef2..fddc07214 100644 --- a/apps/multiclock/txt.face.js +++ b/apps/multiclock/txt.face.js @@ -23,17 +23,17 @@ var d = new Date(); g.setColor(g.theme.fg); g.setFontAlign(0,0); - g.setFont("Vector",44); + g.setFont("Vector",F); var txt = convert(d.getHours()); g.setColor(g.theme.fg); - g.drawString(txt.top,W/2,H/2-2*F); + g.drawString(txt.top,W/2,H/2+24-2*F); g.setColor(g.theme.fg2); - g.drawString(txt.bot,W/2,H/2-F); + g.drawString(txt.bot,W/2,H/2+24-F); txt = convert(d.getMinutes()); g.setColor(g.theme.fg); - g.drawString(txt.top,W/2,H/2); + g.drawString(txt.top,W/2,H/2+24); g.setColor(g.theme.fg2); - g.drawString(txt.bot,W/2,H/2+F); + g.drawString(txt.bot,W/2,H/2+24+F); } From 37b1e38e7fe9702c6d98fb56774fb0f291ca4bd4 Mon Sep 17 00:00:00 2001 From: jeffmer Date: Tue, 2 Nov 2021 12:52:22 +0000 Subject: [PATCH 237/325] fix minute tick bug --- apps.json | 2 +- apps/multiclock/ChangeLog | 1 + apps/multiclock/multiclock.app.js | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 305e5255b..31184e954 100644 --- a/apps.json +++ b/apps.json @@ -2683,7 +2683,7 @@ { "id": "multiclock", "name": "Multi Clock", - "version": "0.07", + "version": "0.08", "description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 or swipe left-right. For best display set theme Background 2 to cyan or some other bright colour in settings.", "icon": "multiclock.png", "type": "clock", diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index f40f04031..9d02ae85e 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -5,6 +5,7 @@ 0.05: make Bangle compatible 0.06: add minute tick for efficiency and nifty A clock 0.07: compatible with Bang;e.js 2 +0.08: fix minute tick bug diff --git a/apps/multiclock/multiclock.app.js b/apps/multiclock/multiclock.app.js index 126802500..c24e5c94b 100644 --- a/apps/multiclock/multiclock.app.js +++ b/apps/multiclock/multiclock.app.js @@ -14,11 +14,12 @@ function stopdraw() { g.clear(); } -function queueMinuteTick(f) { - if (tickTimeout) clearTimeout(drawTimeout); +function queueMinuteTick() { + if (tickTimeout) clearTimeout(tickTimeout); tickTimeout = setTimeout(function() { tickTimeout = undefined; - f(); + face.tick(); + queueMinuteTick(); }, 60000 - (Date.now() % 60000)); } @@ -28,7 +29,7 @@ function startdraw() { if (face.tickpersec) intervalRefSec = setInterval(face.tick,1000); else - queueMinuteTick(face.tick); + queueMinuteTick(); Bangle.drawWidgets(); } From 102b1b2ddb229e929e1fdb1995e01e9f23d920bb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 2 Nov 2021 15:36:19 +0000 Subject: [PATCH 238/325] update default BG2 theme colour --- apps/setting/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 0c2930086..0decb5313 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -193,7 +193,7 @@ function showThemeMenu() { 'Light BW': ()=>{ upd({ fg:cl("#000"), bg:cl("#fff"), - fg2:cl("#00f"), bg2:cl("#0ff"), + fg2:cl("#000"), bg2:cl("#cff"), fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); From ca4bef7a1cac03a652440d121128864efbf3546c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 2 Nov 2021 16:19:32 +0000 Subject: [PATCH 239/325] Add ability to download health info --- apps.json | 1 + apps/health/interface.html | 133 +++++++++++++++++++++++++++++++++++++ apps/health/lib.js | 1 - core | 2 +- 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 apps/health/interface.html diff --git a/apps.json b/apps.json index 35d95d9a7..03e20050e 100644 --- a/apps.json +++ b/apps.json @@ -55,6 +55,7 @@ "tags": "tool,system,health", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"health.app.js","url":"app.js"}, {"name":"health.img","url":"app-icon.js","evaluate":true}, diff --git a/apps/health/interface.html b/apps/health/interface.html new file mode 100644 index 000000000..f04857926 --- /dev/null +++ b/apps/health/interface.html @@ -0,0 +1,133 @@ + + + + + +

+ + + + + diff --git a/apps/health/lib.js b/apps/health/lib.js index 20d73a907..70305bff8 100644 --- a/apps/health/lib.js +++ b/apps/health/lib.js @@ -16,7 +16,6 @@ function getRecordIdx(d) { // Read all records from the given month exports.readAllRecords = function(d, cb) { - var rec = getRecordIdx(d); var fn = getRecordFN(d); var f = require("Storage").read(fn); if (f===undefined) return; diff --git a/core b/core index 70b49d8db..5ef454a1a 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 70b49d8dbd2afa76f4485aadf679dc75e0a8b4ac +Subproject commit 5ef454a1acce54f6420015b519a7ecf461f9bc37 From 47ba763a9d24cd59e0b57549a3d7bcefb54c7aa2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 2 Nov 2021 20:06:27 +0000 Subject: [PATCH 240/325] recorder 0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html --- apps.json | 2 +- apps/recorder/ChangeLog | 2 + apps/recorder/app.js | 8 +- apps/recorder/interface.html | 272 ++++++++++++++++++++++------------- apps/recorder/widget.js | 2 +- 5 files changed, 183 insertions(+), 103 deletions(-) diff --git a/apps.json b/apps.json index 1a79e59f4..35fe0b49a 100644 --- a/apps.json +++ b/apps.json @@ -624,7 +624,7 @@ "id": "recorder", "name": "Recorder (BETA)", "shortName": "Recorder", - "version": "0.01", + "version": "0.02", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 5560f00bc..bf90d0384 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9b9c06c78..ac3e391fc 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -13,7 +13,7 @@ function loadSettings() { var changed = false; if (!settings.file) { changed = true; - settings.file = "record.log0.csv"; + settings.file = "recorder.log0.csv"; } if (!Array.isArray(settings.record)) { settings.record = ["gps"]; @@ -31,7 +31,7 @@ function updateSettings() { } function getTrackNumber(filename) { - return parseInt(filename.match(/^record\.log(.*)\.csv$/)[1]||0); + return parseInt(filename.match(/^recorder\.log(.*)\.csv$/)[1]||0); } function showMainMenu() { @@ -73,7 +73,7 @@ function showMainMenu() { step: 1, onchange: v => { settings.recording = false; // stop recording if we change anything - settings.file = "record.log"+v+".csv"; + settings.file = "recorder.log"+v+".csv"; updateSettings(); } }, @@ -105,7 +105,7 @@ function viewTracks() { '': { 'title': 'Tracks' } }; var found = false; - require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{ found = true; menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); }); diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index 2ae1c3e71..eca13d263 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -3,7 +3,6 @@ - THIS IS NOT CURRENTLY IMPLEMENTED
@@ -14,14 +13,35 @@ function saveKML(track,title) { var kml = ` - - ${title} - - - ${track.map(pt=>[pt.lon, pt.lat, pt.alt].join(",")).join("\n ")} - - - + +${track[0].Heartrate!==undefined ? ` + Heart Rate + `:``} +${track[0].Steps!==undefined ? ` + Step Count`:``} + + + + Tracks + + ${title} + +${track.map(pt=>` ${pt.Time.toISOString()}\n`).join("")} +${track.map(pt=>` ${pt.Longitude} ${pt.Latitude} ${pt.Altitude}\n`).join("")} + + + +${track[0].Heartrate!==undefined ? ` +${track.map(pt=>` ${0|pt.Heartrate}\n`).join("")} + `:``} +${track[0].Steps!==undefined ? ` +${track.map(pt=>` ${0|pt.Steps}\n`).join("")} + `:``} + + + + + `; var a = document.createElement("a"), @@ -39,6 +59,7 @@ function saveKML(track,title) { function saveGPX(track, title) { var gpx = ` + @@ -48,11 +69,19 @@ function saveGPX(track, title) { `; track.forEach(pt=>{ gpx += ` - - ${pt.alt} - + + ${pt.Altitude} + + + + ${pt.Heartrate ? `${pt.Heartrate}`:``} + ${""/*...*/} + ${""/* 65 */} + + `; }); + // https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd gpx += ` @@ -70,104 +99,153 @@ function saveGPX(track, title) { }, 0); } -function trackLineToObject(l, hasTrackNumber) { +function saveCSV(track, title) { + var headers = Object.keys(track[0]); + var csv = headers.join(",")+"\n"; + track.forEach(t=>{ + csv += headers.map(k=>{ + if (t[k] instanceof Date) return t[k].toISOString(); + return t[k]; + }).join(",")+"\n"; + }); + Util.saveCSV(title, csv); +} + +function trackLineToObject(headers, l) { var t = l.trim().split(","); - var n = hasTrackNumber ? 1 : 0; - var o = { - date : new Date(parseInt(t[n+0])), - lat : parseFloat(t[n+1]), - lon : parseFloat(t[n+2]), - alt : parseFloat(t[n+3]) - }; - if (hasTrackNumber) - o.number = t[0]; + var o = {}; + headers.forEach((header,i) => o[header] = t[i]); + if (o.Time) o.Time = new Date(o.Time*1000); return o; } -function downloadTrack(trackid, callback) { +function downloadTrack(filename, callback) { Util.showModal("Downloading Track..."); - Util.readStorageFile(`.gpsrc${trackid.toString(36)}`,data=>{ + Util.readStorageFile(filename,data=>{ Util.hideModal(); - var track = data.trim().split("\n").map(l=>trackLineToObject(l,false)); + var lines = data.trim().split("\n"); + var headers = lines.shift().split(","); + var track = lines.map(l=>trackLineToObject(headers, l)); callback(track); }); } + function getTrackList() { - Util.showModal("Loading Tracks..."); + Util.showModal("Loading Track List..."); domTracks.innerHTML = ""; - Puck.write(`\x10(function() { - Bluetooth.println(""); - for (var n=0;n<36;n++) { - var f = require("Storage").open(".gpsrc"+n.toString(36),"r"); - var l = f.readLine(); - Bluetooth.println((l!==undefined) ? (n + "," + l.trim()) : ""); - } - })()\n`,tracklist=>{ - var trackLines = tracklist.trim().split("\n").filter(l=>l!=""); - var html = `
-
\n`; - trackLines.forEach(l => { - var track = trackLineToObject(l, true /*has track number*/); - html += ` -
-
-
Track ${track.number}
-
${track.date.toString().substr(0,24)}
-
-
- -
-
- -
- `; - }); - if (trackLines.length==0) { - html += ` -
-
-
No tracks
-
No GPS tracks found
-
-
- `; - } - html += ` -
-
`; - domTracks.innerHTML = html; - Util.hideModal(); - var buttons = domTracks.querySelectorAll("button"); - for (var i=0;i { - var button = event.currentTarget; - var trackid = parseInt(button.getAttribute("trackid")); - var task = button.getAttribute("task"); - if (task=="delete") { - Util.showModal("Deleting Track..."); - Util.eraseStorageFile(`.gpsrc${trackid.toString(36)}`,()=>{ - Util.hideModal(); - getTrackList(); + Puck.eval(`require("Storage").list(/^recorder\\.log.*\\.csv$/,{sf:1})`,files=>{ + var trackList = []; + var promise = Promise.resolve(); + /* For each file ask Bangle.js for info. Since we now start recording even + before we have a GPS trace, we get the Bangle to do a *quick* search for us + to see if it found any data first. */ + files.forEach(filename => { + promise = promise.then(()=>new Promise(resolve => { + var trackNo = filename.match(/^recorder\.log(.*)\.csv$/)[1]; + Util.showModal(`Loading Track ${trackNo}...`); + Puck.eval(`(function(fn) { + var f = require("Storage").open(fn,"r"); + var headers = f.readLine(); + var data = f.readLine(); + var lIdx = headers.split(",").indexOf("Latitude"); + if (lIdx >= 0) { + var tries = 100; + var l = data; + while (l && l.split(",")[lIdx]=="" && tries++) + l = f.readLine(); + if (l) data = l; + } + return {headers:headers,l:data}; +})(${JSON.stringify(filename)})`, trackInfo=>{ + console.log(filename," => ",trackInfo); + trackInfo.headers = trackInfo.headers.split(","); + trackList.push({ + filename : filename, + number : trackNo, + info : trackInfo }); + resolve(); + }); + })); + }); + // ================================================ + // When 'promise' completes we now have all the info in trackList + promise.then(() => { + var html = `
+
\n`; + trackList.forEach(track => { + var trackData = trackLineToObject(track.info.headers, track.info.l); + console.log("track", track); + console.log("trackData", trackData); + html += ` +
+
+
Track ${track.number}
+
${trackData.Time.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
+
+${trackData.Latitude ? ` +
+ +
` : `
No GPS info
`} + +
+ `; + }); + if (trackList.length==0) { + html += ` +
+
+
No tracks
+
No GPS tracks found
+
+
+ `; } - if (task=="downloadkml") { - downloadTrack(trackid, track => saveKML(track, `Bangle.js Track ${trackid}`)); - } - if (task=="downloadgpx") { - downloadTrack(trackid, track => saveGPX(track, `Bangle.js Track ${trackid}`)); - } - }); - } - }) + html += ` +
+
`; + domTracks.innerHTML = html; + Util.hideModal(); + var buttons = domTracks.querySelectorAll("button"); + for (var i=0;i { + var button = event.currentTarget; + var filename = button.getAttribute("filename"); + var trackid = parseInt(button.getAttribute("trackid")); + if (!filename || trackid===undefined) return; + var task = button.getAttribute("task"); + if (task=="delete") { + Util.showModal("Deleting Track..."); + Util.eraseStorageFile(filename,()=>{ + Util.hideModal(); + getTrackList(); + }); + } + if (task=="downloadkml") { + downloadTrack(filename, track => saveKML(track, `Bangle.js Track ${trackid}`)); + } + if (task=="downloadgpx") { + downloadTrack(filename, track => saveGPX(track, `Bangle.js Track ${trackid}`)); + } + if (task=="downloadcsv") { + downloadTrack(filename, track => saveCSV(track, `Bangle.js Track ${trackid}`)); + } + }); + } + }); + }); } function onInit() { diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index df0be1d20..09893bbb7 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -7,7 +7,7 @@ function loadSettings() { var settings = require("Storage").readJSON("recorder.json",1)||{}; settings.period = settings.period||10; - if (!settings.file || !settings.file.startsWith("record.log")) + if (!settings.file || !settings.file.startsWith("recorder.log")) settings.recording = false; return settings; } From ab3de04c2deaa14265d8887446d5824aa5e2f360 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Thu, 4 Nov 2021 05:36:09 +0000 Subject: [PATCH 241/325] Fixed wrong day being displayed in nifty clocks --- apps/ffcniftya/app.js | 2 +- apps/ffcniftyb/app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 0b8865bec..31742f64a 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -27,7 +27,7 @@ function draw() { const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); const minutes = d02(now.getMinutes()); - const day = d02(now.getDay()); + const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); const year = now.getFullYear(); diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 58bf0b24b..75d217ab4 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -31,7 +31,7 @@ function renderText(g) { const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); const minutes = d02(now.getMinutes()); - const day = d02(now.getDay()); + const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); const year = now.getFullYear(); From 935d409f4c46f527b8e306fad854113419b69b9e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 17:16:02 +0000 Subject: [PATCH 242/325] ability to depend on a specific app ID Layout can display images in buttons iOS and Android integration apps --- README.md | 1 + apps.json | 43 +++++++++-- apps/android/ChangeLog | 1 + apps/android/app-icon.js | 1 + apps/android/app.js | 2 + apps/android/app.png | Bin 0 -> 636 bytes apps/android/boot.js | 55 ++++++++++++++ apps/ios/ChangeLog | 1 + apps/ios/app-icon.js | 1 + apps/ios/app.js | 2 + apps/ios/app.png | Bin 0 -> 1301 bytes apps/ios/boot.js | 129 ++++++++++++++++++++++++++++++++ apps/messages/ChangeLog | 2 + apps/messages/app.js | 157 ++++++++++++++++++++++++++++----------- apps/messages/boot.js | 36 --------- apps/messages/lib.js | 37 +++++++++ core | 2 +- modules/Layout.js | 31 ++++---- 18 files changed, 403 insertions(+), 98 deletions(-) create mode 100644 apps/android/ChangeLog create mode 100644 apps/android/app-icon.js create mode 100644 apps/android/app.js create mode 100644 apps/android/app.png create mode 100644 apps/android/boot.js create mode 100644 apps/ios/ChangeLog create mode 100644 apps/ios/app-icon.js create mode 100644 apps/ios/app.js create mode 100644 apps/ios/app.png create mode 100644 apps/ios/boot.js create mode 100644 apps/messages/ChangeLog delete mode 100644 apps/messages/boot.js diff --git a/README.md b/README.md index 21f5dbff9..527cb1188 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ and which gives information about the app for the Launcher. "tags": "", // comma separated tag list for searching "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on + "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) diff --git a/apps.json b/apps.json index 35fe0b49a..156b6e5b0 100644 --- a/apps.json +++ b/apps.json @@ -32,17 +32,50 @@ { "id": "messages", "name": "Messages", - "version": "0.01", - "description": "App to display notifications from iOS and Gadgetbridge (BETA)", + "version": "0.02", + "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", + "type": "app", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"messages.app.js","url":"app.js"}, {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.boot.js","url":"boot.js"}, - {"name":"messages.wid.js","url":"widget.js"} + {"name":"messages.wid.js","url":"widget.js"}, + {"name":"messages","url":"lib.js"} + ], + "sortorder": -9 + }, + { + "id": "android", + "name": "Android Integration", + "version": "0.01", + "description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.", + "icon": "app.png", + "tags": "tool,system,messages,notifications", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"android.app.js","url":"app.js"}, + {"name":"android.img","url":"app-icon.js","evaluate":true}, + {"name":"android.boot.js","url":"boot.js"} + ], + "sortorder": -9 + }, + { + "id": "ios", + "name": "iOS Integration", + "version": "0.01", + "description": "(BETA) App to display notifications from iOS devices", + "icon": "app.png", + "tags": "tool,system,ios,apple,messages,notifications", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"ios.app.js","url":"app.js"}, + {"name":"ios.img","url":"app-icon.js","evaluate":true}, + {"name":"ios.boot.js","url":"boot.js"} ], "sortorder": -9 }, @@ -216,7 +249,7 @@ "id": "gbridge", "name": "Gadgetbridge", "version": "0.24", - "description": "The default notification handler for Gadgetbridge notifications from Android", + "description": "The default notification handler for Gadgetbridge notifications from Android. This will eventually be replaced by the 'Android' app.", "icon": "app.png", "type": "widget", "tags": "tool,system,android,widget", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/android/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/android/app-icon.js b/apps/android/app-icon.js new file mode 100644 index 000000000..9253ec839 --- /dev/null +++ b/apps/android/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4MA///xF9FstggwFDuEOAoc//gFJv/+AoZHBAgUB8/nwAFCBYIFCgYFB4AFHABdjCIPGAoPzAoPPAvpHFMpYFFPosAnk8NgYFdjEYfMo=")) diff --git a/apps/android/app.js b/apps/android/app.js new file mode 100644 index 000000000..5c5c7ddaf --- /dev/null +++ b/apps/android/app.js @@ -0,0 +1,2 @@ +// Config app not implemented yet +load("messages.app.js"); diff --git a/apps/android/app.png b/apps/android/app.png new file mode 100644 index 0000000000000000000000000000000000000000..65150f08de4205995f37e8b83cd6bf94aa25352b GIT binary patch literal 636 zcmV-?0)zdDP)1F^Gb$6(WiXLUDF7wIWE{$ymfapyKRc zhJZgN!NDnue?Ui5XhrNIRB-CxBGf_BO|ys^>r38o=#!L?Jx&ow@sJKT6@ zBexX+fQph&|875t3T*lQ3sLew(?;SM8=C}h--8bzcOI@4L{u&Sr-2h;NCWr`dg3jk65lA7^+8@Ur7q4HW`x#fA=;jxH?ikzp- zcKOwn#iaQq$>MTt;W7lTflN5G6~15Uv!r`|PeiUhT6}A4Xk!7D+ET8?kB=ay{*C}-+Ha-HLtMqtJ$1;e)GMNsf|6*;^*yy#RH@z`%Kcop?;rBuX%wpM{?O) zz)O=lj0GsHbeAcjr9oe6zj;+z!BBv)i3R3N*Y@C|#l0M3CGuCOTq|4$sR9H_>FM(3 z`u_O5+AQSp(xwc`*N+$;kSgInsV7=ZUaH-EaZEhhxs;Q}b;W0>e73sLiQxoH8Yf`V zI02JpDg^9yF10?gN(}Qw3J8?a{zh8I^GeBTi)Pq-#7S-RO|Gn0w1u zKg=+0;W0000 { + // feed a copy to other handlers if there were any + if (_GB) setTimeout(_GB,0,Object.assign({},event)); + + /* TODO: Call handling, fitness */ + var HANDLERS = { + // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add + "notify" : function() { event.t="add";require("messages").pushMessage(event); }, + // {t:"notify~",id:int, title:string} // modified + "notify~" : function() { event.t="modify";require("messages").pushMessage(event); }, + // {t:"notify-",id:int} // remove + "notify-" : function() { event.t="remove";require("messages").pushMessage(event); }, + // {t:"find", n:bool} // find my phone + "find" : function() { + if (Bangle.findDeviceInterval) { + clearInterval(Bangle.findDeviceInterval); + delete Bangle.findDeviceInterval; + } + if (event.n) // Ignore quiet mode: we always want to find our watch + Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000); + }, + // {t:"musicstate", state:"play/pause",position,shuffle,repeat} + "musicstate" : function() { + require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state}); + }, + // {t:"musicinfo", artist,album,track,dur,c(track count),n(track num} + "musicinfo" : function() { + require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"})); + } + }; + var h = HANDLERS[event.t]; + if (h) h(); else console.log("GB Unknown",event); + }; + + // Battery monitor + function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } + NRF.on("connect", () => setTimeout(sendBattery, 2000)); + setInterval(sendBattery, 10*60*1000); + // Health tracking + Bangle.on('health', health=>{ + gbSend({ t: "act", stp: health.steps, hrm: health.bpm }); + }); + // Music control + Bangle.musicControl = cmd => { + // play/pause/next/previous/volumeup/volumedown + gbSend({ t: "music", m:cmd }); + } +})(); diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/ios/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/ios/app-icon.js b/apps/ios/app-icon.js new file mode 100644 index 000000000..b74048750 --- /dev/null +++ b/apps/ios/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwZC/AGEB/4AGwARHv4RH/wQGj4QHAAP4CIoQJAAIRWg4RL8ARVn4RL/gR/CJv9BIP934DFEZH+v/0AgMv+wRK+YCBz/7C4PfCJOfAQO//JHMCIX3/d/CJ//t4RJF4JlCCIP/koRKEYh+DCIxlBCIQADCJQgCn4DCCJSbBHIIDBXYQRI/+Sp4DB7ZsCfdQRzg4RL8ARVgARLCAgRSj4QJ/ARFgF/CA/+CA0AgIRHwARHAH4AnA")) diff --git a/apps/ios/app.js b/apps/ios/app.js new file mode 100644 index 000000000..5c5c7ddaf --- /dev/null +++ b/apps/ios/app.js @@ -0,0 +1,2 @@ +// Config app not implemented yet +load("messages.app.js"); diff --git a/apps/ios/app.png b/apps/ios/app.png new file mode 100644 index 0000000000000000000000000000000000000000..79aa78f3a3cbe6d008a396f65cf4b6e739a5d494 GIT binary patch literal 1301 zcmV+w1?u{VP)7hf>g#sri^Q3NGIK}89)+*_c9?slgwz3g`9 z_^{GuU1oM>+XYPa_q6k!Gc*4)-}%n@W)^bf$dTi2qU<<}h&!xl%Rwy@ojE8b0)->g za1)_bIXDhdAMp9=l~PfwCtD666p5@r)K-Bhnd!95InXzi`u%&XIHwIjM4aJ>wnb3u zN3P8}N@RP$@2gcx>1KY8bQiiC;sHdd4WUkLb2>lM83;upD@4>+qbDm9)bdilx8BHV zI0GW$7ExQWBg#60><|&Rk=-zWM73dRR#e$Q!Q`-}Ei47H@~1NS<4c2Dp%KSqDaY6b!?C5<~d_?M z6Ca$$>2WY~L&a_HQ|kBKR;%q9$p7I>oL8C?2cn~n9i@BaJ1OD;2A4ih-;L?Wx4#dK z$(1nr+y_#8t6rbNq-X3GKHCAf|2l*-d<_8AatRv$W^jf<7^AxQ}==kq(abYv^U;A>5sss&y3%_6@Rc7P2&0$kN3@!jS=L zn_X-?mD*3PW?r2zEy#d&vci1yp0e0Qvu zg+)!NhBVj?;AUVPeeU6{$>dd2(PMEAuls|G=X!|6bh>+De0QLY&2^_(ySf7JSQh|3 zuM2N=kQW;r{Mrdo9R_us{Z}vX=HcBuU1l$RAcIEs=k_LoPn1zIyLf0!ABgk)fi}K5 z*v|EiehLcnm^!JDnk5r?aJnz)d|hB?i{jAr4qC#8xX{%>RdESxD`&GfSZ*lPsF97h z6W=LDtrOpCm<7;t$5f=J%gA6Bz||}W$qZ$z#V`P+Xv~h93=obPnM`It3_m8_X_S%% zLz|I7L|mar*C}9HR#aI;!TCV3x6{ + /* eg: + { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true + } */ + + //console.log("ANCS",msg.event,msg.id); + // don't need info for remove events - pass these on + if (msg.event=="remove") + return E.emit("notify", msg); + + // not a remove - we need to get the message info first + function ancsHandler() { + var msg = Bangle.ancsMessageQueue[0]; + NRF.ancsGetNotificationInfo( msg.uid ).then( info => { + E.emit("notify", Object.assign(msg, info)); + Bangle.ancsMessageQueue.shift(); + if (Bangle.ancsMessageQueue.length) + ancsHandler(); + }); + } + Bangle.ancsMessageQueue.push(msg); + // if this is the first item in the queue, kick off ancsHandler, + // otherwise ancsHandler will handle the rest + if (Bangle.ancsMessageQueue.length==1) + ancsHandler(); +}); + +// Handle ANCS events with all the data +E.on('notify',msg=>{ +/* Info from ANCS event plus + "uid" : int, + "appId" : string, + "title" : string, + "subtitle" : string, + "message" : string, + "messageSize" : string, + "date" : string, + "posAction" : string, + "negAction" : string, + "name" : string, +*/ + var appNames = { + "com.netflix.Netflix" : "Netflix", + "com.google.ios.youtube" : "YouTube", + "com.google.hangouts" : "Hangouts" + // could also use NRF.ancsGetAppInfo(msg.appId) here + }; + var unicodeRemap = { + '2019':"'" + }; + var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16)); + if (appNames[msg.appId]) msg.a + require("messages").pushMessage({ + t : msg.event, + id : msg.uid, + src : appNames[msg.appId] || msg.appId, + title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer), + subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer), + body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) + }); + // TODO: posaction/negaction? +}); + +// Apple media service +E.on('AMS',a=>{ + function push(m) { + var msg = { t : "modify", id : "music", title:"Music" }; + if (a.id=="artist") msg.artist = m; + else if (a.id=="album") msg.artist = m; + else if (a.id=="title") msg.tracl = m; + else return; // duration? need to reformat + require("messages").pushMessage(msg); + } + if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push) + else push(a.value); +}); + +// Music control +Bangle.musicControl = cmd => { + // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark + NRF.amsCommand(cmd); +} + +/* +// For testing... + +NRF.ancsGetNotificationInfo = function(uid) { + print("ancsGetNotificationInfo",uid); + return Promise.resolve({ + "uid" : uid, + "appId" : "Hangouts", + "title" : "Hello", + "subtitle" : "There", + "message" : "Lots and lots of text", + "messageSize" : 100, + "date" : "...", + "posAction" : "ok", + "negAction" : "cancel", + "name" : "Fred", + }); +}; + +E.emit("ANCS", { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true +}); + +*/ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog new file mode 100644 index 000000000..bbeb8b717 --- /dev/null +++ b/apps/messages/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Add 'messages' library diff --git a/apps/messages/app.js b/apps/messages/app.js index 801434498..2fb0c613b 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -12,15 +12,10 @@ /* For example for maps: -GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) -GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."}) -GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."}) -GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"}) -GB({"t":"notify-","id":1}) - -GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) -GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) -GB({"t":"notify~","id":1,"title":"High St"}) +// a message +{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"} +// maps +{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="} */ @@ -37,6 +32,21 @@ function saveMessages() { require("Storage").writeJSON("messages.json",MESSAGES) } +function getBackImage() { + return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); +} +function getMessageImage(msg) { + if (msg.img) return atob(msg.img); + var s = (msg.src||"").toLowerCase(); + if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); + if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + if (msg.id=="back") return getBackImage(); + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +} + + function showMapMessage(msg) { var m; var distance, street, target, eta; @@ -50,48 +60,98 @@ function showMapMessage(msg) { target = m[1]; eta = m[2]; } else target=msg.body; - layout = new Layout({ - type:"v", c: [ - {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, - {type:"h", bgCol:"#0f0", fillx:1, c: [ - {type:"txt", font:"6x8", label:"Towards" }, - {type:"txt", font:"6x15:2", label:street } + layout = new Layout({ type:"v", c: [ + {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, + {type:"h", bgCol:"#0f0", fillx:1, c: [ + {type:"txt", font:"6x8", label:"Towards" }, + {type:"txt", font:"6x15:2", label:street } + ]}, + {type:"h",fillx:1, filly:1, c: [ + msg.img?{type:"img",src:atob(msg.img), scale:2}:{}, + {type:"v", fillx:1, c: [ + {type:"txt", font:"6x15:2", label:distance||"" } ]}, - {type:"h",fillx:1, filly:1, c: [ - {type:"img",src:atob(msg.img)}, - {type:"v", fillx:1, c: [ - {type:"txt", font:"6x15:2", label:distance||"" } - ]}, - ]}, - - {type:"txt", font:"6x8:2", label:eta } - ] - }); - g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1); + ]}, + {type:"txt", font:"6x8:2", label:eta } + ]}); + g.clearRect(Bangle.appRect); layout.render(); Bangle.setUI("updown",function() { // any input to mark as not new and return to menu msg.new = false; saveMessages(); + layout = undefined; checkMessages(); }); } +function showMusicMessage(msg) { + function fmtTime(s) { + var m = Math.floor(s/60); + s = (s%60).toString().padStart(2,0); + return m+":"+s; + } + + function back() { + msg.new = false; + saveMessages(); + layout = undefined; + checkMessages(); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:"#0f0", c: [ + { type:"btn", src:getBackImage, cb:back }, + { type:"v", fillx:1, c: [ + { type:"txt", font:"6x15:2", label:msg.artist, pad:2 }, + { type:"txt", font:"6x15", label:msg.album, pad:2 } + ]} + ]}, + {type:"txt", font:"6x15:2", label:msg.track, fillx:1, filly:1, pad:2 }, + Bangle.musicControl?{type:"h",fillx:1, c: [ + {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play + {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause + {type:"btn", pad:8, label:"\0"+atob("EhKBAMAB+AB/gB/wB/8B/+B//B//x//5//5//x//B/+B/8B/wB/gB+AB8ABw"), cb:()=>Bangle.musicControl("next")}, // next + ]}:{}, + {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" } + ]}); + g.clearRect(Bangle.appRect); + layout.render(); +} + function showMessage(msgid) { var msg = MESSAGES.find(m=>m.id==msgid); if (!msg) return checkMessages(); // go home if no message found if (msg.src=="Maps") return showMapMessage(msg); - var m = msg.title+"\n"+msg.body; - E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => { - if (chosen=="read") { - // any input to mark as not new and return to menu - msg.new = false; - saveMessages(); - checkMessages(); - } else { - checkMessages(true); - } - }); + if (msg.id=="music") return showMusicMessage(msg); + // Normal text message display + var title=msg.title, titleFont = "6x15:2"; + if (title) { + var w = g.getWidth()-40; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = "6x15"; + if (g.setFont(titleFont).stringWidth(title) > w) + title = g.wrapString(title, w).join("\n"); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:"#0f0", c: [ + { type:"img", src:getMessageImage(msg), pad:2 }, + { type:"v", fillx:1, c: [ + {type:"txt", font:"6x15", label:msg.src||"Message", bgCol:"#0f0", fillx:1, pad:2 }, + title?{type:"txt", font:titleFont, label:title, bgCol:"#0f0", fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"txt", font:"6x15", label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, + {type:"h",fillx:1, c: [ + {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back + msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ + msg.new = false; // read mail + saveMessages(); + checkMessages(); + }}:{} + ]} + ]}); + g.clearRect(Bangle.appRect); + layout.render(); } function checkMessages(forceShowMenu) { @@ -112,24 +172,35 @@ function checkMessages(forceShowMenu) { // Otherwise show a menu E.showScroller({ h : 48, - c : MESSAGES.length, + c : MESSAGES.length+1, draw : function(idx, r) {"ram" var msg = MESSAGES[idx-1]; if (msg && msg.new) g.setBgColor("#4F4"); else g.setBgColor((idx&1) ? "#CFC" : "#9F9"); g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); - if (idx==0) msg = {title:"< Back"}; + if (idx==0) msg = {id:"back", title:"< Back"}; + if (!msg) return; + var x = r.x+2, title = msg.title, body = msg.body; + var img = getMessageImage(msg); + if (msg.id=="music") { + title = msg.artist || "Music"; + body = msg.track; + } + if (img) { + g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering + x += 50; + } var m = msg.title+"\n"+msg.body; if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2); - if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2); - if (msg.body) { + if (title) g.setFontAlign(-1,-1).setFont("12x20").drawString(title, x,r.y+2); + if (body) { g.setFontAlign(-1,-1).setFont("6x8"); - var l = g.wrapString(msg.body, r.w-14); + var l = g.wrapString(body, r.w-14); if (l.length>3) { l = l.slice(0,3); l[l.length-1]+="..."; } - g.drawString(l.join("\n"), r.x+12,r.y+20); + g.drawString(l.join("\n"), x+10,r.y+20); } }, select : idx => { diff --git a/apps/messages/boot.js b/apps/messages/boot.js deleted file mode 100644 index dce3979da..000000000 --- a/apps/messages/boot.js +++ /dev/null @@ -1,36 +0,0 @@ -(function() { - var _GB = global.GB; - global.GB = (event) => { - if (_GB) setTimeout(_GB,0,event); - // call handling? - if (!event.t.startsWith("notify")) return; - /* event is: - {t:"notify",id:int, src,title,subject,body,sender,tel:string} - {t:"notify~",id:int, title:string} // modified - {t:"notify-",id:int} // remove - */ - var messages, inApp = "undefined"!=typeof MESSAGES; - if (inApp) - messages = MESSAGES; // we're in an app that has already loaded messages - else // no app - load messages - messages = require("Storage").readJSON("messages.json",1)||[]; - // now modify/delete as appropriate - var mIdx = messages.findIndex(m=>m.id==event.id); - if (event.t=="notify-") { - if (mIdx>=0) messages.splice(mIdx, 1); // remove item - mIdx=-1; - } else { // add/modify - if (event.t=="notify") event.new=true; // new message - if (mIdx<0) mIdx=messages.push(event)-1; - else Object.assign(messages[mIdx], event); - } - require("Storage").writeJSON("messages.json",messages); - if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); - // ok, saved now - we only care if it's new - if (event.t!="notify") return; - // if we're in a clock, go straight to messages app - if (Bangle.CLOCK) return load("messages.app.js"); - if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know - WIDGETS.messages.newMessage(); - }; -})() diff --git a/apps/messages/lib.js b/apps/messages/lib.js index e69de29bb..f3ea242e5 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -0,0 +1,37 @@ +exports.pushMessage = function(event) { + /* event is: + {t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new + {t:"add",id:int, id:"music", state, artist, track, etc} // add new + {t:"remove-",id:int} // remove + {t:"modify",id:int, title:string} // modified + */ + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) + messages = MESSAGES; // we're in an app that has already loaded messages + else // no app - load messages + messages = require("Storage").readJSON("messages.json",1)||[]; + // now modify/delete as appropriate + var mIdx = messages.findIndex(m=>m.id==event.id); + if (event.t=="remove") { + if (mIdx>=0) messages.splice(mIdx, 1); // remove item + mIdx=-1; + } else { // add/modify + if (event.t=="add") event.new=true; // new message + if (mIdx<0) mIdx=messages.push(event)-1; + else Object.assign(messages[mIdx], event); + } + require("Storage").writeJSON("messages.json",messages); + // if in app, process immediately + if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); + // ok, saved now - we only care if it's new + if (event.t!="add") return; + // otherwise load after a delay, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + exports.messageTimeout = undefined; + // if we're in a clock or it's important, go straight to messages app + if (Bangle.CLOCK || event.important) return load("messages.app.js"); + if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know + WIDGETS.messages.newMessage(); + }, 500); +} diff --git a/core b/core index 5ef454a1a..59f80bb52 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5ef454a1acce54f6420015b519a7ecf461f9bc37 +Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e diff --git a/modules/Layout.js b/modules/Layout.js index 8a5b0a0a5..c7d44ab9b 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -29,7 +29,9 @@ layoutObject has: * `undefined` - blank, can be used for padding * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw + optional `src` specifies an image (like img) in which case label is ignored + * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw. + optional `scale` specifies if image should be scaled up or not * `"custom"` - a custom block where `render(layoutObj)` is called to render * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` * `"v"` - Veritical layout, `c` is an array of more `layoutObject` @@ -85,6 +87,7 @@ function Layout(layout, options) { this.lazy = options.lazy || false; var btnList; + Bangle.setUI(); // remove all existing input handlers if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -157,6 +160,7 @@ function Layout(layout, options) { } } if (process.env.HWVERSION==2) { + // Handler for touch events function touchHandler(l,e) { if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { @@ -245,10 +249,8 @@ Layout.prototype.render = function (l) { g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } }, "btn":function(l){ - var x = l.x+(0|l.pad); - var y = l.y+(0|l.pad); - var w = l.w-(l.pad<<1); - var h = l.h-(l.pad<<1); + var x = l.x+(0|l.pad), y = l.y+(0|l.pad), + w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ x,y+4, x+4,y, @@ -259,10 +261,12 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ]; - g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + ], bg = l.selected?g.theme.bgH:g.theme.bg2; + g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); + if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); + else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ - g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad)); + g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined); }, "custom":function(l){ l.render(l); },"h":function(l) { l.c.forEach(render); }, @@ -363,12 +367,13 @@ Layout.prototype.update = function() { l._w = m.width; l._h = m.height; } }, "btn": function(l) { - l._h = 32; - l._w = 20 + l.label.length*12; + var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label); + l._h = 16 + m.height; + l._w = 20 + m.width; }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image - l._w = m.width; - l._h = m.height; + var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + l._w = m.width*s; + l._h = m.height*s; }, "": function(l) { // size should already be set up in width/height l._w = 0; From a530c7e391a27d3c47469dbcfa9e1dc4e3b51398 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 19:38:06 +0000 Subject: [PATCH 243/325] more icons --- apps/ios/boot.js | 4 +++- apps/messages/app.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 69e5e2a26..c3ccb9275 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -56,7 +56,9 @@ E.on('notify',msg=>{ var appNames = { "com.netflix.Netflix" : "Netflix", "com.google.ios.youtube" : "YouTube", - "com.google.hangouts" : "Hangouts" + "com.google.hangouts" : "Hangouts", + "com.skype.SkypeForiPad": "Skype", + "com.atebits.Tweetie2": "Twitter" // could also use NRF.ancsGetAppInfo(msg.appId) here }; var unicodeRemap = { diff --git a/apps/messages/app.js b/apps/messages/app.js index 2fb0c613b..749cf3c73 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -41,6 +41,7 @@ function getMessageImage(msg) { if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); if (msg.id=="back") return getBackImage(); return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); From 0a527c08c60515bf7b1e0842179b4a22e0b65696 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 19:38:24 +0000 Subject: [PATCH 244/325] fix sanity check --- bin/sanitycheck.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 9c5f4c916..a84d26efd 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -121,8 +121,8 @@ apps.forEach((app,appIdx) => { if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (app.dependencies[dependency]!="type") - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' right now`); + if (!["type","app"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`); }); } else ERROR(`App ${app.id} 'dependencies' must be an object`); From baa36bdd91b9e89e4eb8ae3d4febc8e296fac211 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 8 Nov 2021 09:42:40 +0000 Subject: [PATCH 245/325] about --- apps.json | 2 +- apps/about/ChangeLog | 1 + apps/about/app-bangle2.js | 3 ++- apps/android/app.js | 2 +- apps/ios/app.js | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 156b6e5b0..eb7289153 100644 --- a/apps.json +++ b/apps.json @@ -132,7 +132,7 @@ { "id": "about", "name": "About", - "version": "0.10", + "version": "0.11", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 9557e448d..03e920a9a 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -8,3 +8,4 @@ 0.08: Make about (mostly) work on non-240px screens 0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021 0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) +0.11: Bangle.js2: New pixels, btn1 to exit diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js index 8a0be9f3d..32e5bafae 100644 --- a/apps/about/app-bangle2.js +++ b/apps/about/app-bangle2.js @@ -6,7 +6,7 @@ var ENV = process.env; var MEM = process.memory(); var s = require("Storage"); -var img = atob("sIwDAG2S66DYwA2AAAAAHAHGSRxJEkAAgmGGBxDIADIdAFJIbAHF9HP00kBUC6DtzDgAgGOxwkgAGbA86CW2222kkgB4BO26/XDDwAwkEEEgYYA+VW22wEAAggwAG2AZZZTFotMIDAA9vB520AJUnXAtwAgAgGxOw2wo+bAmiSAAAAQUkAHMAO2/66TY2GwgggghB5/+SRxJAEAAlm2ABxADLKYFFFBADA/99HP00kHoC6DuzAAAAGOxwkg+uzG86CQADbSUgAB+CSQAAADDAAtEkEkAAAA2khxIAHAAgmGLADIDLLAAAAEDDSQQCAAAAAHA4AAuwAAAAAAAAAAAwAIIAAMgAYQUAAA4CongAAAGABqEkkkAHHGGhhxIHHXa66ADYbAACcEHzUBDbQCSSQAAAAHAAADDDDAAAAAAAAA14GGGABEEAYQWBAIDCQ84AAowwIYQkkiS4g42khxIA4inNPAA1wAATkkABCSAASQQikm2SQHAAAAAGwAAAAotoouJwAIIABEgYYAAJIIoCI84AFt2wBCAEkbYEEHPABxIAAfSqqSQ1wACcEEAACSAAAAAggmACAH/41gGgEwH/AtoFFG2wGGAABEEDYAAIJAtAI2Gw9twwwYAAm2AAAB5AR0kAAAHNPKo2wAT4AAoFCShYCSAgkmwCAAAAWoWgEyCSAottoub2GAAAAMgAAAAJIAFCAwww9FAGwAA9th5AgHPAR22A/AC66KoBGCd8kksAEATQCAAggmFCAEnAawGgEwEkAAADbutwGAIBIAAAAABmhAYAAwA35xAg22A9t5PQAAAARySA44kAEikAAzo+22vJPAZgCCEAgm2CAgAoAGvGwBJAAAGADGGGGxBBAAAAAAMEEIASQAA5+2EEwzb9tn/QAAuARwAA/An4kkkgCeA9ttsAEAAACSAAAAAAAgAooAGAABAAxwGADGGGAAIBIAAAAAMAEP/QUkAH5xAgADD4AAUkQQuQRASQ4AEAEkkATow4AAAAASjDFCSAAAAQAgAtAAoAABIAvgGDDG2GAAAAAAAAAABghHJSG2CSwAACQDb4EEAgGCuQQAyw4HkAAkggdAG4AQAAA0zDFCAC2wDDAEnooH19At2AywGAYGGAAAAAAAAAAAAMIH/QQAACGAASAAA4DjAgwCuCAASwAAABJEEDbbbbbbYAAiTbtqVCbYQQQAAoFAAoFAGAQAG2GGGGwAAAQAAAAAABAGmLTIkCwDACAPIAAAAgAQuQAAJIAAABAEn/JIAIMAEEAADDFCAFAoDDAAAAAAAoAu2SRJAAIAAAAAAAAAAAAAAAAEkNK6iCDDAAA+PADbAG2AuCAAJQAIopSEz7BAABEgEgAADDFCAAAAAQEEAAAAAoAFAQQIAJIAKcu4AACAAAH/AH/GmjaSkSAYAYAJ4ADrAAFAuAQAKIAAtpBmXvBNBIMkEEAADDFCAAJJIAkkgAAAAFFoASQMghIAAAAAAACAAA/H4/HAAZK6EAAAAAAAAADbDYFAuACADAAwFBMydABAABEgG22AAAEsHAALbAkkgAABJACQSAFIggkggkgkkAACaAAJ/4AGoQRYkEAHA/+AGAAAYAAtAwAQHYG2ABmToABIAIMAwAAwAAggn/4LAAEkAAABAIQCAVlAHA2222222AAwH/BxJAYEAgHHAA4AAd2w22EADAAAAAACA7AwAKSdAAA44AAAGG2AAAEkA//LAAAgAA4BAISSAVoAkASqSSSSSEkwH/BIIDbDggn4EgjYAMWGGGEAAYAAAAEAEHYAAjTowAAEAAAAGAAAbbYgAAALFdHHC2ABBAQCSloHcAbbbbYYDEAwH/IIAbbaAkAAskDDASWEmwEgbEHEAAAxOAABJzdAAAA44AAAA2wAAYAAAADbDDH4CGABJAQCklAAkAtootottEkCaAJIADbBoggFtAjYDQWgmGE84H//AAACQAAAhRpAoCA84ACSSwAAYYoAAAAAFdAACSJJJJJJklAAkADAAYAAAEAAbYMIAAYAAggtoADDDSQkgAEmgA44SSSCIEAAadAAgkg/gACJKGAADYAACQAAAAAAAYAAAABMkoAAAADADAQAAEADAAggAAAADL1tAAYAYCAggIE84CHAQCAALoAmboggYggkgttJKAYAAAAAVqBgQAS/kIGAwwxJkoAAAADAYAQAAH/DAAkgAAA2AattFoAAAigwBBAAAB/4SSSAJAEyd84YQkgAAACJKJJIkkAAQCAnQAS/kYwwwwx5koAAAADD2QQAGHXDADlgAAAwxVgtFAkgEkkJAIwAABkgSSSQLomT4/gCIDADAACSSQQQ//AACQAgWAS/kI2w2wxJkoAAAADe2CAAgg/DADFAAAA2CZc8loBgAggBAAggQAAASSSAIEydA84IADDDHXAAAAAASSAAADbAAAS/kYwwwwB5kNAAAADe2AAEGEHAbYlAAAAwzJP/9ABgAAAGGAbYAAAGAEbAAmTsAABJADYYH6AADbAAAAAAMIY2w2w2kIwwwwwJJNAAAADDwAAgGAgAAEFYAAAAEZc8loBgAAABJAggACAAAEYYEydBMJEkHnAACSQAADBSJllIJLQGAwA2kAAAAAAKSIAH/ADAYASHMbtbh4kAAAAwFIAAABBgGWWWWAAwAAAAEEbAmToBJJEEEkGJwAAAAYbAAlAIMLb0w2wEkEAgnJJKSIAAAADADACHMkkkh4HAAAASGQJJIgIgAAAAAE8888G2AgYcydABhhABHnGkwQCAAYYAAAJxIAAAAwAEkEgg/O2LJIAoAoAAAFksAkAAAHAHAABJPYIAIEkAAAGOEkQAAAG2AEkmkgABJJAAAABkIAAAAAEggAAIAA/EgJAEkEEgJO1NAAFjDlAAGAsgAgAAAAAAAwxJOgIIIAADbIBxAgQEAkG2AEEicIAEAAAAAA0AACAAAAEEgAAIAAHAgBA44EAgA2FtAAFkclAGGGEAAkAAA4HJ4AAJFoAAAA/ADIGOAiQEEAm2AEmjkkJ0w/4AAAgwCAAUkkkkgAAAAA/EgJAHAAAAGwADAAAckYAGGGAAAEAAAAIAACABJwFAAAADDAACQAZYkEmAAEylIBN01/4AAAwDASSH//8kgAAAAA4EAICXkAAE82AbAgkjjAAAwwAAAkAAAHAHEkkBJIFwYAHAYkkgeYLIAAGAAkEhABJAEgAAGAgYYEgBJJIkAAAAoAAAAAAXgAOP/AwAA//4YAAAA444AAAtoSXAEEEBJQEGYAHAAAgQYYZfA4HAEEdgAAIgggAAAmAbYEAAAAAAAAAAAA4EHNtHAA008AIAAJJIAACSQ444AAAAoQAgAAAAcgEAwAHAAAgCkgAAHHAAmUpgAAIEgAAAAAAYZEgE8H/AGAwACQAAHHBA2AOIEWAAAPkkkgQAQEAgAAAFASAb2QQAjYYYbfHAAAgAEGwHAAAEycIgAAAgjCDAAAAAQAgE8EkAGGEMQCAAX9pIwAAAAiAAAPn//4AAQgggAAWVAQAf2AAAcAEkAA4AAAgAEGBJHHAmkhAAAAEgg44884AHXEg8kA/4GwBAQAAAXHBA2AAACEAAAPhJJIQCAkggADADASW59QQAjSQiAAAAAH/AEAxAHBAxkMdGA2AGADAn/gAwQwAAAH//GGBAQSAAXFAAAooAIAAAAkkgAAAAAgggAo2woAwwAAAAcQAiAAYFYBJAkmxIHBBRhTr2wwwwwII884HAAHAAAAkgGA0MQCAAQAAAAtooAAAiA//4A4AAAAAAAgwwgEGEAFAAjSACQAoDAHPAAABAHBBFIUd2www2wCAHnAAAAAAAAEEEAAABCQBJu2AAYoooAAEAQJJISSQAA444AAwwAAkgAbYAcQAAAFYdAHPAAABAABJBASw1N2AwwAAAAAAEUQwAA9gg4AAEAAABoBADDAAHHAAiAAXn/AtoAAAAAAGAAAEAADAAjQgAAAEEEAAAAAHAbYHAJIwAIAAtoAAAABJMkQ/4H4EA/AEIAABJohCDbAHAwAAgQA45JA2wAAAbYAAAACECAYYAcAABBJA2wAwAAA64bY64IwwAwAAooCCoSQJMUQkgAHAHAbYAAGxAoACDAAA/4AACIInUkbbYAADDDbbbYAUQAAABAAAHB5EEEGGAAAAAbYAAJGSQoAAtoCSoSRIAAAwAAA/4AA4AAGGJbbaAbAA44AIIIIQBIbAAYADDDtttvHEAAGAGQkSHB5AAAAwAAAAAbYAAIASQwAAoAiCtQQP/H4wAAHgnAw4SAGGAAA/HAAIAAABAII4kkAAAD4AbY///77EXNkgG8gSIAAAAACAO0AADbbAAIASQAAAowgAE0BPJJ4DYf8EE/bYSQGGAAHA5AAIgggQQJIBJJIAAAYAPItttv/EzjEAAUkRLADAAkiQMAAbbbbbYIAAAHgDAmgAGOADAAYDbY4gg4AAQAGwC8A/HA/JEEACAEESAAttAADA/4bbbY4AIAggABkJLtrAA2wQOmAcjjjkYAAAAHkJY0wDE0AB//IAbAAEmRACSAAAC8AggA/CqqAggGmSAFAAoAAYPIAAAH/HKXIAAAkALmDgAAAAIkAcktskYAAAAH8JDAAbAAAAAAAAAAAkyNgASFo/68EssATAVWGEwBxSAo2wFAAAAAAAAHH4R4FtEM/AIgwlllADbAAbbbbbYAAAggjYAbDAYAAAAAAAACCWRtwoQQt4CEEssA/ACG2GAAISwowGFIIACCQAAHHAAgAoEkAAMm2kEAAA4AAAAAAAACSAggjDEAYAAAAAAQAQbaSQAIIoCAo/CEAjgA/AABxAwAASAowGHHHACCAAS8ADAgAoEM/AAwwwFAAA4AAIDvrAACCAEMDYxwAAAAAAASCQtqCFtJIAAAA4CEAjgBJAHAwHGAASAo2wBABASCQAX4AYE8ooBJkAGww2EAAAAABADvrAACSABhDEIMAALJBAAQQQ2wACSIIAAAA/4AAbYBJAAoAowAASAowAvAHAAAAA/gmwA/ooBAQAWG2GFAAAAAAIDvrAlKCAc8bAxwAALIYDAAAAAAAFtIIAAAAAAAAAAG2VRPv4AAASYFwFAQAAAAAAgEAwAntoBISCWwA2H44AAADADvrAlLApkchAEAhgLJBAAAAABJJMkAHPAAAHnSQAAG2VRpvvAAASOGtoAAA4HAHBIAAQAn/4BAQQQ22wHHGGGGAYDHrHlIookkgAAAIAAAAAA4AAAAAH/kh5CBEBJSCAAEkSRNv9HwAAIAEA84HHAAAwBAACCSHgB/AAAAAAH/+wAwCAAAAAlLFoEkAwAAgAACAAH/AAH//8k23PAAAHnQSAAEkVRpvvA+AAW2kgkgH/AEA2IAAwAEEABAAAgAAHFtuGAwIQAAA4lLAsEgjwEAIAAUQAHAAAAgggAAAAAAAAACSAAAAVRNv4HwAAQEEE84HHHAHwBAAAAAgAAJBYAwE49AAAAAeAAAAwAAAggggwgghgASQAA4AAA444A/4D///7AAAQARAIASAAAAAAAPMAAAAAA/4BIAH/AkkgGADIAwC44tAAAAIwAAA4AEgE0A/wEAAAAQQAAHA1AkEAA8kAEkkAACQAABLICQAAAAAEk58AQAbbYAAAAA4C4ggg22ADA2249AAgkAYYAAAAAm0AgwwEikH/kn/AH/G1oAAAA88AAEAAkQCAABZICQkkkkkG2PIAAAYYYAAAAHwS3AAAGABBA2KHDrYwwwIwAAHkgm0AGGGACAGJ03nAA4ASAAAAA/8AAEYYkgATACSSAMkkkkkHGwEiQAbb88AAA+yC24AgAADIAwCAAYGwAADAABDWQEgAGGGACAEk03/ADAAQAAAAA8kDEEbYtgkKIAQQBBkkkkkH+wEtVAc0f/DYA+QC24EAEkAAEQGgDbYA23AHAAHQQCAkkmACCCAAAAAAeYGwoAAAAQAbckYYJgERAAQCPHIAAEkHGAEtVA/H88DDA+QW24EgEk/4AAAAABAIkwAAABYEASACCAAASQAAIAIAbYwAtbAAC6DYbAAAJEgAAAAAIAIAAEkAAAEiQA//4AEwA+yW24EAEk44IBBIJAAAA3HHAA4AACQCCACAAAHABBAAYYwAtbAHXXXbYoAoAAAAAAAABJwAAEkAAAAAAA//4AEGAf2W3EkgAA/4AAAIIBAIwwAwAAoAACAAAQSQQDA4DLAQAAwAAYAAC6ADAoAoAAAABSIAAAwAAEkABIAFFtP/IAEGAb//4AuoDYA4IBBIIBAI2wAIAA4AAAAE8QCAQrACDDCSkAGwAiIAAQ/4GFFBJAAAB/I4A4wAAkkABBAFFAJJIAEGAb4/4AywYDkgIBAAAAJAkgAQAAAAAoAggiSSATA4AACCAAAAA1gAA484w1FBBAAABSIAAA222EkABIAFFo5J4AAAAb/A4ALIYD0wIBAAAAA4AAQwgHAAAAEAAESQATHAX/CSg0AAAKYBAA/4AwoBBAAAAAAAwADMQSkQBBAFtA4A4AAAAbH/AAAAYDkgBIAAEAAAAAHPAAAAQAgAAAgAAtAAGACCA0D4AAABrwAGAwAIIAAACAAgAgZiCCCABIAAAA//4AIImTA4AAAAAYAAC4AAkgAAgEAAAACCXvAAA/n8/k8Q2wCCk0HYAAABrYAw2AAAAAwQDAQEkDMCCCQAASAAAAAAAAJMyYAAAAAAAA//HCAEkkAAEgAIIBCCrroAAHn8nn8W2wAAAAAAAABBtYggAAAA4AADFDAAgAAHDAAACAAAAAAAAAIIAAAAAAkgFtA46HAkkggAEgAIIIKCAAAAAHk88n/Wx2AbkkkkkkhJBAggAH/AAAAAuoA/4AAHYYwFCAAAAAAAAAIIAJaQwEEEFAA/HQEkkHEAAAAww2wQAAAAAHn88k8W22Aee22222wAA4GFdHvA/4Cd31akgCAHYYwFASAAAAAAAgAEAAYQwAkgFtw4wwAkkgAA2wA2wwwAAAAAA/EAAAAW22AbAAPIH/AngAgjrH/A/4AAuoASQAA/DGAFtAAAAEAAE5EkAIaQAEEEFA22wowEkkHA2AAAAAAGAAAAA2E/8ro2w2AYYA/44A4/4AgldAAA/4ADFDAoEkkAoFEEkkETA2DAnIgggJYQwAAAFAwwwwAAkgAAAH/4wwwGebbAA2E88dY2AGADAAPI4H4nmwABJAAA/4AQDAQoAAAFAkEkkkkjEkYA5AEkAAAAAAAAAAAAAuoAEAAH/44HwwAG222AA/E/8rowmlFtoAAA444AGBBBHAAAAAQACAAobYAoFFE0kk0TGzAAAAAFtCSAAAAAA4AAAAAAAAAHAH4H2wwWebbAA+glVGgm21tFEkAAH/AAGxIBBAAAAACABAAoYYFAgFEGkmEjEaBJAAAFtCSAAAAAAFSQAwigEg2HCA4HwwwQAgAAF+giKGEGmlFFAQQSAAAAGBBBJAAAACSJJJIobYoFAEEA0wETDyBAAAADbCSA2SQEECSQAARQEAYYVQ/4IAAWAggAA2glVGEGkk64ASAQQAAAAAAHArrAAAAAAAAoYFAhttEAGAEjfyBIAAAttoAAAQAwwwSQAAigAgbCACBJBASSQkAH/3gggG/+AgigAQASAAFtAAAA4trAAwAwAEgoYoFZosEAGAETDyBAAAABJ85JGSAQAXS2kFAHcgAAH////AAQAggHfkAEEk8ggg64EgYQQQAoA64/4rtAABBAwEAoFAoZtt8DeAcjAaBAAAEBJ85JAQADDBS2kFovD4HoskkkkEAQAgEH/AAAAA/4kgDYYAAAAAAoAURxIAAAABhGGH4ooEAZos8YADETADBAE2gh5855AQAAoGS2kFFHY4Hov////EAQAAAkAbAQAQBIkAYDAA//AJIoA6+2wEkgABBAwBAtAoAZtt8YADEjAAYAmm0B/8/5AAAAADAAAFAHDFItoAAAEkAQAAAggYYQQQIAggDYYA8nAIFtAABxKSkjYAAQABIoEAAbYE8DYAcTAADAEGAAAAAAAAAAAAAAAFAHYBoooAAAERJIAAAkAbACCABIkAAgAA8ngJIAAbYAAQkgDAkgAAAAAAAAABJAAAAAAAAACAYYY2AAAAAAAAAAAAAAGuAAAAAFEhAAAAAgAAAAAAAAAAACA4/8gAI44Aa8QQgAAkAgAAHHH/P/H/3/H/H/H/HASAbYYwAJJJJIAAAAAAGwFtAIAAAAAhAAAFAggAAAAAAAACCCAAAAAJIAAYYASUgAAYAAAAAAABJIAAwAAAAAAAAACAYYY22AAAAAAAAAAAzGGuHGAAAAAhJAAAFkAAAAAtoAABJJAAA4AAEAEbekDYbAbYAAH/4DDHP4AAADDAYYAAAAAAEkkgw////4AAAAAAwGAAQgQAAAAhAAAFogAAAAAoFDbFAF22wAAAEEEAG0GGGAAYkgHAHDYbAACAIYAL47AAAGwGEEAgwADbbYAAAAAAGwAAGHAAFAAhAAASWGIJAAAoFYAFotAwAAAAAggAAAtttoAYAgHAADDDAAQQAAoAbYwQAGwwEkggtttttoBJAAAAAAAoAIAAFtAhAYACG2BBSQAooYAFFFAwAHAAAAAAAFEkkFAYEgA4HAAAAAGAAYAIAAwQAGGGYAFo223//4BJAAAAAAFoAPIAFFFmsAASWGABRIAAADbFFFAwGgA44AAAFAm22goEEgH4H4BJBIwzDBBAAA0QAAAAYDAAbbdttoBJAAA2AB2oAPIAAFtiFAAAAAAASQEkkAAdFFAwGgHHHHXAtEyaa0FAkA/AA/A4MhwwgAABbZwQAAAAYDoFAADbbYCSAAAxwB2QAJIAAFFhWAAAG22wJIEEEDbFAFDwGgA4446AAm7rr+goAA/H4/HHBIGoArbDADF+AAADYbFoAAAA/kiCAH/2/5KSk/IAAAAgAAAAAAAxawEgkAAAAAYYAYHHHHXA4ndllfgoAwH4H4AAG2AFtAYDGDF+SQQAAAAAHIPHHAiQAH/3//4AAJIAAAAgJAACAG2xW4A2wAAAAAYAEEA4466AAmdklegoAGA//AAAwAwA4AYDADAGSQEEAAAAAAAAHEiCEH/3//4AG54AAAAbZAAAQGGxawACAAAAAADAIYPHHHXAomTsrWguwGAAADTGSreAAAABbZAASQEkAAAAEAEAHAiSnn////t2wJIAAAAYZQSSQAYwJIACAEAAAGAZAgB4464AogydawgoGwAAACqGAoGHHAAAAA/H4AEEAAAAA2wEHkgAEHH/n/9tAAAAAAAbYQQAgDbAAAACBW2GAGYYAz3HHHXAFEGTWEFGzbAAADTASrYAgADDAH///AEEAAAAAAAAAAAAYTH8k/4AAAAAAAAACQSAAAAAAAAIOOwGAGDAAbY4464AoogzwksADbAAAAAAQAYHHAltA/k8n4AAAAAAACQCAAAAYQf/n/20kkkgAAQAAGwGBJAAAAAIRuOo2wDAAz3HHAAAAFEGEBBADbHJIIIASrYAAAgoA/kkn4AAAAAAAQCCAwAAaAf/n/km222xAKQAAwAGBhAAAABMH3DwwAQgAAAAAAAFAFogmoIwAAHPJIIAAAAAwAgoAH8k/AAFllllAACCGGCWAAH/n/2wAAABBCQAAGEmBJFoAABIoooOwAPoAddoCEkFFFAEwwAGwAHJIIJBAAAG2AjDAA/n4AAAAAAAAAQCwAyQ1wH///4AAAABICQDAA0mBAFqABIHCSH2wAgw4FDCCHnAooAoGAAAAAwAAAGAAAAGGAkkkkn/FtrADAAAAAQCgAiCtoH///4AmbYBBCQZY2AGAAACABIoqSookAAAADFCSEnAAAAFigAAAMEAAA2wAAA/q6gAAAA4AAFBBEAkgEAAgAgS1wH///4A0cYBAIDLLAAAAAkCABAHCSHCAABBHAJKCDbFAoAAEEgkIMkAAAGAAAAvq6AAAAYGPOFAAEAggEQCGGGSbY////4AmbYAAABZZA2wEggiQQQAowoAQCBIHBICADAFFAA2GEEENMEAAAGAQQYtqSwwADDAHAFCCEYggcAAAwAAfYH/ADAA0BJAAAALIAAAggkACCkEnAHACABB/JJIADbFoDSww0AEAIwAAAkgSTYAgAwwgjbGIOFBJEYkgcAAH/BpbYAAADAAmIQA9FABHMkgEAggAAgkgggEgEAAG222HvvFFCAwwwBJJIAAAAAAQQYAgA2wgjbAAAADAAbADYAUg4FFkgAAADgE0IQHFoAABMkgAAgEAAkEEABJJJAA2GG2HvvFAoSDADBJJAAGWAAAoAAAAAwwAAAAAARP8gYYYYAWg4FIijbDDbkEmQSQ9FAAEkkgAAAAgAAAAggAAggA2222HvvAAAADYbBJJAACiAAAooAgA4AAH/CACCBP8gbADYAUg4BFkjAADDgk0gQQAAAAGAAAGAAAg2AAHEHAAEAF2AA1HvvDbAYDDDYBAYAGWAAAoEkA/AAA//6CCARP8gAAAAAAAAAAtrDDDDwAmgSQAEkkAYAAFGSAkGAAASkkAggA+224F//AYDDDAAbBAYAbYAAAov/gA/AA446HCCAJJFAFAEkAAAMgSrDDDDSQMEkB4AAwGAAAouWAAGAAAW0kA44AFotAAASAaQYDAAGxYYYYYAAos//8HAAA446A6AQABFAFAEAgAAIESrbDDbQBIoQYAAGAAYAAFCWAAwAAASkkAIIkQDAAAACACAA2AAABbbYbYgAtt7D8AAto//6/6CAABFAFAEAgAAIEtoAAAgSIIGABAAAAGAAADH/AAAAAA4A4ABAigFAASQiCChABgdoBAAAAFtAAAgAgAA/oH/CQSAQIBFAFAEkAAJMgJIBJEgRAIoAA4AAAAYAAYfnAkgHHAAH/AIIkQBQAAQgQQhABgYqBAAAAttoAAFEAJA94A4ASQCABIFtFtEAgAAAA54BAggAAEAABAAAAGAAADH/AgEnHAAAAAAAigoDAAQEEEBhhgdqCH/AAssoAAAwAIA/4AAACAAQ4w4w4w4w4AAAAJIBBkkAAAAAP/4AAAYAFvAAAkggAGAAAAJABOAEAAQAggAgggaCSJJAAttoAAAAAIAAAAAAAAAAbbgAAAAAAAAAA2wBJAgAg5//J/4AAGAAF/AAAAgmWWWWLIJGBIAoAQQAAAAEAkiSA//AAllgAAARAJAAwAAAAAAAbbAAG2wAG2wAAOIAAAAEEPJJJP8kkAEAFvAJIkggwwwwLIAGAAGAACJASQYAAAACAIAAAEkAAACIABAAAAAFAAAADAAAwAG2wnOAAEAAAAQAE5/JJJ8nkFEAAAA/4AEgwxwwLIAGAA4AAAIIAQb7AwASQAAAAgAkgARIABAAA4AAAAAHHHEn8gA2AIgAAggAQAQAgAAAJJM/8tEACAAtqSJAGAGAAAgAAgIEAAIOCG4GAAGFwAAuAgAgACIBAJAAAAAEAAAAggEn8gGAwgIM3EACQSSUkAAbeecnkFEAQQ1wAQIIwxwwkgEkkACYAAIIQAYAwwwAQAGAIgAkgRABAAAAAAomgoAAkgEn8gADY5hg+EAAAVtQAH4YGmkkkAICCCGuCQIIAAAAoAAAAAAAAAJASQ4AG2AAozAGAgAAgIJIAAAAAAAECAAAEAAAAAAoFQDPcggASV1SQAADEgkngBxAQQA1wAJAAHgAFAAkkAFwwAwAGAD7AwAQQAAoAkgkgFEkAGAAoooAWSACAQAAAAAHAQYAxggGwVtTbbAAYAE//AIACAAAAAJIIk/AAoAAgAHI0kwAAABJAAAGAAGMIAAAArsJAwwAFFFFSSQSQQAkQ/9AoEAGGVQk1SSVroAbYAEngBxFtAA/HABBA4kASQ4EAAAAwwwAAAFotAFFoAtAAAAAFdEMAwAAoooAQSCCCQAmgJIbAkkG2qoG2wQDrtoqSJEACAIFkoAAHABGIE4AAAAkkAAAmGgAAAIAIAAAAAAAAH//FoFFAGbbYAAAACACAQAUgSQSQAAGGqsE2ASDbvooABEATQbdkoA/HQAAACQAkkgAAAAAkEgAAAJAIGG2GAYAAYHAQQFFAAwZJAAEgkBIAA444QQQQCSSSVUkxwXUgttqSBEAQQYdtAA/AAAAAQCw/X4w84AAgggAAAIIIGGAGADADknAEAYAYwwZAH/EEEIAAABJASQSABAiBqsE2ISU8oooBBECACbdAAAACSAAAWyA6S4AigAAgAgAAAIBIGAGGADDD2nGEGD7AGAZBHPEEEIAAAAgAQAQQBkEBVUExwQUgooqSJMgFAYFFgAASSQAAQCAkkgA84AAgAgAAAIAI2G2GwAYY03A2wXAH/6ZJH/EAEBIAAHHAQASABgkB"); +var img = atob("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"); var imgHeight = g.imageMetrics(img).height; var imgScroll = Math.floor(Math.random()*imgHeight); @@ -69,3 +69,4 @@ function drawImage() { // TODO: a nice little animation before setTimeout(drawInfo, 1000); +setWatch(_=>load(), BTN1); diff --git a/apps/android/app.js b/apps/android/app.js index 5c5c7ddaf..b210886fd 100644 --- a/apps/android/app.js +++ b/apps/android/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -load("messages.app.js"); +setTimeout(()=>load("messages.app.js"),10); diff --git a/apps/ios/app.js b/apps/ios/app.js index 5c5c7ddaf..b210886fd 100644 --- a/apps/ios/app.js +++ b/apps/ios/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -load("messages.app.js"); +setTimeout(()=>load("messages.app.js"),10); From 4a3ef4829c8036394a21efc274c8d4484d21c82c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 8 Nov 2021 10:03:33 +0000 Subject: [PATCH 246/325] alarm 0.14: Order of 'back' menu item --- apps.json | 8 ++++---- apps/alarm/ChangeLog | 1 + apps/alarm/app.js | 6 +++--- bin/firmwaremaker_c.js | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps.json b/apps.json index eb7289153..a700dc58c 100644 --- a/apps.json +++ b/apps.json @@ -94,8 +94,7 @@ {"name":"health.img","url":"app-icon.js","evaluate":true}, {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} - ], - "sortorder": -2 + ] }, { "id": "launch", @@ -142,13 +141,14 @@ {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"about.img","url":"app-icon.js","evaluate":true} - ] + ], + "sortorder": -4 }, { "id": "alarm", "name": "Default Alarm & Timer", "shortName": "Alarms", - "version": "0.13", + "version": "0.14", "description": "Set and respond to alarms and timers", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index fce54f273..d129e9f9f 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -12,3 +12,4 @@ 0.12: Fix widget for bangle 2, now uses theme Widgets now shown on Alarm screen 0.13: Alarm widget state now updates when setting/resetting an alarm +0.14: Order of 'back' menu item diff --git a/apps/alarm/app.js b/apps/alarm/app.js index fdb7784f7..53c7154bc 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -33,6 +33,7 @@ function getCurrentHr() { function showMainMenu() { const menu = { '': { 'title': 'Alarm/Timer' }, + '< Back' : ()=>{load();}, 'New Alarm': ()=>editAlarm(-1), 'New Timer': ()=>editTimer(-1) }; @@ -48,7 +49,7 @@ function showMainMenu() { else editAlarm(idx); }; }); - menu['< Back'] = ()=>{load();}; + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } @@ -70,6 +71,7 @@ function editAlarm(alarmIndex) { } const menu = { '': { 'title': 'Alarm' }, + '< Back' : showMainMenu, 'Hours': { value: hrs, onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' @@ -119,7 +121,6 @@ function editAlarm(alarmIndex) { showMainMenu(); }; } - menu['< Back'] = showMainMenu; return E.showMenu(menu); } @@ -174,7 +175,6 @@ function editTimer(alarmIndex) { showMainMenu(); }; } - menu['< Back'] = showMainMenu; return E.showMenu(menu); } diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 28fbd2f05..eef54f226 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,8 +29,8 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launch","antonclk","setting","health", - "about","alarm","widlock","widbat","widbt","widid" + "boot","launch","antonclk","setting", + "about","alarm","health","widlock","widbat","widbt","widid" ]; } else { console.log("USAGE:"); From ffc290cd6a6d7205a8ff32414fa68d94868148fe Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 9 Nov 2021 20:13:55 +0000 Subject: [PATCH 247/325] welcome as a default app --- bin/firmwaremaker_c.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index eef54f226..14ced9ef8 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -30,7 +30,7 @@ if (DEVICE=="BANGLEJS") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launch","antonclk","setting", - "about","alarm","health","widlock","widbat","widbt","widid" + "about","alarm","health","widlock","widbat","widbt","widid","welcome" ]; } else { console.log("USAGE:"); From 8b987aa1f45bdccfbdcb27b0dbb705f0e03e6ae8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 9 Nov 2021 20:18:29 +0000 Subject: [PATCH 248/325] remove sortorder on apps that might confuse new users --- apps.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index a700dc58c..6ef598255 100644 --- a/apps.json +++ b/apps.json @@ -1782,8 +1782,7 @@ {"name":"toucher.app.js","url":"app.js"}, {"name":"toucher.settings.js","url":"settings.js"} ], - "data": [{"name":"toucher.json"}], - "sortorder": -10 + "data": [{"name":"toucher.json"}] }, { "id": "balltastic", @@ -2060,8 +2059,7 @@ {"name":"dane_tcr.app.js","url":"app.js"}, {"name":"dane_tcr.settings.js","url":"settings.js"} ], - "data": [{"name":"dane_tcr.json"}], - "sortorder": -10 + "data": [{"name":"dane_tcr.json"}] }, { "id": "buffgym", From 779e550152b6c2f7a872824ffa4b1dd72707828b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 10 Nov 2021 13:06:15 +0000 Subject: [PATCH 249/325] Fix issue where loader could forget apps if 'don't ask again' was clicked in the watch chooser --- loader.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/loader.js b/loader.js index 61e2b1880..a28f7fe78 100644 --- a/loader.js +++ b/loader.js @@ -55,7 +55,7 @@ function onFoundDeviceInfo(deviceId, deviceVersion) { var originalAppJSON = undefined; function filterAppsForDevice(deviceId) { - if (originalAppJSON===undefined) + if (originalAppJSON===undefined && appJSON.length) originalAppJSON = appJSON; var device = DEVICEINFO.find(d=>d.id==deviceId); @@ -97,8 +97,7 @@ function setSavedDeviceId(deviceId) { // At boot, show a window to choose which type of device you have... window.addEventListener('load', (event) => { let deviceId = getSavedDeviceId() - if (deviceId !== undefined) - return filterAppsForDevice(deviceId); + if (deviceId !== undefined) return; // already chosen var html = `
${DEVICEINFO.map(d=>` @@ -168,6 +167,10 @@ window.addEventListener('load', (event) => { }); function onAppJSONLoaded() { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + filterAppsForDevice(deviceId); + return new Promise(resolve => { httpGet("screenshots.json").then(screenshotJSON=>{ var screenshots = []; From f4f40cee3a1c55eb50bd17319aec5f37f7ac134a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 10 Nov 2021 13:41:58 +0000 Subject: [PATCH 250/325] Fixes for Bangle.js 1 --- apps.json | 4 +-- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 6 ++--- apps/messages/ChangeLog | 1 + apps/messages/app.js | 57 ++++++++++++++++++++++++++++------------- modules/Layout.js | 4 +-- 6 files changed, 47 insertions(+), 26 deletions(-) diff --git a/apps.json b/apps.json index 6ef598255..a09997f7d 100644 --- a/apps.json +++ b/apps.json @@ -16,7 +16,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.35", + "version": "0.36", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -32,7 +32,7 @@ { "id": "messages", "name": "Messages", - "version": "0.02", + "version": "0.03", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 6dc2a3577..98f80efd9 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -39,3 +39,4 @@ Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) 0.35: Add Bangle.appRect polyfill Don't set beep vibration up on Bangle.js 2 (built in) +0.36: Add comments to .boot0 to make debugging a bit easier diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 8ad61f763..5cb6421a5 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -142,8 +142,8 @@ else if (mode=="updown") { } delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill - boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fgG).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), -k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; + boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), +k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; } delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill @@ -195,7 +195,7 @@ require('Storage').list(/\.boot\.js/).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! - boot += require('Storage').read(bootFile)+";\n"; + boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; }); // update ble boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index bbeb8b717..4f7df3859 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Add 'messages' library +0.03: Fixes for Bangle.js 1 diff --git a/apps/messages/app.js b/apps/messages/app.js index 749cf3c73..6c7cf5fc9 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -20,6 +20,26 @@ */ var Layout = require("Layout"); +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +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"; +// hack for 2v10 firmware's lack of ':size' font handling +try { + g.setFont("6x8:2"); +} catch (e) { + g._setFont = g.setFont; + g.setFont = function(f,s) { + if (f.includes(":")) { + f = f.split(":"); + return g._setFont(f[0],f[1]); + } + return g._setFont(f,s); + }; +} + var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; if (!Array.isArray(MESSAGES)) MESSAGES=[]; @@ -62,15 +82,15 @@ function showMapMessage(msg) { eta = m[2]; } else target=msg.body; layout = new Layout({ type:"v", c: [ - {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, - {type:"h", bgCol:"#0f0", fillx:1, c: [ + {type:"txt", font:fontMedium, label:target, bgCol:colBg, fillx:1, pad:2 }, + {type:"h", bgCol:colBg, fillx:1, c: [ {type:"txt", font:"6x8", label:"Towards" }, - {type:"txt", font:"6x15:2", label:street } + {type:"txt", font:fontLarge, label:street } ]}, {type:"h",fillx:1, filly:1, c: [ msg.img?{type:"img",src:atob(msg.img), scale:2}:{}, {type:"v", fillx:1, c: [ - {type:"txt", font:"6x15:2", label:distance||"" } + {type:"txt", font:fontLarge, label:distance||"" } ]}, ]}, {type:"txt", font:"6x8:2", label:eta } @@ -100,14 +120,14 @@ function showMusicMessage(msg) { checkMessages(); } layout = new Layout({ type:"v", c: [ - {type:"h", fillx:1, bgCol:"#0f0", c: [ + {type:"h", fillx:1, bgCol:colBg, c: [ { type:"btn", src:getBackImage, cb:back }, { type:"v", fillx:1, c: [ - { type:"txt", font:"6x15:2", label:msg.artist, pad:2 }, - { type:"txt", font:"6x15", label:msg.album, pad:2 } + { type:"txt", font:fontLarge, label:msg.artist, pad:2 }, + { type:"txt", font:fontMedium, label:msg.album, pad:2 } ]} ]}, - {type:"txt", font:"6x15:2", label:msg.track, fillx:1, filly:1, pad:2 }, + {type:"txt", font:fontLarge, label:msg.track, fillx:1, filly:1, pad:2 }, Bangle.musicControl?{type:"h",fillx:1, c: [ {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause @@ -125,23 +145,23 @@ function showMessage(msgid) { if (msg.src=="Maps") return showMapMessage(msg); if (msg.id=="music") return showMusicMessage(msg); // Normal text message display - var title=msg.title, titleFont = "6x15:2"; + var title=msg.title, titleFont = fontLarge; if (title) { var w = g.getWidth()-40; if (g.setFont(titleFont).stringWidth(title) > w) - titleFont = "6x15"; + titleFont = fontMedium; if (g.setFont(titleFont).stringWidth(title) > w) title = g.wrapString(title, w).join("\n"); } layout = new Layout({ type:"v", c: [ - {type:"h", fillx:1, bgCol:"#0f0", c: [ + {type:"h", fillx:1, bgCol:colBg, c: [ { type:"img", src:getMessageImage(msg), pad:2 }, { type:"v", fillx:1, c: [ - {type:"txt", font:"6x15", label:msg.src||"Message", bgCol:"#0f0", fillx:1, pad:2 }, - title?{type:"txt", font:titleFont, label:title, bgCol:"#0f0", fillx:1, pad:2 }:{}, + {type:"txt", font:fontMedium, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2 }, + title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{}, ]}, ]}, - {type:"txt", font:"6x15", label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, + {type:"txt", font:fontMedium, label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, {type:"h",fillx:1, c: [ {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ @@ -176,8 +196,8 @@ function checkMessages(forceShowMenu) { c : MESSAGES.length+1, draw : function(idx, r) {"ram" var msg = MESSAGES[idx-1]; - if (msg && msg.new) g.setBgColor("#4F4"); - else g.setBgColor((idx&1) ? "#CFC" : "#9F9"); + if (msg && msg.new) g.setBgColor(colBg); + else g.setBgColor((idx&1) ? colSBg1 : colSBg2); g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); if (idx==0) msg = {id:"back", title:"< Back"}; if (!msg) return; @@ -192,8 +212,8 @@ function checkMessages(forceShowMenu) { x += 50; } var m = msg.title+"\n"+msg.body; - if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2); - if (title) g.setFontAlign(-1,-1).setFont("12x20").drawString(title, x,r.y+2); + if (msg.src) g.setFontAlign(1,-1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+2); + if (title) g.setFontAlign(-1,-1).setFont(fontBig).drawString(title, x,r.y+2); if (body) { g.setFontAlign(-1,-1).setFont("6x8"); var l = g.wrapString(body, r.w-14); @@ -211,6 +231,7 @@ function checkMessages(forceShowMenu) { }); } +g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); checkMessages(); diff --git a/modules/Layout.js b/modules/Layout.js index c7d44ab9b..6dc4b6368 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -109,9 +109,7 @@ function Layout(layout, options) { delete this.buttons[s].selected; this.render(this.buttons[s]); } - s += dir; - if (s<0) s+=lh; - if (s>=l) s-=l; + s = (s+l+dir) % l; if (this.buttons[s]) { this.buttons[s].selected = 1; this.render(this.buttons[s]); From 81252d1dc6bf056b52d92d07f19a3088b29f2a46 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 10 Nov 2021 14:55:08 +0000 Subject: [PATCH 251/325] recorder 0.03: Fix theme and maps/graphing if no GPS --- apps.json | 2 +- apps/recorder/ChangeLog | 1 + apps/recorder/app.js | 64 ++++++++++++++++++++---------------- apps/recorder/interface.html | 8 ++--- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/apps.json b/apps.json index a09997f7d..89d06093a 100644 --- a/apps.json +++ b/apps.json @@ -657,7 +657,7 @@ "id": "recorder", "name": "Recorder (BETA)", "shortName": "Recorder", - "version": "0.02", + "version": "0.03", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index bf90d0384..2ea6e9fa8 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Use 'recorder.log..' rather than 'record.log..' Fix interface.html +0.03: Fix theme and maps/graphing if no GPS diff --git a/apps/recorder/app.js b/apps/recorder/app.js index ac3e391fc..d29959e25 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -140,10 +140,10 @@ function getTrackInfo(filename) { } // pushed this loop together to try and bump loading speed a little while(l!==undefined) { - ++nl;c=l.split(","); + ++nl;c=l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; n = +c[latIdx];if(n>maxLat)maxLat=n;if(nmaxLong)maxLong=n;if(n 100) { g.flip();i=0; } } - g.setColor(1,0,0); + g.setColor("#f00"); g.fillCircle(ox,oy,5); - if (info.qOSTM) g.setColor(0, 0, 0); - else g.setColor(1,1,1); + if (info.qOSTM) g.setColor("#000"); + else g.setColor(g.theme.fg); g.drawString(require("locale").distance(dist),120,220); g.setFont("6x8",2); g.setFontAlign(0,0,3); @@ -340,16 +340,22 @@ function plotGraph(info, style) { title = "Altitude (m)"; var altIdx = info.fields.indexOf("Altitude"); while(l!==undefined) { - ++nl;c=l.split(","); + ++nl;c=l.split(",");l = f.readLine(f); + if (c[altIdx]=="") continue; i = Math.round(80*(c[timeIdx] - strt)/dur); infn[i]+=+c[altIdx]; infc[i]++; - l = f.readLine(f); } } else if (style=="Speed") { title = "Speed (m/s)"; var latIdx = info.fields.indexOf("Latitude"); var lonIdx = info.fields.indexOf("Longitude"); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now iterate var p,lp = Bangle.project({lat:c[1],lon:c[2]}); var t,dx,dy,d,lt = c[timeIdx]; while(l!==undefined) { diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index eca13d263..ad0de4887 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -62,7 +62,7 @@ function saveGPX(track, title) { - + ${title} @@ -74,9 +74,7 @@ function saveGPX(track, title) { - ${pt.Heartrate ? `${pt.Heartrate}`:``} - ${""/*...*/} - ${""/* 65 */} + ${pt.Heartrate ? `${pt.Heartrate}`:``}${""/*...65*/} `; @@ -189,7 +187,7 @@ ${trackData.Latitude ? ` width="100%" height="250" frameborder="0" style="border:0" - src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${track.lat},${track.lon}&zoom=10" allowfullscreen> + src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${trackData.Latitude},${trackData.Longitude}&zoom=10" allowfullscreen>
` : `
No GPS info
`}

THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE + INSTRUCTIONS FOR BANGLE.JS 1 AND BANGLE.JS 2