From bff8fbacda2e8cfb289dd08f507822e1d3c3ba3c Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Fri, 9 Dec 2022 18:52:30 +0000 Subject: [PATCH 01/82] Simplest++ dropped the word clock from the app name --- apps/simplestpp/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/simplestpp/metadata.json b/apps/simplestpp/metadata.json index 10f756e32..cf7648d85 100644 --- a/apps/simplestpp/metadata.json +++ b/apps/simplestpp/metadata.json @@ -1,6 +1,6 @@ { "id": "simplestpp", - "name": "Simplest++ Clock", + "name": "Simplest++", "version": "0.01", "description": "The simplest working clock, with fast load and clock_info, acts as a tutorial piece", "readme": "README.md", From 30bf43ecc27482057ac03cab5bfe5e8c3982ac84 Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Sat, 10 Dec 2022 12:40:39 +0100 Subject: [PATCH 02/82] CalClock: 0.06 - Improved multi-line text --- apps/calclock/ChangeLog | 1 + apps/calclock/calclock.js | 7 ++++--- apps/calclock/metadata.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog index 5c1b7c4bc..90bcfb9d4 100644 --- a/apps/calclock/ChangeLog +++ b/apps/calclock/ChangeLog @@ -3,3 +3,4 @@ 0.03: Tell clock widgets to hide. 0.04: Improve current time readability in light theme. 0.05: Show calendar colors & improved all day events. +0.06: Improved multi-line locations & titles diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js index 5a13a202f..1f98502ef 100644 --- a/apps/calclock/calclock.js +++ b/apps/calclock/calclock.js @@ -54,13 +54,15 @@ function drawEventBody(event, y) { var yStart = y; if (lines.length > 2) { lines = lines.slice(0,2); - lines[1] = lines[1].slice(0,-3)+"..."; + lines[1] += "..."; } g.drawString(lines.join('\n'),10,y); y+=20 * lines.length; if(event.location) { g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y); - g.drawString(event.location,25,y); + var loclines = g.wrapString(event.location, g.getWidth()-30); + if(loclines.length>1) loclines[0] += "..."; + g.drawString(loclines[0],25,y); y+=20; } if (event.color) { @@ -131,4 +133,3 @@ var minuteInterval = setInterval(redraw, 60 * 1000); Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); - diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json index be0a1bdd8..bfd847595 100644 --- a/apps/calclock/metadata.json +++ b/apps/calclock/metadata.json @@ -2,7 +2,7 @@ "id": "calclock", "name": "Calendar Clock", "shortName": "CalClock", - "version": "0.05", + "version": "0.06", "description": "Show the current and upcoming events synchronized from Gadgetbridge", "icon": "calclock.png", "type": "clock", From 825a705339e390e8fb07567592caa40aa1ede0fb Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 11 Dec 2022 15:15:44 +0100 Subject: [PATCH 03/82] ClockFace: fix fast loading --- modules/ClockFace.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ClockFace.js b/modules/ClockFace.js index b1b007be9..bf64d418a 100644 --- a/modules/ClockFace.js +++ b/modules/ClockFace.js @@ -58,6 +58,7 @@ function ClockFace(options) { ClockFace.prototype.tick = function() { "ram" + if (this._removed) return; const time = new Date(); const now = { d: `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`, @@ -131,6 +132,7 @@ ClockFace.prototype.resume = function() { this.tick(); }; ClockFace.prototype.remove = function() { + this._removed = true; if (this._timeout) clearTimeout(this._timeout); Bangle.removeListener("lcdPower", this._onLcd); if (this._remove) this._remove.apply(this); From 3af685e0722fd8c7323f698bc1f73df562fcf8e9 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 17:25:38 +0000 Subject: [PATCH 04/82] Firmware ClockInfo --- apps/clkinfofw/ChangeLog | 1 + apps/clkinfofw/app.png | Bin 0 -> 1012 bytes apps/clkinfofw/clkinfo.js | 31 +++++++++++++++++++++++++++++++ apps/clkinfofw/metadata.json | 12 ++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 apps/clkinfofw/ChangeLog create mode 100644 apps/clkinfofw/app.png create mode 100644 apps/clkinfofw/clkinfo.js create mode 100644 apps/clkinfofw/metadata.json diff --git a/apps/clkinfofw/ChangeLog b/apps/clkinfofw/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/clkinfofw/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/clkinfofw/app.png b/apps/clkinfofw/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c6575b73b8824950ae1478b4fcdfe3ee180ed586 GIT binary patch literal 1012 zcmVQEHN5433o2V)~G3@io)j7a1p8f7BJ)!o6-#o%O& z{t0aU1q`NPa?ltDe}E`vogHz#Qomho=-0K zT<*EJ48s6B>AKFcY!|21Y5{=T?f(5CpUG|e#P;^K$KyFTIH1nT%8JkDt5hn~nVg)Q znwp}`N~JP1G_<<9N}c`veUHbpv$OLLfK*l0bzN0ev-A4;S}K*y&dtpYZI()cWy;v;%10aWG|D^^30YsD}i8`TB2ob$rFLlPp$IU~$UN0hsLLur%l7xtXK)^Pf ztK*_>G#b~}*VSs(cKn}dU|=8`jrR5Rb?7tNbaZs&^ZC9Bg-R5~d_LbE-3nB%*Bzjt zOpQjv21rp9XK08NMX>-_v+V8d%}Jp4a6Os!ySuxZrU8K8?;jo>c9>vubMxWh0RSSA z$lTl<=(^5v9O*;ELZRR!O`%XgMDm&E`DU}pG7Ph~w|8-I0RVo#-$@#x<>h5EL-Bar z?RHzU%=sCz$+90;&jGOju(q~F)=)Sco}ZuZ^7Q)p`pnGCmjdMT`E)wn<$%L~m}CJ2JA>ny`Cnx@^~-vfZx>wSKH{?Y{e zy<}Og)oK6`3-mX^iNV1^06@h52ejW%&jGOjaCmrld3gyxT?WPD@!8qg9x8)4j{7*EZEbDQLeWVY zB3cHGjEpc0^FPb%WCiN)?{_kMhj5k|tJmxFeEr>$B+355$}o&vF6RJ~BuU4|$L-NO zUwXd3zh7Nlee0#?*w`4)^Bwxy87<0WGKiSXW~q}(r4Uh7Rq7-X334JPQdJcZQ>hen zve_&mW-=MuaQ64q<#Jh36!RYmcXxM+qL4!j5!vXeYPFi8DCW!4N~NMGO1WIN4cBvN zZ$CrPXq4yqXf#TlSS)saejW@4&5pTr2?m3LAjD!Z>Wq$#avW!V$!F@ks4Z%>TIRo= i+8y!|Gn4I*&-4ql4;+?rBV)q=0000 { + let d = new Date(); + let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); + //g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); + g.drawImage(atob("GBjD/wAA//8c56pSLGOylJrWwxgkknFY7HI4kkkkk5mw2mUvEkkklarVWq5XIkkksAAAAAAABkklAAAAAAAAAIk7AAAAAAAAAbHOgAAAAAAAAV4lAAAAAAAAAIlXAAdtttvgAarSgAMkkklgAWYlAAMkkklgAInXAAMkkklgAa7XAAMkkklgAa4lAAMkkklgAInSgAMkkklgAWZXAAdtttvgAaolAAAAAAAAAInOgAAAAAAAAV47AAAAAAAAAbElAAAAAAAAAIkksAAAAAAABkkklarVWq5XIkkkk5mw2mUvEkkkknFY7HI4kkk="),1,0); + return { + text : process.env.VERSION, + img : g.asImage("string") + }; + }, + show : function() { + this.interval = setTimeout(()=>{ + this.emit("redraw"); + this.interval = setInterval(()=>{ + this.emit("redraw"); + }, 86400000); + }, 86400000 - (Date.now() % 86400000)); + }, + hide : function() { + clearInterval(this.interval); + this.interval = undefined; + } + } + ] + }; +}) diff --git a/apps/clkinfofw/metadata.json b/apps/clkinfofw/metadata.json new file mode 100644 index 000000000..8b722e52f --- /dev/null +++ b/apps/clkinfofw/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfofw", + "name": "Firmware Clockinfo", + "version":"0.01", + "description": "For clocks that display 'clockinfo', this displays the firmware version string", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,firmware", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"clkinfofw.clkinfo.js","url":"clkinfo.js"} + ] +} From 3b0f525bd2d7e6d290bf0a657a954322cd731862 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 11 Dec 2022 18:34:07 +0100 Subject: [PATCH 05/82] messagegui: write "remove" messages to flash Whoops: we didn't handle these at all, but are actually responsible for saving them, even if the UI won't be launched. Fixes #2380 --- apps/messagegui/ChangeLog | 1 + apps/messagegui/lib.js | 17 +++++++++++++---- apps/messagegui/metadata.json | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 3f5ff70fd..36ec8191f 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -82,3 +82,4 @@ 0.58: Fast load messages without writing to flash Don't write messages to flash until the app closes 0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373) +0.60: Fix saving of removal messages if UI not open diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js index f9919c01c..57dc3c1e2 100644 --- a/apps/messagegui/lib.js +++ b/apps/messagegui/lib.js @@ -10,7 +10,15 @@ exports.listener = function(type, msg) { clearTimeout(exports.messageTimeout); delete exports.messageTimeout; } - if (msg.t==="remove") return; + if (msg.t==="remove") { + // we won't open the UI for removed messages, so make sure to delete it from flash + if (Bangle.MESSAGES) { + // we were waiting for exports.messageTimeout + require("messages").apply(msg, Bangle.MESSAGES); + if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES; + } + return require("messages").save(msg); // always write removal to flash + } const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; let loadMessages = (Bangle.CLOCK || event.important); @@ -26,12 +34,12 @@ exports.listener = function(type, msg) { require("messages").save(msg); } else { if (!Bangle.MESSAGES) Bangle.MESSAGES = []; - Bangle.MESSAGES.push(msg); + require("messages").apply(msg, Bangle.MESSAGES); + if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES; } const saveToFlash = () => { // save messages from RAM to flash after all, if we decide not to launch app - if (!Bangle.MESSAGES) return; - Bangle.MESSAGES.forEach(m => require("messages").save(m)); + if (Bangle.MESSAGES) Bangle.MESSAGES.forEach(m => require("messages").save(m)); delete Bangle.MESSAGES; } msg.handled = true; @@ -50,6 +58,7 @@ exports.listener = function(type, msg) { if (exports.messageTimeout) clearTimeout(exports.messageTimeout); exports.messageTimeout = setTimeout(function() { delete exports.messageTimeout; + if (!Bangle.MESSAGES) return; // message was removed during the delay if (type!=="music") { if (!loadMessages) { // not opening the app, just buzz diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 80b362551..5b1cb60c6 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.59", + "version": "0.60", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From 8a67b1a63d0841b7785625c1b9a681b45eb8c175 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 17:56:18 +0000 Subject: [PATCH 06/82] changed icon to be transparent --- apps/clkinfofw/clkinfo.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/clkinfofw/clkinfo.js b/apps/clkinfofw/clkinfo.js index 44b94afa8..a7886e2bc 100644 --- a/apps/clkinfofw/clkinfo.js +++ b/apps/clkinfofw/clkinfo.js @@ -7,7 +7,8 @@ let d = new Date(); let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); //g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); - g.drawImage(atob("GBjD/wAA//8c56pSLGOylJrWwxgkknFY7HI4kkkkk5mw2mUvEkkklarVWq5XIkkksAAAAAAABkklAAAAAAAAAIk7AAAAAAAAAbHOgAAAAAAAAV4lAAAAAAAAAIlXAAdtttvgAarSgAMkkklgAWYlAAMkkklgAInXAAMkkklgAa7XAAMkkklgAa4lAAMkkklgAInSgAMkkklgAWZXAAdtttvgAaolAAAAAAAAAInOgAAAAAAAAV47AAAAAAAAAbElAAAAAAAAAIkksAAAAAAABkkklarVWq5XIkkkk5mw2mUvEkkkknFY7HI4kkk="),1,0); + //g.drawImage(atob("GBjD/wAA//8c56pSLGOylJrWwxgkknFY7HI4kkkkk5mw2mUvEkkklarVWq5XIkkksAAAAAAABkklAAAAAAAAAIk7AAAAAAAAAbHOgAAAAAAAAV4lAAAAAAAAAIlXAAdtttvgAarSgAMkkklgAWYlAAMkkklgAInXAAMkkklgAa7XAAMkkklgAa4lAAMkkklgAInSgAMkkklgAWZXAAdtttvgAaolAAAAAAAAAInOgAAAAAAAAV47AAAAAAAAAbElAAAAAAAAAIkksAAAAAAABkkklarVWq5XIkkkk5mw2mUvEkkkknFY7HI4kkk="),1,0); + g.drawImage(atob("GBjD/wAA//8QhEEIvvfPewhC970kk9Ho/lcrkkkkkpxOJRqNEkkkxZ3O7V6vKEkmMAAAAAAABwklAAAAAAAAAInrAAAAAAAAAZdNgAAAAAAAANoygAAAAAAAAWHrAABtttsAAZdNgAOSSSRgANoygAOEkkxgAWHrAAOEkkxgAZfrAAOEkkxgAZcygAOEkkxgAWFNgAOSSSRgANrrAABtttsAAZcygAAAAAAAAWFNgAAAAAAAANrrAAAAAAAAAZclAAAAAAAAAIkmMAAAAAAABwkkxZ3O7V6vKEkkkpxOJRqNEkkkk9Ho/lcrkkk="),1,0); return { text : process.env.VERSION, img : g.asImage("string") From 776ef09608f69e02501b523376a517127c41b67c Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 18:23:05 +0000 Subject: [PATCH 07/82] added screenshot --- apps/clkinfofw/clkinfo.js | 4 +--- apps/clkinfofw/metadata.json | 1 + apps/clkinfofw/screenshot.png | Bin 0 -> 2772 bytes 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 apps/clkinfofw/screenshot.png diff --git a/apps/clkinfofw/clkinfo.js b/apps/clkinfofw/clkinfo.js index a7886e2bc..4ca05e8d1 100644 --- a/apps/clkinfofw/clkinfo.js +++ b/apps/clkinfofw/clkinfo.js @@ -6,9 +6,7 @@ get : () => { let d = new Date(); let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); - //g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); - //g.drawImage(atob("GBjD/wAA//8c56pSLGOylJrWwxgkknFY7HI4kkkkk5mw2mUvEkkklarVWq5XIkkksAAAAAAABkklAAAAAAAAAIk7AAAAAAAAAbHOgAAAAAAAAV4lAAAAAAAAAIlXAAdtttvgAarSgAMkkklgAWYlAAMkkklgAInXAAMkkklgAa7XAAMkkklgAa4lAAMkkklgAInSgAMkkklgAWZXAAdtttvgAaolAAAAAAAAAInOgAAAAAAAAV47AAAAAAAAAbElAAAAAAAAAIkksAAAAAAABkkklarVWq5XIkkkk5mw2mUvEkkkknFY7HI4kkk="),1,0); - g.drawImage(atob("GBjD/wAA//8QhEEIvvfPewhC970kk9Ho/lcrkkkkkpxOJRqNEkkkxZ3O7V6vKEkmMAAAAAAABwklAAAAAAAAAInrAAAAAAAAAZdNgAAAAAAAANoygAAAAAAAAWHrAABtttsAAZdNgAOSSSRgANoygAOEkkxgAWHrAAOEkkxgAZfrAAOEkkxgAZcygAOEkkxgAWFNgAOSSSRgANrrAABtttsAAZcygAAAAAAAAWFNgAAAAAAAANrrAAAAAAAAAZclAAAAAAAAAIkmMAAAAAAABwkkxZ3O7V6vKEkkkpxOJRqNEkkkk9Ho/lcrkkk="),1,0); + g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0); return { text : process.env.VERSION, img : g.asImage("string") diff --git a/apps/clkinfofw/metadata.json b/apps/clkinfofw/metadata.json index 8b722e52f..924297ca3 100644 --- a/apps/clkinfofw/metadata.json +++ b/apps/clkinfofw/metadata.json @@ -4,6 +4,7 @@ "description": "For clocks that display 'clockinfo', this displays the firmware version string", "icon": "app.png", "type": "clkinfo", + "screenshots": [{"url":"screenshot.png"}], "tags": "clkinfo,firmware", "supports" : ["BANGLEJS2"], "storage": [ diff --git a/apps/clkinfofw/screenshot.png b/apps/clkinfofw/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..da185bd2e2837ea214c62f22cc25a2c7616c18e5 GIT binary patch literal 2772 zcmeH}c~sJg7RP^}xPW_UZn#ldiY3yxOJ)$ZsWl@Sq@gLAdzxFMp|7S)Ma#xA7o5Se z6a_WY2^X3Sb#kN4)C9)L9R>Ha=sD-S_t*RPz5m`H_kPbk_xsm9=iblF^6|o}Lk*z- z0H`1K#Gc$#&flS;w3*pWp8_`plyDO70o3*xPXPdwei(b;r<6cR>0j}qR%%y@S1(Tr z&p(*+IpfatAny)av_|W3SeTb~m2O|wmuCj&&#d26z`@sacsR7>kVr#?oZ&MJ0-bhF z)A_!icKLf*Q^%PzgZc9Q@Y$%GlV-{U<27Y6Oi2CJB>$cNftFi4bKy<{kQBuc^(i&+ zM5A8R0i+fat^#_Wc|j?C>F0eSu7ojIlhWp09k}`%|CX@sbrh63f_22D!F>#^%YHn!N=n2x~Flz= zScSA9`FCc(zWxk`slC~59~|!VdW(K63R`0L-A2(aEBF(y2LwW5J^_Exa}t{>L<=CxBJC3PU~?j@e~^!vpCvvAd}9h>*~7(Lxbu5AkCXElf+2 z`9fh1t;&6j>ZSyXq(y?{7jpy6x@1@dJl{Xk?!o*(Co7Lnr1o+00t}l#QL1gXwnQWA zcS2vlx7FMX5Z%D9lmuxOda?O704X?`m%$oBcf0g~XF3wG8+4CnbLxU3Fc}-pO=VA3 zX1sga<*c2+$y(-^xhqapJtEnz$D*?nJ*uR z?CP^r;UGL~T7}&p49lSW11(LK1 zL;~I<07`x?Q@QRY?z`3EYziJyrTG!BQ8mHM08b@OfZ~GawB!rNrdpo`Fgoh^^Y%$e zVGTrvEby$~;Bt;glJMqh6|e7(qvqDZyH;9w(7VOm@i8E^G6l4yDnWD_H0Ifrf5(2l z*M?@4%v`stQ18}|Y0#M8d^KCNh0|IL#gxw!0z`grm_AM3JYCPEl+)$XifXAd?YTuNMyj!$C5V`)B(pS5}5=9leOQhGf{cD}v($e~_p zf5WR8^loil4hcKE=WI<=h^C`tb+Dlwb1w?FJA#Hg+cz1ced1pX+Dm)keok32Zffap3!tZPAFjo_|JqUp_E)=|d_CA_g0Ng-(Fx$@vH$6k8U*|y3Dd- zdl-Lv>#b%e87@o1(qj`5MB!5OCs`zbK75&PLCG7(w}nN8%7@y@A{%CAGq-`h92Zf_ zOd$HG3=kDUdvg4owOk~$S|j!PwckYPq$kZc7=gU|trm$w z8f?9XFlntGX31145bT=YTieV((m|J^F-IYoqU&MtZrv+6sXNYN>Q zcd%Q_PJGy6e4&kGZ4@ifPIX8Timj#80dWqLSPlL<$k1Kk}& zYC|nTcWuaJzX;B1B^kO7=7C%z%mvGZ(9$8;zu8s9q9;RL#cN923%k z4^-p&*bOddg;4$kYVLr5+78Ewk89M5 z0+V{X{JkNEVjl|6jusRTxi*y4ZG=5Gufto#QD_21|8FR$PJQ4{IS}_Z&RwpBdh^Q& zAS~M4|06Z+x3?~sE&pI;b+FppaqMU7V#tcqoCsN=~ zSDHq75f(19z)g9>CP3No52iWgKiAFljfaxm2PHMva^PvRBbm89ugA@fWLvUl?;>;^ z55lx>V!chXYO^0h^+K3!`fmrSSC}gks)j^QfA^X-CV9^J9H z`sCKqn4&b*r@nR#%5x}pxGp*WeldJH^|+LRn}P?h!I#>@mjV}9&~Nkr;t$0NY*0zB t%G9GN8cF%`p&jz*(B_*89L9NJYdy%B{{%!t5fuOc literal 0 HcmV?d00001 From 8465d61d3115d412bd80b9728d7b87b8f3138409 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 18:52:59 +0000 Subject: [PATCH 08/82] made icon transparent, fixed screenshot filename --- apps/lato/icon.js | 2 +- apps/lato/screenshot1..png | Bin 2666 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 apps/lato/screenshot1..png diff --git a/apps/lato/icon.js b/apps/lato/icon.js index 345458d53..746f010dc 100644 --- a/apps/lato/icon.js +++ b/apps/lato/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwkA/4A/AH4AUiIAKCxXzC5c/C5PyC5cvC8PxC5cfLxNEABhgI+gXNp4X3//+9wAK96PJC6/zC5bvKC6//C5YWKC4nUoMUpoXS8lDn/zmlOC6NCA4ckC6Hkl4HD+QwCC5c+LoIsCoSKBMIPjC5tD//0olEp//mgXNmMRiYuBC4JjBBAYAK+MRj//CwIABBAgXkI5AXOiRyBC4J8BkIXN+dEoKnFiNEAYIXNa4sUC59EJAIACkIHBC5iMCoMTn/zmIuBSQIXODAMRAAKqDABikCAAqqBC8i8CAArCBC/n0C49PC5oA/AH4AIA==")) +require("heatshrink").decompress(atob("mEw4UA///1NygH+zn/Jf4AJgdVAAnABZ8BBYtABbc1BYtcBYcVBYtUBbcC1QAEwALPgYLFQYoLWgAHBytWAYK0F1Wpv/9tQLH0v//9aBY+XBYPWBY3qz/1r/21YLGv/Vq/9BY3Vv6NB/tXBaMVBYamEBZ1fHYP1BY01r5TB+ruEBYVXNYPVBY9VBYNVBY0FqoiBqtQBY4ACBb0NBYdwBbsBBYdABYoA/AAg=")) diff --git a/apps/lato/screenshot1..png b/apps/lato/screenshot1..png deleted file mode 100644 index 14c8d6d048afce0b1540cc32e155886a022fd675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2666 zcmc&$=~vPT7yd!uLhhy&naiN0sih*8iHwqhrnzsSiA@%YqBd6UiPkjZB<7a5Vs5=P zhT@)y$=hOk|ad@)u+JFO~ zGM5ZRCmtFft~pHF%{_+&t1Ans_e$*~@Vkd*wRG z2>HHQGRw!AjTlr;DTH|(5$bXc=iU;w7YBd+fs**#)`%r9@bVf&f&GE!Cx6waC02Q2 zi+75$DkkN+Uck$@M;<_i;(At*W=GhDG-7y<@zwUku2}&@ecZe!Fb;Y2u>IRo?TLo6 z=avcKB=lS-+&qCC@nsZqAuAUr-Ve$FH@H=`25N=)4qi|axiLoy=sH}S(rT8;JYN|7d2cI8vzkz-FOsD#)*|A0Z z;An$b%^9+`L@hA&46*2etwyHTu|#O#XVRZUva_fr)G~>1WPUDoG>18Dh~`e8wOX$= zi@Q9a4wZ@+L!OZqzGFR~qvHgzj|bJCACeSg-CW*VK~$}NieIn}y&^xBKXdDZs*7H} zDEZj%Aa=hXDMOlg3Ln+w_`Lp2C2e<59?0$UQuN3Xx1>DllcjKwy6c?~cU^9xTXyML z$Jz=2ggo>}MU(qz^!1dq(Lp;F0CF0~BAsb(tSGxJd|5S)0Xs^#>08mErPBM3 zPtU^G9-M47w*V@AU)0NL?=*zD&wzXTy)P@N^dQaYSqUSzC;!mR5-Apwg9vZayZ6vB zukay^t5Ki>hQu>+hRgoHw;vYAv6?MGcyt|u^9hcOw4zy9T$wo*ErK?wJR1zsyFG2e z?YNH{P~zisLadqJ$ng#lCC-Q6$b<*&BY5lpn36>&%OOi>AOFRA4hqvfvDdd+u@0*v^m62ri-NY!*^$$1@_puyu&4V@$7^Mc|I%Z3$hu#|N4)xK zWJMVR2S6}^%bFiGZ&23nUytf7_akm=x2U|9z9T!HyncX|F`oMPAXIT_zD5FMUaHzP zzqw+{**3oUJI%S6X`zsE(UWBJg7vE{GRDS|*E#_Un9?l)X_tP=-@=pbr>}638iM(j zamawvtq_Vp-j=}8sSw9Gpe8<(DlfF_Ei1re7F?%_ zW>R}HfgJ~YHlL_~UUfy|!&JcrTMuqcPde=>{%w&$v#iIc^E-ksfJ)j45N>y@5?`=^ ze)78hBbR)kMJeF8fLDX7* zQi87{S0KKQ&d@^S2^q2ObU*6xT|&KeQ876 znXLFNOsp*6Z<)|fkh0Mg%eli28`hS;c^BRhj5b&_F?N$|An~KXP^xVrsHMw zex?h&cq6%)t>p|+v*-Knnr|2*{?L9 zbO9fk9;W2tLb*cz@Y*uEqAR_NAWrdX>xX=6UCS<|d`tD-+RqLlo{E1pR<(8&>IUeQ);Nvt-lj6Ah+eUk;olM~?7 z8INA?ix$1d?>+8+3nyeN;!QFYJzNaC4p_mopf; zmwgpG0IO`CX4f7b&u#hdX5BDq0;k#gz4>zeCt^pipqgt8DElYodf8}l^*zXrC}<;e zE#_7Gd`Nreg4%A;g}%$U8JI#qr2V{+8wjlS$n0X?t^&W;sd!HA-RBXow{^0q!UZJ%4PfNx A&j0`b From ad9ea67cba4aef8d5f234ee6537d4744dd50cdd5 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 19:11:36 +0000 Subject: [PATCH 09/82] fixed whitespace --- apps/clkinfofw/clkinfo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/clkinfofw/clkinfo.js b/apps/clkinfofw/clkinfo.js index 4ca05e8d1..9815ca87f 100644 --- a/apps/clkinfofw/clkinfo.js +++ b/apps/clkinfofw/clkinfo.js @@ -6,8 +6,8 @@ get : () => { let d = new Date(); let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); - g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0); - return { + g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0); + return { text : process.env.VERSION, img : g.asImage("string") }; From 0a0c6f8d23b21ab41ce7c9bc76e000fdd9f1cdf4 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sun, 11 Dec 2022 19:13:36 +0000 Subject: [PATCH 10/82] Lato, fixed screenshot filename --- apps/lato/screenshot1.png | Bin 0 -> 2666 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/lato/screenshot1.png diff --git a/apps/lato/screenshot1.png b/apps/lato/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..14c8d6d048afce0b1540cc32e155886a022fd675 GIT binary patch literal 2666 zcmc&$=~vPT7yd!uLhhy&naiN0sih*8iHwqhrnzsSiA@%YqBd6UiPkjZB<7a5Vs5=P zhT@)y$=hOk|ad@)u+JFO~ zGM5ZRCmtFft~pHF%{_+&t1Ans_e$*~@Vkd*wRG z2>HHQGRw!AjTlr;DTH|(5$bXc=iU;w7YBd+fs**#)`%r9@bVf&f&GE!Cx6waC02Q2 zi+75$DkkN+Uck$@M;<_i;(At*W=GhDG-7y<@zwUku2}&@ecZe!Fb;Y2u>IRo?TLo6 z=avcKB=lS-+&qCC@nsZqAuAUr-Ve$FH@H=`25N=)4qi|axiLoy=sH}S(rT8;JYN|7d2cI8vzkz-FOsD#)*|A0Z z;An$b%^9+`L@hA&46*2etwyHTu|#O#XVRZUva_fr)G~>1WPUDoG>18Dh~`e8wOX$= zi@Q9a4wZ@+L!OZqzGFR~qvHgzj|bJCACeSg-CW*VK~$}NieIn}y&^xBKXdDZs*7H} zDEZj%Aa=hXDMOlg3Ln+w_`Lp2C2e<59?0$UQuN3Xx1>DllcjKwy6c?~cU^9xTXyML z$Jz=2ggo>}MU(qz^!1dq(Lp;F0CF0~BAsb(tSGxJd|5S)0Xs^#>08mErPBM3 zPtU^G9-M47w*V@AU)0NL?=*zD&wzXTy)P@N^dQaYSqUSzC;!mR5-Apwg9vZayZ6vB zukay^t5Ki>hQu>+hRgoHw;vYAv6?MGcyt|u^9hcOw4zy9T$wo*ErK?wJR1zsyFG2e z?YNH{P~zisLadqJ$ng#lCC-Q6$b<*&BY5lpn36>&%OOi>AOFRA4hqvfvDdd+u@0*v^m62ri-NY!*^$$1@_puyu&4V@$7^Mc|I%Z3$hu#|N4)xK zWJMVR2S6}^%bFiGZ&23nUytf7_akm=x2U|9z9T!HyncX|F`oMPAXIT_zD5FMUaHzP zzqw+{**3oUJI%S6X`zsE(UWBJg7vE{GRDS|*E#_Un9?l)X_tP=-@=pbr>}638iM(j zamawvtq_Vp-j=}8sSw9Gpe8<(DlfF_Ei1re7F?%_ zW>R}HfgJ~YHlL_~UUfy|!&JcrTMuqcPde=>{%w&$v#iIc^E-ksfJ)j45N>y@5?`=^ ze)78hBbR)kMJeF8fLDX7* zQi87{S0KKQ&d@^S2^q2ObU*6xT|&KeQ876 znXLFNOsp*6Z<)|fkh0Mg%eli28`hS;c^BRhj5b&_F?N$|An~KXP^xVrsHMw zex?h&cq6%)t>p|+v*-Knnr|2*{?L9 zbO9fk9;W2tLb*cz@Y*uEqAR_NAWrdX>xX=6UCS<|d`tD-+RqLlo{E1pR<(8&>IUeQ);Nvt-lj6Ah+eUk;olM~?7 z8INA?ix$1d?>+8+3nyeN;!QFYJzNaC4p_mopf; zmwgpG0IO`CX4f7b&u#hdX5BDq0;k#gz4>zeCt^pipqgt8DElYodf8}l^*zXrC}<;e zE#_7Gd`Nreg4%A;g}%$U8JI#qr2V{+8wjlS$n0X?t^&W;sd!HA-RBXow{^0q!UZJ%4PfNx A&j0`b literal 0 HcmV?d00001 From 90b68051c57fafe6f245f133d5ff2956bf0a6ad4 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 11 Dec 2022 22:09:16 +0100 Subject: [PATCH 11/82] astrocalc - Compatibility with Bangle.js 2 - Get location from My Location --- .gitignore | 1 + apps/astrocalc/ChangeLog | 1 + apps/astrocalc/astrocalc-app.js | 107 ++++++-------------------------- apps/astrocalc/metadata.json | 7 ++- modules/suncalc.js | 2 +- 5 files changed, 27 insertions(+), 91 deletions(-) diff --git a/.gitignore b/.gitignore index f4588ac6f..7687a770a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ _site .owncloudsync.log Desktop.ini .sync_*.db* +*.swp diff --git a/apps/astrocalc/ChangeLog b/apps/astrocalc/ChangeLog index 746ab2162..11b2d7177 100644 --- a/apps/astrocalc/ChangeLog +++ b/apps/astrocalc/ChangeLog @@ -1,3 +1,4 @@ 0.01: Create astrocalc app 0.02: Store last GPS lock, can be used instead of waiting for new GPS on start 0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps +0.04: Compatibility with Bangle.js 2, get location from My Location diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 46fb855ec..3c879779d 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -11,8 +11,7 @@ const SunCalc = require("suncalc"); // from modules folder const storage = require("Storage"); -const LAST_GPS_FILE = "astrocalc.gps.json"; -let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null); +const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2 function drawMoon(phase, x, y) { const moonImgFiles = [ @@ -73,7 +72,7 @@ function drawTitle(key) { */ function drawPoint(angle, radius, color) { const pRad = Math.PI / 180; - const faceWidth = 80; // watch face radius + const faceWidth = g.getWidth()/3; // watch face radius const centerPx = g.getWidth() / 2; const a = angle * pRad; @@ -141,6 +140,7 @@ function drawData(title, obj, startX, startY) { function drawMoonPositionPage(gps, title) { const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon); + const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0}; const pageData = { Azimuth: pos.azimuth.toFixed(2), @@ -150,13 +150,13 @@ function drawMoonPositionPage(gps, title) { }; const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI); - drawData(title, pageData, null, 80); + drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20); drawPoints(); - drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1}); + drawPoint(azimuthDegrees, 8, moonColor); let m = setWatch(() => { let m = moonIndexPageMenu(gps); - }, BTN3, {repeat: false, edge: "falling"}); + }, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"}); } function drawMoonIlluminationPage(gps, title) { @@ -166,43 +166,45 @@ function drawMoonIlluminationPage(gps, title) { ]; const phase = SunCalc.getMoonIllumination(new Date()); + const phaseIdx = Math.round(phase.phase*8), const pageData = { - Phase: phaseNames[phase.phase], + Phase: phaseNames[phaseIdx], }; drawData(title, pageData, null, 35); - drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2); + drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2); let m = setWatch(() => { let m = moonIndexPageMenu(gps); - }, BTN3, {repease: false, edge: "falling"}); + }, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"}); } function drawMoonTimesPage(gps, title) { const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon); + const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0}; const pageData = { Rise: dateToTimeString(times.rise), Set: dateToTimeString(times.set), }; - drawData(title, pageData, null, 105); + drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5); drawPoints(); // Draw the moon rise position const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon); const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI); - drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1}); + drawPoint(riseAzimuthDegrees, 8, moonColor); // Draw the moon set position const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon); const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI); - drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1}); + drawPoint(setAzimuthDegrees, 8, moonColor); let m = setWatch(() => { let m = moonIndexPageMenu(gps); - }, BTN3, {repease: false, edge: "falling"}); + }, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"}); } function drawSunShowPage(gps, key, date) { @@ -224,7 +226,7 @@ function drawSunShowPage(gps, key, date) { Degrees: azimuthDegrees }; - drawData(key, pageData, null, 85); + drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5); drawPoints(); @@ -233,7 +235,7 @@ function drawSunShowPage(gps, key, date) { m = setWatch(() => { m = sunIndexPageMenu(gps); - }, BTN3, {repeat: false, edge: "falling"}); + }, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"}); return null; } @@ -300,7 +302,7 @@ function indexPageMenu(gps) { "Moon": () => { m = moonIndexPageMenu(gps); }, - "< Exit": () => { load(); } + "< Back": () => { load(); } }; return E.showMenu(menu); @@ -310,78 +312,9 @@ function getCenterStringX(str) { return (g.getWidth() - g.stringWidth(str)) / 2; } -/** - * GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page - */ -function drawGPSWaitPage() { - const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA==")); - const str1 = "Astrocalc v0.02"; - const str2 = "Locating GPS"; - const str3 = "Please wait..."; - - g.clear(); - g.drawImage(img, 100, 50); - g.setFont("6x8", 1); - g.drawString(str1, getCenterStringX(str1), 105); - g.drawString(str2, getCenterStringX(str2), 140); - g.drawString(str3, getCenterStringX(str3), 155); - - if (lastGPS) { - lastGPS = JSON.parse(lastGPS); - lastGPS.time = new Date(); - - const str4 = "Press Button 3 to use last GPS"; - g.setColor("#d32e29"); - g.fillRect(0, 190, g.getWidth(), 215); - g.setColor("#ffffff"); - g.drawString(str4, getCenterStringX(str4), 200); - - setWatch(() => { - clearWatch(); - Bangle.setGPSPower(0); - m = indexPageMenu(lastGPS); - }, BTN3, {repeat: false}); - } - - g.flip(); - - const DEBUG = false; - if (DEBUG) { - clearWatch(); - - const gps = { - "lat": 56.45783133333, - "lon": -3.02188583333, - "alt": 75.3, - "speed": 0.070376, - "course": NaN, - "time":new Date(), - "satellites": 4, - "fix": 1 - }; - - m = indexPageMenu(gps); - - return; - } - - Bangle.on('GPS', (gps) => { - if (gps.fix === 0) return; - clearWatch(); - - if (isNaN(gps.course)) gps.course = 0; - require("Storage").writeJSON(LAST_GPS_FILE, JSON.stringify(gps)); - Bangle.setGPSPower(0); - Bangle.buzz(); - Bangle.setLCDPower(true); - - m = indexPageMenu(gps); - }); -} - function init() { - Bangle.setGPSPower(1); - drawGPSWaitPage(); + let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"}; + indexPageMenu(location); } let m; diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index d77474700..1f238be12 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -1,12 +1,13 @@ { "id": "astrocalc", "name": "Astrocalc", - "version": "0.03", + "version": "0.04", "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", - "supports": ["BANGLEJS"], + "tags": "app,sun,moon,cycles,tool", + "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, + "dependencies": {"mylocation":"app"}, "storage": [ {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, {"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true}, diff --git a/modules/suncalc.js b/modules/suncalc.js index 0c22c6cb2..fe17148e1 100644 --- a/modules/suncalc.js +++ b/modules/suncalc.js @@ -279,7 +279,7 @@ function hoursLater(date, h) { // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { - var t = new Date(date); + var t = typeof(date) === "object" ? date : new Date(date); if (inUTC) t.setUTCHours(0, 0, 0, 0); else t.setHours(0, 0, 0, 0); From 340fc0667831bf058d2023615f8d39ba0c23d8c1 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 11 Dec 2022 22:16:39 +0100 Subject: [PATCH 12/82] astrocalc fix typo --- apps/astrocalc/astrocalc-app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 3c879779d..7b41ad136 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -166,7 +166,7 @@ function drawMoonIlluminationPage(gps, title) { ]; const phase = SunCalc.getMoonIllumination(new Date()); - const phaseIdx = Math.round(phase.phase*8), + const phaseIdx = Math.round(phase.phase*8); const pageData = { Phase: phaseNames[phaseIdx], }; From f6ee8333c46ec812fb9291f8f577bb152ed73ee4 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 11 Dec 2022 22:36:31 +0100 Subject: [PATCH 13/82] astrocalc translations --- apps/astrocalc/astrocalc-app.js | 16 ++++++++-------- lang/de_DE.json | 10 +++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 7b41ad136..6629842cf 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -161,8 +161,8 @@ function drawMoonPositionPage(gps, title) { function drawMoonIlluminationPage(gps, title) { const phaseNames = [ - "New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", - "Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent", + /*LANG*/"New Moon", /*LANG*/"Waxing Crescent", /*LANG*/"First Quarter", /*LANG*/"Waxing Gibbous", + /*LANG*/"Full Moon", /*LANG*/"Waning Gibbous", /*LANG*/"Last Quater", /*LANG*/"Waning Crescent", ]; const phase = SunCalc.getMoonIllumination(new Date()); @@ -275,15 +275,15 @@ function moonIndexPageMenu(gps) { }, "Times": () => { m = E.showMenu(); - drawMoonTimesPage(gps, "Times"); + drawMoonTimesPage(gps, /*LANG*/"Times"); }, "Position": () => { m = E.showMenu(); - drawMoonPositionPage(gps, "Position"); + drawMoonPositionPage(gps, /*LANG*/"Position"); }, "Illumination": () => { m = E.showMenu(); - drawMoonIlluminationPage(gps, "Illumination"); + drawMoonIlluminationPage(gps, /*LANG*/"Illumination"); }, "< Back": () => m = indexPageMenu(gps), }; @@ -294,12 +294,12 @@ function moonIndexPageMenu(gps) { function indexPageMenu(gps) { const menu = { "": { - "title": "Select", + "title": /*LANG*/"Select", }, - "Sun": () => { + /*LANG*/"Sun": () => { m = sunIndexPageMenu(gps); }, - "Moon": () => { + /*LANG*/"Moon": () => { m = moonIndexPageMenu(gps); }, "< Back": () => { load(); } diff --git a/lang/de_DE.json b/lang/de_DE.json index 84f29a6c2..5ae7e449f 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -193,7 +193,15 @@ "cyan": "Cyan", "orange": "Orange", "purple": "Violett", - "grey": "Grau" + "grey": "Grau", + "New Moon": "Neumond", + "Waxing Crescent": "Zunehmender Sichelmond ", + "First Quarter": "Zunehmender Halbmond", + "Waxing Gibbous": "Zunehmender Mond", + "Full Moon": "Vollmond,", + "Waning Gibbous": "Abnehmender Mond", + "Last Quater": "Abnehmender Halbmond", + "Waning Crescent": "Abnehmender Sichelmond" }, "alarm": { "//": "App-specific overrides", From 5fe0a7ad6a2c2158f4a42a099a9bd8c4a3deb1cc Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Mon, 12 Dec 2022 07:37:36 +0100 Subject: [PATCH 14/82] astrocalc: Update description --- apps/astrocalc/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index 1f238be12..653c097da 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -2,7 +2,7 @@ "id": "astrocalc", "name": "Astrocalc", "version": "0.04", - "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", + "description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app", "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool", "supports": ["BANGLEJS", "BANGLEJS2"], From 214a018843f08d3bb8da00ffba5f231ab5bdea3a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 12 Dec 2022 09:01:00 +0000 Subject: [PATCH 15/82] update shortnames --- apps/health/metadata.json | 1 + apps/simplestpp/metadata.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/health/metadata.json b/apps/health/metadata.json index d4eab1f38..12f6b617f 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -1,6 +1,7 @@ { "id": "health", "name": "Health Tracking", + "shortName": "Health", "version": "0.17", "description": "Logs health data and provides an app to view it", "icon": "app.png", diff --git a/apps/simplestpp/metadata.json b/apps/simplestpp/metadata.json index cf7648d85..d808b132b 100644 --- a/apps/simplestpp/metadata.json +++ b/apps/simplestpp/metadata.json @@ -1,6 +1,7 @@ { "id": "simplestpp", - "name": "Simplest++", + "name": "Simplest++ Clock", + "shortName": "Simplest++", "version": "0.01", "description": "The simplest working clock, with fast load and clock_info, acts as a tutorial piece", "readme": "README.md", From 6a434f35ccf3aa8fa2254c221dab731e238c426a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 12 Dec 2022 11:32:20 +0000 Subject: [PATCH 16/82] 0.03: Fix icons broken in 0v02 (#2386) + Store all icons in a separate binary file (much faster lookup) --- apps/messageicons/icons.img | Bin 0 -> 5168 bytes apps/messageicons/icons/bibel.png | Bin 0 -> 204 bytes apps/messageicons/icons/bring.png | Bin 0 -> 210 bytes apps/messageicons/icons/default.png | Bin 0 -> 207 bytes apps/messageicons/icons/etar.png | Bin 0 -> 182 bytes apps/messageicons/icons/generate.js | 143 +++++++++++++++++++++ apps/messageicons/icons/gmx.png | Bin 0 -> 227 bytes apps/messageicons/icons/home assistant.png | Bin 0 -> 269 bytes apps/messageicons/icons/icon_names.json | 111 ++++++++++++++++ apps/messageicons/icons/kalender.png | Bin 0 -> 249 bytes apps/messageicons/icons/music.png | Bin 0 -> 209 bytes apps/messageicons/icons/n26.png | Bin 0 -> 188 bytes apps/messageicons/icons/nextbike.png | Bin 0 -> 228 bytes apps/messageicons/icons/nina.png | Bin 0 -> 261 bytes apps/messageicons/icons/warnapp.png | Bin 0 -> 247 bytes apps/messageicons/icons/wordfeud.png | Bin 0 -> 213 bytes apps/messageicons/lib.js | 80 +----------- apps/messageicons/metadata.json | 5 +- 18 files changed, 262 insertions(+), 77 deletions(-) create mode 100644 apps/messageicons/icons.img create mode 100644 apps/messageicons/icons/bibel.png create mode 100644 apps/messageicons/icons/bring.png create mode 100644 apps/messageicons/icons/default.png create mode 100644 apps/messageicons/icons/etar.png create mode 100755 apps/messageicons/icons/generate.js create mode 100644 apps/messageicons/icons/gmx.png create mode 100644 apps/messageicons/icons/home assistant.png create mode 100644 apps/messageicons/icons/icon_names.json create mode 100644 apps/messageicons/icons/kalender.png create mode 100644 apps/messageicons/icons/music.png create mode 100644 apps/messageicons/icons/n26.png create mode 100644 apps/messageicons/icons/nextbike.png create mode 100644 apps/messageicons/icons/nina.png create mode 100644 apps/messageicons/icons/warnapp.png create mode 100644 apps/messageicons/icons/wordfeud.png diff --git a/apps/messageicons/icons.img b/apps/messageicons/icons.img new file mode 100644 index 0000000000000000000000000000000000000000..104168357f754ea3bd419eb544afd41ea7d297f9 GIT binary patch literal 5168 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 Cl2Dxh literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/bring.png b/apps/messageicons/icons/bring.png new file mode 100644 index 0000000000000000000000000000000000000000..673d1b7be024acf0f943766bca951af24b0536a4 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|dOTemLp;3S zP7UO1FyL^0`mg<1f|Jp6x78=)>t@dQx-lpqB((pKyHvxSr8+_r+xc!=NhLJ#eJW=Y zVhWqOUZ>L`nn6470Nb4$`Ppyf>o%WgefDn&L$S*Jb1Xg=Bkt-&u9U9eS$ty|W5)d{ z%!?A6Z5^)G&r7jBv4(rghZVWIcC{Xy_#-sszfmB^DP1c@=QGQ9sunD$0=kRA)78&q Iol`;+0E%=`3jhEB literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/default.png b/apps/messageicons/icons/default.png new file mode 100644 index 0000000000000000000000000000000000000000..1f85079df4e48ecf3280039a267fb380a4a05afc GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Iz3$+Lp;3S zPQA$2pupk!_`m%ux7mKaJCv?XJFPC?z$_k+lx8PqvxUE*V;a}gDeG0DST%1~F{%`( zv1w~QK4Hu3GGAlK*Zqu26XJ?u5Ae9Gj$y4(n6^eOfu*U6jZut+)qmerk<8i4Y{N71 z+q62?eCKa?m8&xGN|zhotBVqQe#=*E{(PhU|7CkdEA~S>JyuMx13HSq)78&qol`;+ E00LS_)Bpeg literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/etar.png b/apps/messageicons/icons/etar.png new file mode 100644 index 0000000000000000000000000000000000000000..24f0cc587b46848dc5c6507bdbfdfcf83d211947 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|3OrpLLp;2b z|NQ^|zn)Es>Da?`MrCDXSH?EEM;eYUD<2w6SB!4D%2f1N_Spf!1V-15XAT^2Fz9HT z&Z_W!(Q>{NJ3TWU5jHjX3pRWgno~_W9*FX7T**A!>Pdz4F-hJhzFx0c_K5N=eJMZt e-@^Ep%nTD6kM6wK-%$&+o59o7&t;ucLK6TUt3VY1 literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/generate.js b/apps/messageicons/icons/generate.js new file mode 100755 index 000000000..e857032af --- /dev/null +++ b/apps/messageicons/icons/generate.js @@ -0,0 +1,143 @@ +#!/usr/bin/node + +// Creates lib.js from icons +// npm install png-js + +// default icon must come first in icon_names + +var imageconverter = require("../../../webtools/imageconverter.js"); +var icons = JSON.parse(require("fs").readFileSync(__dirname+"/icon_names.json")); +const imgOptions = { + mode : "1bit", + inverted : true, + transparent : true, + output: "raw" +}; +var PNG = require('png-js'); +var IMAGE_BYTES = 76; + +var iconTests = []; +var iconImages = []; // array of converted icons +var iconIndices = {}; // maps filename -> index in iconImages + +var promises = []; + +icons.forEach(icon => { + var index = iconIndices[icon.icon]; + if (index===undefined) { // need a new icon + index = iconImages.length; + iconIndices[icon.icon] = index; + iconImages.push(""); // placeholder + // create image + console.log("Loading "+icon.icon); + var png = new PNG(require("fs").readFileSync(__dirname+"/"+icon.icon)); + if (png.width!=24 || png.height!=24) { + console.warn(icon.icon+" should be 24x24px"); + } + + promises.push(new Promise(r => { + png.decode(function (pixels) { + var rgba = new Uint8Array(pixels); + var isTransparent = false; + for (var i=0;i { + // Yay, more JS. Why is it so hard to get the bytes??? + iconData.set(Array.prototype.slice.call(Buffer.from(img,"binary")), idx*IMAGE_BYTES) + }); + + console.log("Saving images"); + require("fs").writeFileSync(__dirname+"/../icons.img", Buffer.from(iconData,"binary")); + + console.log("Saving library"); + require("fs").writeFileSync(__dirname+"/../lib.js", `exports.getImage = function(msg) { + if (msg.img) return atob(msg.img); + let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + if (msg.id=="music") s="music"; + let match = ${JSON.stringify(","+icons.map(icon=>icon.app+"|"+icon.index).join(",")+",")}.match(new RegExp(\`,\${s}\\\\|(\\\\d+)\`)) + return require("Storage").read("messageicons.img", (match===null)?0:match[1]*${IMAGE_BYTES}, ${IMAGE_BYTES}); +}; + +exports.getColor = function(msg,options) { + options = options||{}; + var st = options.settings || require('Storage').readJSON("messages.settings.json", 1) || {}; + if (options.default===undefined) options.default=g.theme.fg; + if (st.iconColorMode == 'mono') return options.default; + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + return { + // generic colors, using B2-safe colors + // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used + "airbnb": "#ff385c", // https://news.airbnb.com/media-assets/category/brand/ + "mail": "#ff0", + "music": "#f0f", + "phone": "#0f0", + "sms message": "#0ff", + // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) + // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) + "bibel": "#54342c", + "bring": "#455a64", + "discord": "#5865f2", // https://discord.com/branding + "etar": "#36a18b", + "facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo + "gmail": "#ea4335", + "gmx": "#1c449b", + "google": "#4285F4", + "google home": "#fbbc05", +// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background + "instagram": "#ff0069", // https://about.instagram.com/brand/gradient + "lieferando": "#ff8000", + "linkedin": "#0a66c2", // https://brand.linkedin.com/ + "messenger": "#0078ff", + "mastodon": "#563acc", // https://www.joinmastodon.org/branding + "mattermost": "#00f", + "n26": "#36a18b", + "nextbike": "#00f", + "newpipe": "#f00", + "nina": "#e57004", + "opentasks": "#409f8f", + "outlook mail": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "paypal": "#003087", + "pocket": "#ef4154f", // https://blog.getpocket.com/press/ + "post & dhl": "#f2c101", + "reddit": "#ff4500", // https://www.redditinc.com/brand + "signal": "#3a76f0", // https://github.com/signalapp/Signal-Desktop/blob/main/images/signal-logo.svg + "skype": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "slack": "#e51670", + "snapchat": "#ff0", + "steam": "#171a21", + "teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "telegram": "#0088cc", + "telegram foss": "#0088cc", + "to do": "#3999e5", + "twitch": "#9146ff", // https://brand.twitch.tv/ + "twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit + "vlc": "#ff8800", + "whatsapp": "#4fce5d", + "wordfeud": "#e7d3c7", + "youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors + }[s]||options.default; +}; + `); +}); diff --git a/apps/messageicons/icons/gmx.png b/apps/messageicons/icons/gmx.png new file mode 100644 index 0000000000000000000000000000000000000000..185c90aa3195fcaeebc433838e2badfaac39fb6c GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|=6bp~hIn|t z4RPdaFyLtZZ!gs>cuA2Z_L1JX+0sjw@7q; z5|8bQCSC)R_nY2{I)Px#$4Nv%R7gv`mD>`4AP7X`|3BKsCLBaAOOEwcLmYu+rPf-z4nBkei0C;+^*j)O zlXIaJND9!&tB?@jC9gnSK$cuWWUOwV39yp$j~O@xc*&)TB1i~`2D%MEOGo2)M$Zg9 zom<1pzUgJ`>p59P@1fNWJ!CIVOL(ZrOR6mbjhkB3UqUSt4G-#Zi&9>nvb<`kKpJc2 zInlm^6(A7QqrwV2&-t`ysJ_uSxJ8TX#6Yc!(6tKmWN6&~6`Z>F(<^fqd>n8C0C)1a TN$tt&00000NkvXXu0mjfXbEl7 literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/icon_names.json b/apps/messageicons/icons/icon_names.json new file mode 100644 index 000000000..0085731cc --- /dev/null +++ b/apps/messageicons/icons/icon_names.json @@ -0,0 +1,111 @@ +[ + { "app":"default", "icon":"default.png" }, + { "app":"airbnb", "icon":"airbnb.png" }, + { "app":"alarm", "icon":"alarm.png" }, + { "app":"alarmclockreceiver", "icon":"alarm.png" }, + { "app":"amazon shopping", "icon":"amazon.png" }, + { "app":"bibel", "icon":"bibel.png" }, + { "app":"bitwarden", "icon":"security.png" }, + { "app":"1password", "icon":"security.png" }, + { "app":"lastpass", "icon":"security.png" }, + { "app":"dashlane", "icon":"security.png" }, + { "app":"bring", "icon":"bring.png" }, + { "app":"calendar", "icon":"etar.png" }, + { "app":"etar", "icon":"etar.png" }, + { "app":"chat", "icon":"google chat.png" }, + { "app":"chrome", "icon":"chrome.png" }, + { "app":"corona-warn", "icon":"coronavirus.png" }, + { "app":"bmo", "icon":"bank.png" }, + { "app":"desjardins", "icon":"bank.png" }, + { "app":"rbc mobile", "icon":"bank.png" }, + { "app":"nbc", "icon":"bank.png" }, + { "app":"rabobank", "icon":"bank.png" }, + { "app":"scotiabank", "icon":"bank.png" }, + { "app":"td (canada)", "icon":"bank.png" }, + { "app":"discord", "icon":"discord.png" }, + { "app":"drive", "icon":"google drive.png" }, + { "app":"element", "icon":"matrix element.png" }, + { "app":"facebook", "icon":"facebook.png" }, + { "app":"messenger", "icon":"facebook messenger.png" }, + { "app":"firefox", "icon":"firefox.png" }, + { "app":"firefox beta", "icon":"firefox.png" }, + { "app":"firefox nightly", "icon":"firefox.png" }, + { "app":"f-droid", "icon":"security.png" }, + { "app":"neo store", "icon":"security.png" }, + { "app":"aurora droid", "icon":"security.png" }, + { "app":"github", "icon":"github.png" }, + { "app":"gitlab", "icon":"gitlab.png" }, + { "app":"gmx", "icon":"gmx.png" }, + { "app":"google", "icon":"google.png" }, + { "app":"google home", "icon":"google home.png" }, + { "app":"google play store", "icon":"google play store.png" }, + { "app":"home assistant", "icon":"home assistant.png" }, + { "app":"instagram", "icon":"instagram.png" }, + { "app":"kalender", "icon":"kalender.png" }, + { "app":"keep notes", "icon":"google keep.png" }, + { "app":"lieferando", "icon":"lieferando.png" }, + { "app":"linkedin", "icon":"linkedin.png" }, + { "app":"maps", "icon":"map.png" }, + { "app":"organic maps", "icon":"map.png" }, + { "app":"osmand", "icon":"map.png" }, + { "app":"mastodon", "icon":"mastodon.png" }, + { "app":"fedilab", "icon":"mastodon.png" }, + { "app":"tooot", "icon":"mastodon.png" }, + { "app":"tusky", "icon":"mastodon.png" }, + { "app":"mattermost", "icon":"mattermost.png" }, + { "app":"n26", "icon":"n26.png" }, + { "app":"netflix", "icon":"netflix.png" }, + { "app":"news", "icon":"news.png" }, + { "app":"cbc news", "icon":"news.png" }, + { "app":"rc info", "icon":"news.png" }, + { "app":"reuters", "icon":"news.png" }, + { "app":"ap news", "icon":"news.png" }, + { "app":"la presse", "icon":"news.png" }, + { "app":"nbc news", "icon":"news.png" }, + { "app":"nextbike", "icon":"nextbike.png" }, + { "app":"nina", "icon":"nina.png" }, + { "app":"outlook mail", "icon":"outlook.png" }, + { "app":"paypal", "icon":"paypal.png" }, + { "app":"phone", "icon":"phone.png" }, + { "app":"plex", "icon":"plex.png" }, + { "app":"pocket", "icon":"pocket.png" }, + { "app":"post & dhl", "icon":"delivery.png" }, + { "app":"proton mail", "icon":"protonmail.png" }, + { "app":"reddit", "icon":"reddit.png" }, + { "app":"sync pro", "icon":"reddit.png" }, + { "app":"sync dev", "icon":"reddit.png" }, + { "app":"boost", "icon":"reddit.png" }, + { "app":"infinity", "icon":"reddit.png" }, + { "app":"slide", "icon":"reddit.png" }, + { "app":"signal", "icon":"signal.png" }, + { "app":"skype", "icon":"skype.png" }, + { "app":"slack", "icon":"slack.png" }, + { "app":"snapchat", "icon":"snapchat.png" }, + { "app":"starbucks", "icon":"cafe.png" }, + { "app":"steam", "icon":"steam.png" }, + { "app":"teams", "icon":"teams.png" }, + { "app":"telegram", "icon":"telegram.png" }, + { "app":"telegram foss", "icon":"telegram.png" }, + { "app":"threema", "icon":"threema.png" }, + { "app":"tiktok", "icon":"tiktok.png" }, + { "app":"to do", "icon":"task.png" }, + { "app":"opentasks", "icon":"task.png" }, + { "app":"tasks", "icon":"task.png" }, + { "app":"transit", "icon":"transit.png" }, + { "app":"twitch", "icon":"twitch.png" }, + { "app":"twitter", "icon":"twitter.png" }, + { "app":"uber", "icon":"taxi.png" }, + { "app":"lyft", "icon":"taxi.png" }, + { "app":"vlc", "icon":"vlc.png" }, + { "app":"warnapp", "icon":"warnapp.png" }, + { "app":"whatsapp", "icon":"whatsapp.png" }, + { "app":"wordfeud", "icon":"wordfeud.png" }, + { "app":"youtube", "icon":"youtube.png" }, + { "app":"newpipe", "icon":"youtube.png" }, + { "app":"zoom", "icon":"videoconf.png" }, + { "app":"meet", "icon":"videoconf.png" }, + { "app":"music", "icon":"music.png" }, + { "app":"sms message", "icon":"default.png" }, + { "app":"mail", "icon":"default.png" }, + { "app":"gmail", "icon":"default.png" } +] diff --git a/apps/messageicons/icons/kalender.png b/apps/messageicons/icons/kalender.png new file mode 100644 index 0000000000000000000000000000000000000000..dd807dd9e9241b33e305dc65c4756ff387b0afc3 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Hha1_hIn{i z4RhpcFyNT{-(JdCh{utOd$!cxlU%MD;+->p7Vryts#tsqs679ng~RRR#E6L+Y>BN7 z)*IexI^NjCt+eLI)B|NvrKzu#--x=ithV;xOgk2on8y<8^jzlMf?%CVzsgxA=A`e_ z3Q1B6E?ucsbv&xLa`9B7t*#SiPv=N;dt~@laOKN49h(YoUR)O5dY^GpQcL#Rgx8be x?;cdv`nBE1`t$#Pn{Hn$ee&kR`Xu$6oHDo4Pncbqb^+*o22WQ%mvv4FO#ptQWV!$V literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/music.png b/apps/messageicons/icons/music.png new file mode 100644 index 0000000000000000000000000000000000000000..62f7acfeef8108ce0f8c7018e3d11a361367301e GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|x;uZ`dLbqk@%!p4}%4DMW@CLJz m!k>Kys@J_wci7(Yfsg;e?43p}b1Q%@VDNPHb6Mw<&;$TQXi9Vd literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/nextbike.png b/apps/messageicons/icons/nextbike.png new file mode 100644 index 0000000000000000000000000000000000000000..467bed8ac11be9365381c81579f3cc3e89c3d23f GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|=6Sj}hIn|t zo$AeZK!L;M>A&`62V2-Q?QhSxv25MJ=RAv*9SK-OktR~E0tm9u1h?7+FmGS-Y&V)F1g{par2rxbN|L){{El+ ces}@H<{hW^GjqJV3Un!hr>mdKI;Vst0G$h4B>(^b literal 0 HcmV?d00001 diff --git a/apps/messageicons/icons/nina.png b/apps/messageicons/icons/nina.png new file mode 100644 index 0000000000000000000000000000000000000000..2669b640155d5e3b008316621bccd36f5d55e849 GIT binary patch literal 261 zcmV+g0s8)lP)Px#zez+vR7gwBmfH@%AP7U{|3BKrWiE(CE4%u(1bQr>PIAsUJ-7q`L?lJHD6a!V z#cL64{=0yyyGJe>2O6*n^KwrBD?S1vjU_-4s0&RQ1sFN;08scC_1(cJKsU~wNA03& zSgi)&HcFMogLs>S02@5JNvad;qI`b?C_BF69CQw}a1;QW>6qG*u)?AjV3ig)1H^sV6aaYbvsI^x(tCe$k`~J_TYg+ctZ4^(?KA`nAB=s#%WJ`o#tm)|n zkF#b9n^;4qYGp7-s8=37@p?sS_r7 Date: Mon, 12 Dec 2022 11:37:19 +0000 Subject: [PATCH 17/82] fix overzealous sanity check --- bin/sanitycheck.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 6537f4389..838f99895 100644 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -245,7 +245,7 @@ apps.forEach((app,appIdx) => { if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`, {file:appDirRelative+file.url}); } // warn if JS icon is the wrong size - if (file.name == app.id+".img") { + if (file.name == app.id+".img" && file.evaluate) { let icon; let match = fileContents.match(/^\s*E\.toArrayBuffer\(atob\(\"([^"]*)\"\)\)\s*$/); if (match==null) match = fileContents.match(/^\s*atob\(\"([^"]*)\"\)\s*$/); From 66e35d0d64f9ebc4fad5e32cdb57d8158a03806a Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 12 Dec 2022 17:14:27 +0100 Subject: [PATCH 18/82] 0.30: Another try on the swipe --- apps/hworldclock/ChangeLog | 1 + apps/hworldclock/README.md | 2 + apps/hworldclock/app.js | 151 +++++++++++++++++++++++---------- apps/hworldclock/metadata.json | 2 +- 4 files changed, 112 insertions(+), 44 deletions(-) diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index 13e3fa809..eef39a6ca 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -13,3 +13,4 @@ 0.27: BJS2: Changed swipe down to swipe up 0.28: Reverted changes to implementation of 0.25 0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps +0.30: BJS2: swipe seems to be working now diff --git a/apps/hworldclock/README.md b/apps/hworldclock/README.md index 93780bc2d..905e9987b 100644 --- a/apps/hworldclock/README.md +++ b/apps/hworldclock/README.md @@ -14,6 +14,8 @@ Provide names and the UTC offsets for up to three other timezones in the app sto The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones. +BangleJS2: Swipe up to rotate screen. So you can show the time to a friend real quick. + ## Requests Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if you have feature requests or notice bugs. diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index 6bd30be8e..38afa5572 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -7,6 +7,7 @@ var showSunInfo; var colorWhenDark; // ------- Settings file +const BANGLEJS2 = process.env.HWVERSION == 2; const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; @@ -15,7 +16,7 @@ require("Font5x9Numeric7Seg").add(Graphics); require("FontTeletext10x18Ascii").add(Graphics); // Font for single secondary time -const secondaryTimeFontSize = 4; +const secondaryTimeFontSize = 4; const secondaryTimeZoneFontSize = 2; // Font / columns for multiple secondary times @@ -24,6 +25,7 @@ const xcol1 = 10; const xcol2 = g.getWidth() - xcol1; const font = "6x8"; +let drag; /* TODO: we could totally use 'Layout' here and avoid a whole bunch of hard-coded offsets */ @@ -39,7 +41,7 @@ const yposWorld = big ? 170 : 120; const OFFSET_TIME_ZONE = 0; const OFFSET_HOURS = 1; -var PosInterval = 0; +var PosInterval = 0; var offsets = require("Storage").readJSON("hworldclock.settings.json") || []; @@ -87,7 +89,7 @@ const mockOffsets = { //offsets = mockOffsets.fourOffsets; // should render in columns // END TESTING CODE - + // Load settings function loadMySettings() { @@ -141,9 +143,11 @@ function getCurrentTimeFromOffset(dt, offset) { function updatePos() { coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"}; if (coord.lat != 0 && coord.lon != 0) { + //pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon); times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon); rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5); set = "v" + times.sunset.toString().split(" ")[4].substr(0,5); + //noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon); } else { rise = null; set = null; @@ -177,7 +181,7 @@ function drawSeconds() { //console.log(seconds); if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x //console.log(seconds); - g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); + g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); queueDrawSeconds(); } @@ -194,18 +198,18 @@ function draw() { let time = da[4].split(":"); let hours = time[0], minutes = time[1]; - - + + if (_12hour){ //do 12 hour stuff if (hours > 12) { ampm = "PM"; - hours = hours - 12; - if (hours < 10) hours = doublenum(hours); + hours = hours - 12; + if (hours < 10) hours = doublenum(hours); } else { - ampm = "AM"; - } - } + ampm = "AM"; + } + } //g.setFont(font, primaryTimeFontSize); g.setFont("5x9Numeric7Seg",primaryTimeFontSize); @@ -219,18 +223,18 @@ function draw() { g.setColor(g.theme.fg); } g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true); - + // am / PM ? if (_12hour){ //do 12 hour stuff //let ampm = require("locale").medidian(new Date()); Not working g.setFont("Vector", 17); g.drawString(ampm, xyCenterSeconds, yAmPm, true); - } + } if (secondsMode != "none") drawSeconds(); // To make sure... - - // draw Day, name of month, Date + + // draw Day, name of month, Date //DATE let localDate = require("locale").date(new Date(), 1); localDate = localDate.substring(0, localDate.length - 5); @@ -249,7 +253,7 @@ function draw() { if (offsets.length === 1) { - let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; + let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); @@ -275,7 +279,7 @@ function draw() { g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set } else { - g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true); + g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true); } } //debug settings @@ -285,7 +289,7 @@ function draw() { //g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true); queueDraw(); - + if (secondsMode != "none") queueDrawSeconds(); } @@ -295,26 +299,63 @@ g.clear(); // Init the settings of the app loadMySettings(); -// Show launcher when middle button pressed -Bangle.setUI({ - mode : "clock", - remove : function() { - // Called to unload all of the clock app - if (PosInterval) clearInterval(PosInterval); - PosInterval = undefined; - if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); - drawTimeoutSeconds = undefined; - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - }}); -Bangle.loadWidgets(); -Bangle.drawWidgets(); + // draw immediately at first, queue update draw(); + + + +//if (BANGLEJS2) { + //Bangle.on("drag", e => { + let onDrag = e => { + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (Math.abs(dx)>Math.abs(dy)+10) { + // horizontal + if (dx < dy) { + // for later purpose + } else { + // for later purpose + } + } else if (Math.abs(dy)>Math.abs(dx)+10) { + // vertical + if (dx < dy) { //down + g.clear().setRotation(0); + draw(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } else { + g.clear().setRotation(2); + draw(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } + } else { + //console.log("tap " + e.x + " " + e.y); + if (e.x > 145 && e.y > 145) { + // for later purpose + } + } + } + }; //); + Bangle.on("drag", onDrag); + //} else { + //setWatch(xxx, BTN1, { repeat: true, debounce:50 }); // maybe adding this later + //setWatch(xxx, BTN3, { repeat: true, debounce:50 }); + //setWatch(xxx, BTN4, { repeat: true, debounce:50 }); + //setWatch(xxx, BTN5, { repeat: true, debounce:50 }); + // } +//} + + + if (!Bangle.isLocked()) { // Initial state if (showSunInfo) { if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval); @@ -326,15 +367,15 @@ if (!Bangle.isLocked()) { // Initial state if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; - } + } if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; draw(); // draw immediately, queue redraw - + }else{ if (secondsMode == "always") secondsTimeout = 1000; if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; - + if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; @@ -348,11 +389,11 @@ if (!Bangle.isLocked()) { // Initial state updatePos(); } draw(); // draw immediately, queue redraw - } + - -Bangle.on('lock',on=>{ +//Bangle.on('lock',on=>{ +let onLock = on => { if (!on) { // UNlocked if (showSunInfo) { if (PosInterval != 0) clearInterval(PosInterval); @@ -364,7 +405,7 @@ Bangle.on('lock',on=>{ if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; - } + } if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; @@ -373,7 +414,7 @@ Bangle.on('lock',on=>{ if (secondsMode == "always") secondsTimeout = 1000; if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; - + if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; @@ -386,7 +427,31 @@ Bangle.on('lock',on=>{ PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins updatePos(); } - draw(); // draw immediately, queue redraw + draw(); // draw immediately, queue redraw } - }); -} + }; +Bangle.on('lock', onLock); + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "custom",clock:true, + remove : function() { + // Called to unload all of the clock app + if (typeof PosInterval === "undefined") { + console.log("PosInterval is undefined"); + } else { + if (PosInterval) clearInterval(PosInterval); + } + PosInterval = undefined; + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + if (BANGLEJS2) Bangle.removeListener("drag",onDrag); + Bangle.removeListener("onLock",onLock); + }}); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// ); +} \ No newline at end of file diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index 3b633ead7..401201f51 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.29", + "version": "0.30", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", From 8962c2527ee4080e445586634cc5d3f9ca249879 Mon Sep 17 00:00:00 2001 From: pebl-hank Date: Mon, 12 Dec 2022 17:17:47 +0100 Subject: [PATCH 19/82] Update ChangeLog --- apps/messageicons/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/messageicons/ChangeLog b/apps/messageicons/ChangeLog index fefc6ee6e..68494fccc 100644 --- a/apps/messageicons/ChangeLog +++ b/apps/messageicons/ChangeLog @@ -1,2 +1,3 @@ 0.01: Moved message icons from messages into standalone library 0.02: Added several new icons and colors +0.03: ? From 5836d7de6559119866cf6d863c8e74a2d6a43d5e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 12 Dec 2022 16:33:37 +0000 Subject: [PATCH 20/82] oops. didn't commit --- apps/messageicons/ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/messageicons/ChangeLog b/apps/messageicons/ChangeLog index fefc6ee6e..26683e38b 100644 --- a/apps/messageicons/ChangeLog +++ b/apps/messageicons/ChangeLog @@ -1,2 +1,4 @@ 0.01: Moved message icons from messages into standalone library 0.02: Added several new icons and colors +0.03: Fix icons broken in 0v02 (#2386) + Store all icons in a separate binary file (much faster lookup) From 186630f0c70f15c38605d031c1ba1cfa81104d4e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 13 Dec 2022 08:51:01 +0000 Subject: [PATCH 21/82] Show installs/favourites with percentage values --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 2a89ea64f..9246a2b35 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 2a89ea64f7874b9264572f68836fe8ecd0a6b191 +Subproject commit 9246a2b350b9b240fe20b2631eac9b92926a50d4 From 821205c63ba4f5f6578f7f00d7eb69cc720c2e36 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 13 Dec 2022 16:00:54 +0000 Subject: [PATCH 22/82] Check for potential clashes (eg installing 2 battery widgets) and prompt for a solution --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 9246a2b35..376824068 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 9246a2b350b9b240fe20b2631eac9b92926a50d4 +Subproject commit 376824068d90986c245b46970fd80ccdca44e431 From dcefe4b393ccc5e6308752514e7ef29c8d354f3c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 09:33:03 +0000 Subject: [PATCH 23/82] Update clock_info's heart rate handling to ensure the HRM value is up to date --- modules/clock_info.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/clock_info.js b/modules/clock_info.js index 50968311e..6f37e5d3d 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -61,12 +61,15 @@ if (stepGoal == undefined) { exports.load = function() { // info used for drawing... - var hrm = "--"; + var hrm = 0; var alt = "--"; // callbacks (needed for easy removal of listeners) function batteryUpdateHandler() { bangleItems[0].emit("redraw"); } function stepUpdateHandler() { bangleItems[1].emit("redraw"); } - function hrmUpdateHandler() { bangleItems[2].emit("redraw"); } + function hrmUpdateHandler(e) { + if (e && e.confidence>60) hrm = Math.round(e.bpm); + bangleItems[2].emit("redraw"); + } function altUpdateHandler() { Bangle.getPressure().then(data=>{ if (!data) return; @@ -99,12 +102,12 @@ exports.load = function() { }, { name : "HRM", hasRange : true, - get : () => { let v = Math.round(Bangle.getHealthStatus("last").bpm); return { - text : v + " bpm", v : v, min : 40, max : 200, + get : () => { return { + text : (hrm||"--") + " bpm", v : hrm, min : 40, max : 200, img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }}, - show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); }, - hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; }, + show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); }, + hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = 0; }, } ], }]; From d0e67164d01d816500db831b560d1eda60543801 Mon Sep 17 00:00:00 2001 From: Hank Date: Wed, 14 Dec 2022 11:14:56 +0100 Subject: [PATCH 24/82] 0.31: Tweaking the swipe option; Added mylocation as a dependency. --- apps/hworldclock/ChangeLog | 1 + apps/hworldclock/README.md | 21 ++++-- apps/hworldclock/app.js | 133 +++++++++++++++++---------------- apps/hworldclock/metadata.json | 5 +- apps/hworldclock/settings.js | 3 +- 5 files changed, 89 insertions(+), 74 deletions(-) diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index eef39a6ca..df3965698 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -14,3 +14,4 @@ 0.28: Reverted changes to implementation of 0.25 0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps 0.30: BJS2: swipe seems to be working now +0.31: Tweaking the swipe option; Added mylocation as a dependency. diff --git a/apps/hworldclock/README.md b/apps/hworldclock/README.md index 905e9987b..273389775 100644 --- a/apps/hworldclock/README.md +++ b/apps/hworldclock/README.md @@ -1,8 +1,16 @@ + # Hanks World Clock - See the time in four locations In addition to the main clock and date in your current location, you can add up to three other locations. Great for travel or remote working. -Additionally we show the sunset/sunrise and seconds for the current location and the day name is shown in your locale. -If watch is locked, seconds get refreshed every 10 seconds. +Additionally we show the sunset/sunrise and seconds for the current location. The day name is shown in your locale. +Also, you can swipe up to show the current time to a friend. + +All this is configurable: + + - Show seconds only when unlocked (saves battery) / always / do not show seconds + - Green color on dark mode (on/off) + - Show sun info (on/off) (set your location in the mylocation app) + - Rotation degree on swipe (off / 90 / 180 / 270) ![](hworldclock.png) @@ -10,11 +18,11 @@ If watch is locked, seconds get refreshed every 10 seconds. Location for sun set / rise set with mylocation app. -Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India). +Provide names and the UTC offsets for up to three other timezones in the App Loader before uploading. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India). -The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones. +The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the App Launcher and update. Currently the clock only supports 24 hour time format for the additional time zones. -BangleJS2: Swipe up to rotate screen. So you can show the time to a friend real quick. +BangleJS2: Swipe up to rotate screen (Target rotation can be set in the settings). Swipe up again to go back to the default rotation. So you can show the time to a friend real quick or temporarily change orientation for sports etc. ## Requests @@ -24,5 +32,4 @@ Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if y Created by Hank. -Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock). -And Sun Clock [Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock) \ No newline at end of file +Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver) diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index 38afa5572..6b10fb4f0 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -2,18 +2,19 @@ // ------- Settings file const SETTINGSFILE = "hworldclock.json"; -var secondsMode; -var showSunInfo; -var colorWhenDark; +let secondsMode; +let showSunInfo; +let colorWhenDark; +let rotationTarget; // ------- Settings file -const BANGLEJS2 = process.env.HWVERSION == 2; +//const BANGLEJS2 = process.env.HWVERSION == 2; const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; const primaryDateFontSize = big?3:2; -require("Font5x9Numeric7Seg").add(Graphics); -require("FontTeletext10x18Ascii").add(Graphics); +let font5x9 = require("Font5x9Numeric7Seg").add(Graphics); +let font10x18 = require("FontTeletext10x18Ascii").add(Graphics); // Font for single secondary time const secondaryTimeFontSize = 4; @@ -27,8 +28,6 @@ const xcol2 = g.getWidth() - xcol1; const font = "6x8"; let drag; -/* TODO: we could totally use 'Layout' here and -avoid a whole bunch of hard-coded offsets */ const xyCenter = g.getWidth() / 2; const xyCenterSeconds = xyCenter + (big ? 85 : 68); @@ -41,22 +40,27 @@ const yposWorld = big ? 170 : 120; const OFFSET_TIME_ZONE = 0; const OFFSET_HOURS = 1; -var PosInterval = 0; +let PosInterval = 0; -var offsets = require("Storage").readJSON("hworldclock.settings.json") || []; +let offsets = require("Storage").readJSON("hworldclock.settings.json") || []; //=======Sun -setting = require("Storage").readJSON("setting.json",1); +let setting = require("Storage").readJSON("setting.json",1); E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ -SunCalc = require("suncalc"); // from modules folder +//https://raw.githubusercontent.com/pebl-hank/BangleApps/master/modules/suncalc.js +let SunCalc = require("suncalc"); // from modules folder const LOCATION_FILE = "mylocation.json"; -var rise = "read"; -var set = "..."; +let rise = "read"; +let set = "..."; //var pos = {altitude: 20, azimuth: 135}; //var noonpos = {altitude: 37, azimuth: 180}; //=======Sun -var ampm = "AM"; +let ampm = "AM"; + +let defaultRotation = setting.rotate || 0; +let currentRotation = defaultRotation; + // TESTING CODE // Used to test offset array values during development. @@ -91,30 +95,36 @@ const mockOffsets = { // END TESTING CODE -// Load settings -function loadMySettings() { - // Helper function default setting - function def (value, def) {return value !== undefined ? value : def;} - - var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; - secondsMode = def(settings.secondsMode, "when unlocked"); - showSunInfo = def(settings.showSunInfo, true); - colorWhenDark = def(settings.colorWhenDark, "green"); +// ================ Load settings +// Helper function default setting +let def = function(value, def) { + return value !== undefined ? value : def; } +let settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; +secondsMode = def(settings.secondsMode, "when unlocked"); +showSunInfo = def(settings.showSunInfo, true); +colorWhenDark = def(settings.colorWhenDark, "green"); +rotationTarget = def(settings.rotationTarget, "90"); +rotationTarget = parseInt(rotationTarget) || 0; +if (rotationTarget == 90) rotationTarget = 1; // very lame, but works for now. +if (rotationTarget == 180) rotationTarget = 2; +if (rotationTarget == 270) rotationTarget = 3; +// ================ Load settings + // Check settings for what type our clock should be -var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; +let _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; // timeout used to update every minute -var drawTimeout; -var drawTimeoutSeconds; -var secondsTimeout; +let drawTimeout; +let drawTimeoutSeconds; +let secondsTimeout; g.setBgColor(g.theme.bg); // schedule a draw for the next minute -function queueDraw() { +let queueDraw = function() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; @@ -123,7 +133,7 @@ function queueDraw() { } // schedule a draw for the next second -function queueDrawSeconds() { +let queueDrawSeconds = function() { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = setTimeout(function() { drawTimeoutSeconds = undefined; @@ -132,16 +142,16 @@ function queueDrawSeconds() { }, secondsTimeout - (Date.now() % secondsTimeout)); } -function doublenum(x) { +let doublenum = function(x) { return x < 10 ? "0" + x : "" + x; } -function getCurrentTimeFromOffset(dt, offset) { +let getCurrentTimeFromOffset = function(dt, offset) { return new Date(dt.getTime() + offset * 60 * 60 * 1000); } -function updatePos() { - coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"}; +let updatePos = function() { + let coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"}; if (coord.lat != 0 && coord.lon != 0) { //pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon); times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon); @@ -154,8 +164,7 @@ function updatePos() { } } - -function drawSeconds() { +let drawSeconds = function() { // get date let d = new Date(); let da = d.toString().split(" "); @@ -177,16 +186,13 @@ function drawSeconds() { } else { g.setColor(g.theme.fg); } - //console.log("---"); - //console.log(seconds); if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x - //console.log(seconds); g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); queueDrawSeconds(); } -function draw() { +let draw = function() { // get date let d = new Date(); let da = d.toString().split(" "); @@ -211,7 +217,6 @@ function draw() { } } - //g.setFont(font, primaryTimeFontSize); g.setFont("5x9Numeric7Seg",primaryTimeFontSize); if (g.theme.dark) { if (colorWhenDark == "green") { @@ -293,24 +298,10 @@ function draw() { if (secondsMode != "none") queueDrawSeconds(); } -// clean app screen -g.clear(); - -// Init the settings of the app -loadMySettings(); - - - - -// draw immediately at first, queue update -draw(); - - //if (BANGLEJS2) { - //Bangle.on("drag", e => { let onDrag = e => { if (!drag) { // start dragging drag = {x: e.x, y: e.y}; @@ -327,12 +318,20 @@ draw(); } else if (Math.abs(dy)>Math.abs(dx)+10) { // vertical if (dx < dy) { //down - g.clear().setRotation(0); - draw(); - Bangle.loadWidgets(); - Bangle.drawWidgets(); + //g.clear().setRotation(defaultRotation); + //currentRotation = defaultRotation; + //draw(); + //Bangle.loadWidgets(); + //Bangle.drawWidgets(); } else { - g.clear().setRotation(2); + if (currentRotation == rotationTarget) { + g.clear().setRotation(defaultRotation); + currentRotation = defaultRotation; + } else { + g.clear().setRotation(rotationTarget); + currentRotation = rotationTarget; + } + draw(); Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -346,13 +345,12 @@ draw(); } }; //); Bangle.on("drag", onDrag); - //} else { + //} <-- BJS2 only } else { //setWatch(xxx, BTN1, { repeat: true, debounce:50 }); // maybe adding this later //setWatch(xxx, BTN3, { repeat: true, debounce:50 }); //setWatch(xxx, BTN4, { repeat: true, debounce:50 }); //setWatch(xxx, BTN5, { repeat: true, debounce:50 }); // } -//} @@ -392,7 +390,6 @@ if (!Bangle.isLocked()) { // Initial state } -//Bangle.on('lock',on=>{ let onLock = on => { if (!on) { // UNlocked if (showSunInfo) { @@ -432,11 +429,13 @@ let onLock = on => { }; Bangle.on('lock', onLock); + // Show launcher when middle button pressed Bangle.setUI({ mode : "custom",clock:true, remove : function() { // Called to unload all of the clock app + g.setRotation(defaultRotation); // bring back default rotation if (typeof PosInterval === "undefined") { console.log("PosInterval is undefined"); } else { @@ -447,9 +446,15 @@ Bangle.setUI({ drawTimeoutSeconds = undefined; if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; - if (BANGLEJS2) Bangle.removeListener("drag",onDrag); + //if (BANGLEJS2) + Bangle.removeListener("drag",onDrag); Bangle.removeListener("onLock",onLock); }}); + + +g.clear().setRotation(defaultRotation); // clean app screen and make sure the default rotation is set +draw(); // draw immediately at first, queue update + Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index 401201f51..590f1dc86 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.30", + "version": "0.31", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", @@ -12,6 +12,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", + "dependencies": {"mylocation":"app"}, "storage": [ {"name":"hworldclock.app.js","url":"app.js"}, {"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true}, @@ -19,6 +20,6 @@ ], "data": [ {"name":"hworldclock.settings.json"}, - {"name":"hworldclock.json"} + {"name":"hworldclock.json"} ] } diff --git a/apps/hworldclock/settings.js b/apps/hworldclock/settings.js index 26c946b5f..ad97c161a 100644 --- a/apps/hworldclock/settings.js +++ b/apps/hworldclock/settings.js @@ -42,7 +42,8 @@ settings.showSunInfo = v; writeSettings(); } - } + }, + "Rotation": stringInSettings("rotationTarget", ["off", "90", "180", "270"]), }; E.showMenu(mainmenu); From 51304530f681fb2f90eb3fcc4f2a6fc42194114d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 10:23:22 +0000 Subject: [PATCH 25/82] 0.31: Remove calls to Bangle.loadWidgets as they are not needed and create warnings --- apps/hworldclock/ChangeLog | 1 + apps/hworldclock/app.js | 4 +--- apps/hworldclock/metadata.json | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index eef39a6ca..f571697b7 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -14,3 +14,4 @@ 0.28: Reverted changes to implementation of 0.25 0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps 0.30: BJS2: swipe seems to be working now +0.31: Remove calls to Bangle.loadWidgets as they are not needed and create warnings diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index 38afa5572..3ec4b2c3d 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -329,12 +329,10 @@ draw(); if (dx < dy) { //down g.clear().setRotation(0); draw(); - Bangle.loadWidgets(); Bangle.drawWidgets(); } else { g.clear().setRotation(2); draw(); - Bangle.loadWidgets(); Bangle.drawWidgets(); } } else { @@ -454,4 +452,4 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); // ); -} \ No newline at end of file +} diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index 401201f51..eb0e72ddc 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.30", + "version": "0.31", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", From 69660b69aa6b00552873c2b0bd0eedcfdd995cea Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 10:23:36 +0000 Subject: [PATCH 26/82] Add BTHome thermometer --- apps/bthometemp/ChangeLog | 1 + apps/bthometemp/README.md | 9 ++++++ apps/bthometemp/app-icon.js | 1 + apps/bthometemp/app.js | 58 ++++++++++++++++++++++++++++++++++ apps/bthometemp/app.png | Bin 0 -> 13318 bytes apps/bthometemp/metadata.json | 14 ++++++++ 6 files changed, 83 insertions(+) create mode 100644 apps/bthometemp/ChangeLog create mode 100644 apps/bthometemp/README.md create mode 100644 apps/bthometemp/app-icon.js create mode 100644 apps/bthometemp/app.js create mode 100644 apps/bthometemp/app.png create mode 100644 apps/bthometemp/metadata.json diff --git a/apps/bthometemp/ChangeLog b/apps/bthometemp/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bthometemp/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bthometemp/README.md b/apps/bthometemp/README.md new file mode 100644 index 000000000..1a8212ea4 --- /dev/null +++ b/apps/bthometemp/README.md @@ -0,0 +1,9 @@ +# BTHome Temperature and Pressure + +This app displays temperature and pressure and advertises them over bluetooth using BTHome.io standard (along with battery level) + +This can be used to integrate with [Home Assistant](https://www.home-assistant.io/), so you can use your Bangle as a wireless temperature/pressure sensor. + +More info on the standard at https://bthome.io + +And the data format used is https://bthome.io/format/ diff --git a/apps/bthometemp/app-icon.js b/apps/bthometemp/app-icon.js new file mode 100644 index 000000000..e2dff3eb9 --- /dev/null +++ b/apps/bthometemp/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4kA///1N6BIPf//1gMIwdE8sG2me+9Y/8C/2snXsoUNpdnzdt/xj/AH4AYgMRAAUQCyoYSCQNXs1muoFBFyHm1X//+qtwwPiMX1+YmczxP6uIwNFwN6yeDnGDmc504wNFwOpnGYC4OJweaGBsR9WTmYtBmc4GAOuC5ZGBt4SBAAQEBwf2JBcBiupnIuCmedxGTzVRC5cX1AuDnPZF4OKuIXLi3zIoedMgMzn9hC5uICQON5IDBxAXSznYC6RdDPQYXNO4JcB7pdCO56nBnGZ7p6DU5zXBXgSqDa5sAiPqIgOZd4c510RCxQXBi+pRQIXBxODzVxC5hIBvR1DnE505GMGAevzAvC/QuNGAfm1X//+qtwuOGAURq9ms11AoIWOGAQAEFw1EDBwWFggBCkUgAQMigUAAIIAJoABDCgIXQFwYXBCYYBDHAMCEAIkCFgcEAIIKCCoQFCkAhBAQIlCkAsBOoIXCBoIvEAwQTCAYI2BIwgXIF4YXDQwIVCC4YIBMIwfCAQRfGYBSPNC6TBFACgwBACouWAH4AiA=")) diff --git a/apps/bthometemp/app.js b/apps/bthometemp/app.js new file mode 100644 index 000000000..7b55777d1 --- /dev/null +++ b/apps/bthometemp/app.js @@ -0,0 +1,58 @@ +// history of temperature/pressure readings +var history = []; + +// When we get temperature... +function onTemperature(p) { + // Average the last 5 temperature readings + while (history.length>4) history.shift(); + history.push(p); + var avrTemp = history.reduce((i,h)=>h.temperature+i,0) / history.length; + var avrPressure = history.reduce((i,h)=>h.pressure+i,0) / history.length; + var t = require('locale').temp(avrTemp).replace("'","°"); + // Draw + var rect = Bangle.appRect; + g.reset(1).clearRect(rect.x, rect.y, rect.x2, rect.y2); + var x = (rect.x+rect.x2)/2; + var y = (rect.y+rect.y2)/2 + 10; + g.setFont("6x15").setFontAlign(0,0).drawString("Temperature:", x, y - 65); + g.setFontVector(50).setFontAlign(0,0).drawString(t, x, y-25); + g.setFont("6x15").setFontAlign(0,0).drawString("Pressure:", x, y+15 ); + g.setFont("12x20").setFontAlign(0,0).drawString(Math.round(avrPressure)+" hPa", x, y+40); + // Set Bluetooth Advertising + // https://bthome.io/format/ + var temp100 = Math.round(avrTemp*100); + var pressure100 = Math.round(avrPressure*100); + + Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information + bit 0: "Encryption flag" + bit 1-4: "Reserved for future use" + bit 5-7: "BTHome Version" */ + + 0x01, // Battery, 8 bit + E.getBattery(), + + 0x02, // Temperature, 16 bit + temp100&255,temp100>>8, + + 0x04, // Pressure, 16 bit + pressure100&255,(pressure100>>8)&255,pressure100>>16 + ]; + NRF.setAdvertising(Bangle.bleAdvert); +} + +// Gets the temperature in the most accurate way with pressure sensor +function drawTemperature() { + Bangle.getPressure().then(p =>{if (p) onTemperature(p);}); +} + +if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; +setInterval(function() { + drawTemperature(); +}, 10000); // update every 10s +Bangle.loadWidgets(); +Bangle.setUI({ + mode : "custom", + back : function() {load();} +}); +E.showMessage("Reading temperature..."); +drawTemperature(); diff --git a/apps/bthometemp/app.png b/apps/bthometemp/app.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8eb3f14ce0f0ed83734d8a386e0283ce665781 GIT binary patch literal 13318 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 literal 0 HcmV?d00001 diff --git a/apps/bthometemp/metadata.json b/apps/bthometemp/metadata.json new file mode 100644 index 000000000..4bfd08c31 --- /dev/null +++ b/apps/bthometemp/metadata.json @@ -0,0 +1,14 @@ +{ "id": "bthometemp", + "name": "BTHome Temperature and Pressure", + "shortName":"BTHome T", + "version":"0.01", + "description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard", + "icon": "app.png", + "tags": "bthome,bluetooth,temperature", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthometemp.app.js","url":"app.js"}, + {"name":"bthometemp.img","url":"app-icon.js","evaluate":true} + ] +} From f95ce3a84ead7a078216bf99bd3e538145ae100b Mon Sep 17 00:00:00 2001 From: glemco Date: Wed, 14 Dec 2022 12:58:42 +0100 Subject: [PATCH 27/82] circlesclock: fixed crash if item has no image and cutting long overflowing text --- apps/circlesclock/ChangeLog | 1 + apps/circlesclock/app.js | 3 +++ apps/circlesclock/metadata.json | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index cb5248e32..83abde6df 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -38,3 +38,4 @@ Add fast load capability 0.21: Remade all icons without a palette for dark theme Now re-adds widgets if they were hidden when fast-loading +0.22: Fixed crash if item has no image and cutting long overflowing text diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 444040ef0..30d6a48f4 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -183,9 +183,12 @@ let drawCircle = function(index, item, data) { if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min); if(data.short) txt = data.short; + //long text can overflow and we do not draw there anymore.. + if(txt.length>6) txt = txt.slice(0,5)+"\n"+txt.slice(5,10) drawGauge(w, h3, percent, color); drawInnerCircleAndTriangle(w); writeCircleText(w, txt); + if(!img) return; //or get it from the clkinfo? g.setColor(getCircleIconColor(index, color, percent)) .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index 7b4c25532..1b94c00b3 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.21", + "version":"0.22", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], From 99065debc71bb7ced9a13a5dc2e7caa3a940587c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 14:52:04 +0000 Subject: [PATCH 28/82] Ensure Agenda supplies an image for clkinfo items (fix #2397) --- apps/agenda/ChangeLog | 1 + apps/agenda/agenda.clkinfo.js | 2 +- apps/agenda/metadata.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index 7f749ff25..cb928213e 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -7,3 +7,4 @@ 0.07: Clkinfo improvements. 0.08: Fix error in clkinfo (didn't require Storage & locale) Fix clkinfo icon +0.09: Ensure Agenda supplies an image for clkinfo items diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index baa8b9516..54677327b 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -19,7 +19,7 @@ agendaItems.items.push({ name: "Agenda "+i, - get: () => ({ text: title + "\n" + dateStr, img: null}), + get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }), show: function() { agendaItems.items[i].emit("redraw"); }, hide: function () {} }); diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 7e49e3f96..58a5091cd 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -1,7 +1,7 @@ { "id": "agenda", "name": "Agenda", - "version": "0.08", + "version": "0.09", "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], From 1dcf6085d5f284d630dae2556275d335a8446396 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 15:00:29 +0000 Subject: [PATCH 29/82] Ensure Timer supplies an image for clkinfo items --- apps/smpltmr/ChangeLog | 3 ++- apps/smpltmr/clkinfo.js | 6 +++--- apps/smpltmr/metadata.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog index 805e8546f..12b77aacd 100644 --- a/apps/smpltmr/ChangeLog +++ b/apps/smpltmr/ChangeLog @@ -2,4 +2,5 @@ 0.02: Rewrite with new interface 0.03: Added clock infos to expose timer functionality to clocks. 0.04: Improvements of clock infos. -0.05: Updated clkinfo icon. \ No newline at end of file +0.05: Updated clkinfo icon. +0.06: Ensure Timer supplies an image for clkinfo items diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js index c16e6127e..270a14fc4 100644 --- a/apps/smpltmr/clkinfo.js +++ b/apps/smpltmr/clkinfo.js @@ -69,7 +69,7 @@ items: [ { name: null, - get: () => ({ text: getAlarmMinutesText() + (isAlarmEnabled() ? " min" : ""), img: null}), + get: () => ({ text: getAlarmMinutesText() + (isAlarmEnabled() ? " min" : ""), img: smpltmrItems.img }), show: function() { smpltmrItems.items[0].emit("redraw"); }, hide: function () {}, run: function() { } @@ -81,7 +81,7 @@ offsets.forEach((o, i) => { smpltmrItems.items = smpltmrItems.items.concat({ name: null, - get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: null}), + get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }), show: function() { smpltmrItems.items[i+1].emit("redraw"); }, hide: function () {}, run: function() { @@ -94,4 +94,4 @@ }); return smpltmrItems; -}) \ No newline at end of file +}) diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json index 2191902de..71e793cc2 100644 --- a/apps/smpltmr/metadata.json +++ b/apps/smpltmr/metadata.json @@ -2,7 +2,7 @@ "id": "smpltmr", "name": "Simple Timer", "shortName": "Simple Timer", - "version": "0.05", + "version": "0.06", "description": "A very simple app to start a timer.", "icon": "app.png", "tags": "tool,alarm,timer,clkinfo", From 487742f037f926f5a2e84fb5df0184b835e0b4a5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 14 Dec 2022 17:24:31 +0000 Subject: [PATCH 30/82] 0.61: Fix regression where loading into messages app stops back from working (#2398) --- apps/messagegui/ChangeLog | 1 + apps/messagegui/app.js | 1 + apps/messagegui/metadata.json | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 36ec8191f..834d703ce 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -83,3 +83,4 @@ Don't write messages to flash until the app closes 0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373) 0.60: Fix saving of removal messages if UI not open +0.61: Fix regression where loading into messages app stops back from working (#2398) diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 0f3d90a9d..9f8a20219 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -370,6 +370,7 @@ function checkMessages(options) { } // If we have a new message, show it if ((toShow||options.showMsgIfUnread) && newMessages.length) { + delete newMessages[0].show; // stop us getting stuck here if we're called a second time showMessage(newMessages[0].id); // buzz after showMessage, so being busy during layout doesn't affect the buzz pattern if (global.BUZZ_ON_NEW_MESSAGE) { diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 5b1cb60c6..811f9baff 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.60", + "version": "0.61", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From bc3ab8ef76f7d244fd5b36c80f0025574af19635 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 26 Nov 2022 15:28:09 +0100 Subject: [PATCH 31/82] messagelist: new app --- apps/messagelist/ChangeLog | 1 + apps/messagelist/README.md | 69 ++ apps/messagelist/TODO.txt | 17 + apps/messagelist/app-icon.js | 1 + apps/messagelist/app.js | 1207 ++++++++++++++++++++++++++++++ apps/messagelist/app.png | Bin 0 -> 8988 bytes apps/messagelist/boot.js | 3 + apps/messagelist/lib.js | 246 ++++++ apps/messagelist/metadata.json | 28 + apps/messagelist/screenshot0.png | Bin 0 -> 19719 bytes apps/messagelist/screenshot1.png | Bin 0 -> 20953 bytes apps/messagelist/screenshot2.png | Bin 0 -> 20743 bytes apps/messagelist/screenshot3.png | Bin 0 -> 16791 bytes apps/messagelist/settings.js | 139 ++++ bin/sanitycheck.js | 1 + 15 files changed, 1712 insertions(+) create mode 100644 apps/messagelist/ChangeLog create mode 100644 apps/messagelist/README.md create mode 100644 apps/messagelist/TODO.txt create mode 100644 apps/messagelist/app-icon.js create mode 100644 apps/messagelist/app.js create mode 100644 apps/messagelist/app.png create mode 100644 apps/messagelist/boot.js create mode 100644 apps/messagelist/lib.js create mode 100644 apps/messagelist/metadata.json create mode 100644 apps/messagelist/screenshot0.png create mode 100644 apps/messagelist/screenshot1.png create mode 100644 apps/messagelist/screenshot2.png create mode 100644 apps/messagelist/screenshot3.png create mode 100644 apps/messagelist/settings.js mode change 100644 => 100755 bin/sanitycheck.js diff --git a/apps/messagelist/ChangeLog b/apps/messagelist/ChangeLog new file mode 100644 index 000000000..759f68777 --- /dev/null +++ b/apps/messagelist/ChangeLog @@ -0,0 +1 @@ +0.01: New app! \ No newline at end of file diff --git a/apps/messagelist/README.md b/apps/messagelist/README.md new file mode 100644 index 000000000..776d0d0e6 --- /dev/null +++ b/apps/messagelist/README.md @@ -0,0 +1,69 @@ +# Message List + +Display messages inline as a single list: +Displays one message at a time, if it doesn't fit on the screen you can scroll +up/down. When you reach the bottom, you can scroll on to the next message. + +## Installation +**First** uninstall the default [Message UI](/?id=messagegui) app (`messagegui`, +not the library!). +Then install this app. + +## Screenshots + +### Main menu: +![Screenshot](screenshot0.png) + +### Unread message: +![Screenshot](screenshot1.png) +The chevrons are hints for swipe actions: +- Swipe right to go back +- Swipe left for the message-actions menu +- Swipe down to show the previous message: We are currently viewing message 2 of 2, + so message 1 is "above" this one. + +### Long (read) message: +![Screenshot](screenshot2.png) +The button is disabled until you scroll all the way to the bottom. + +### Music: +![Screenshot](screenshot3.png) +Minimal setup: album name and buttons disabled through settings. +Swipe for next/previous song, tap to pause/resume. + +## Settings + +### Interface +* `Font size` - The font size used when displaying messages/music. +* `On Tap` - If messages are too large to fit on the screen, tapping the screen scrolls down. + This is the action to take when tapping a message after reaching the bottom: + - `Message menu`: Open menu with message actions + - `Dismiss`: Dismiss message right away + - `Back`: Go back to clock/main menu + - `Nothing`: Do nothing +* `Dismiss button` - Show inline button to dismiss message right away + +### Behaviour +* `Vibrate` - The pattern of buzzes when a new message is received. +* `Vibrate for calls` - The pattern of buzzes for incoming calls. +* `Vibrate for alarms` - The pattern of buzzes for (phone) alarms. +* `Repeat` - How often buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds. +* `Unread timer` - When a new message is received the Messages app is opened. + If there is no user input for this amount of time then the app will exit and return to the clock. +* `Auto-open` - Automatically open app when a new message arrives. +* `Respect quiet mode` - Prevent auto-opening during quiet mode. + +### Music +* `Auto-open` - Automatically open app when music starts playing. +* `Always visible` - Show "music" in the main menu even when nothing is playing. +* `Buttons` - Show `previous`/`play/pause`/`next` buttons on music screen. +* `Show album` - Display album names? + + +### Util +* `Delete all` - Erase all messages. + + +## Attributions + +Some icons used in this app are from https://icons8.com diff --git a/apps/messagelist/TODO.txt b/apps/messagelist/TODO.txt new file mode 100644 index 000000000..3a6d7b664 --- /dev/null +++ b/apps/messagelist/TODO.txt @@ -0,0 +1,17 @@ +## Nice to have: +* Add labels to B1 music HW buttons +* Add volume buttons to B2 music screen (when controls are enabled) +* Draw messages ourselves instead of piling hacks on Layout +* Make sure all icons are 24x24px: icon sizes affect layout +* Check/optimize layout for B1, other fonts (scrolling for just 5px is a shame) + +## Wishlist: +* Option to swipe-dismiss (instead of action menu) +* Maybe refactor showGrid() out into a general-use module? + +* Message replies (needs `android` support) +* Customize replies +* Custom replies (i.e. `textinput`) +* Hooks to add custom replies/actions, + e.g. external code could add "Send intent" option to Home Assistant messages + Maybe just use this for all replies, so we don't hardcode anything in "messages"? diff --git a/apps/messagelist/app-icon.js b/apps/messagelist/app-icon.js new file mode 100644 index 000000000..6ed3c1141 --- /dev/null +++ b/apps/messagelist/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA=")) diff --git a/apps/messagelist/app.js b/apps/messagelist/app.js new file mode 100644 index 000000000..c10ac726d --- /dev/null +++ b/apps/messagelist/app.js @@ -0,0 +1,1207 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +/* For example for maps: + +// 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="} +// call +{"t":"add","id":"call","src":"Phone","name":"Bob","number":"12421312",positive:true,negative:true} +*/ +{ + const B2 = process.env.HWVERSION>1, // Bangle.js 2? + RIGHT = 1, LEFT = -1, // swipe directions + UP = -1, DOWN = 1; // updown directions + const Layout = require("Layout"); + + const settings = () => require("messagegui").settings(); + const fontTiny = "6x8"; // fixed size, don't use this for important things + let fontNormal; + // setFont() is also called after we close the settings screen + const setFont = function() { + const fontSize = settings().fontSize; + if (fontSize===0) // small + fontNormal = g.getFonts().includes("6x15") ? "6x15" : "6x8:2"; + else if (fontSize===2) // large + fontNormal = g.getFonts().includes("6x15") ? "6x15:2" : "6x8:4"; + else // medium + fontNormal = g.getFonts().includes("12x20") ? "12x20" : "6x8:3"; + }; + setFont(); + + let active, back; // active screen, last active screen + + /// List of all our messages + let MESSAGES; + const saveMessages = function() { + const noSave = ["alarm", "call", "music"]; // assume these are outdated once we close the app + noSave.forEach(id => remove({id: id})); + require("messages").write(MESSAGES + .filter(m => m.id && !noSave.includes(m.id)) + .map(m => { + delete m.show; + return m; + }) + ); + }; + const uiRemove = function() { + if (musicTimeout) clearTimeout(musicTimeout); + layout = undefined; + Bangle.removeListener("message", onMessage); + saveMessages(); + clearUnreadStuff(); + delete Bangle.appRect; + }; + try { + MESSAGES = require("messages").getMessages(); + // Apply fast loaded messages + (Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, MESSAGES)); + delete Bangle.MESSAGES; + // Write them back to storage when we're done + E.on("kill", saveMessages); + } catch(e) { + g.reset().clear(); + E.showPrompt(/*LANG*/"Message file corrupt, erase all messages?", {title:/*LANG*/"Delete All Messages"}).then(isYes => { + // We are troubleshooting, so do a clean "load" in both cases (instead of Bangle.load) + if (isYes) { // OK: erase message file and reload this app + require("messages").clearAll(); + load("messagelist.app.js"); + } else { + load(); // well, this app won't work... let's go back to the clock + } + }); + } + + const setUI = function(options, cb) { + options = Object.assign({remove: () => uiRemove()}, options); + Bangle.setUI(options, cb); + Bangle.on("message", onMessage); + }; + + const remove = function(msg) { + if (msg.id==="call") call = undefined; + else if (msg.id==="map") map = undefined; + else if (msg.id==="alarm") alarm = undefined; + else if (msg.id==="music") music = undefined; + else MESSAGES = MESSAGES.filter(m => m.id!==msg.id); + }; + const buzz = function(msg) { + return require("messages").buzz(msg.src); + }; + const show = function(msg) { + delete msg.show; // don't show this again + if (msg.id==="call") showCall(msg); + else if (msg.id==="map") showMap(msg); + else if (msg.id==="alarm") showAlarm(msg); + else if (msg.id==="music") showMusic(msg); + else showMessage(msg); + }; + + const onMessage = function(type, msg) { + if (msg.handled) return; + msg.handled = true; + switch(type) { + case "call": + return onCall(msg); + case "music": + return onMusic(msg); + case "map": + return onMap(msg); + case "alarm": + return onAlarm(msg); + case "text": + return onText(msg); + case "clearAll": + MESSAGES = []; + if (["messages", "menu"].includes(active)) showMenu(); + break; + default: + E.showAlert(/*LANG*/"Unknown message type:"+"\n"+type).then(goBack); + } + }; + Bangle.on("message", onMessage); + + const onCall = function(msg) { + if (msg.t==="remove") { + call = undefined; + return exitScreen("call"); + } + // incoming call: show it + call = msg; + buzz(call); + showCall(); + }; + const onAlarm = function(msg) { + if (msg.t==="remove") { + alarm = undefined; + return exitScreen("alarm"); + } + alarm = msg; + buzz(alarm); + showAlarm(); + }; + let musicTimeout; + const onMusic = function(msg) { + const hadMusic = !!music; + if (musicTimeout) clearTimeout(musicTimeout); + musicTimeout = undefined; + if (msg.t==="remove") { + music = undefined; + if (active==="main" && hadMusic) return showMain(); // refresh menu: remove "Music" entry (if not always visible) + else return exitScreen("music"); + } + + music = Object.assign({}, music, msg); + + // auto-close after being paused + if (music.state!=="play") musicTimeout = setTimeout(function() { + musicTimeout = undefined; + if (active==="music" && (!music || music.state!=="play")) Bangle.showClock(); + }, 60*1000); // paused for 1 minute + // auto-close after "playing" way beyond song duration (because "stop" messages don't seem to exist) + else musicTimeout = setTimeout(function() { + musicTimeout = undefined; + if (active==="music" && (!music || music.state==="play")) Bangle.showClock(); + }, 2*Math.max(music.dur || 0, 5*60)*1000); // playing: assume ended after twice song duration, or at least 10 minutes + + if (active==="music") showMusic(); // update music screen + else if (active==="main" && !hadMusic) { + if (settings().openMusic && music.state==="play" && music.track) showMusic(); + else showMain(); // refresh menu: add "Music" entry + } + }; + const onMap = function(msg) { + const hadMap = !!map; + if (msg.t==="remove") { + map = undefined; + if (back==="map") back = undefined; + if (active==="main" && hadMap) return showMain(); // refresh menu: remove "Map" entry + else return exitScreen("map"); + } + map = msg; + if (["map", "music"].includes(active)) showMap(); // update map screen, or switch away from music (not other screens) + else if (active==="main" && !hadMap) showMain(); // refresh menu: add "Map" entry + }; + const onText = function(msg) { + require("messages").apply(msg, MESSAGES); + const mIdx = MESSAGES.findIndex(m => m.id===msg.id); + if (!MESSAGES[mIdx]) if (back==="messages") back = undefined; + if (active==="main") showMain(); // update message count + if (MESSAGES.length===0) exitScreen("messages"); // removed last message + else if (active==="messages") showMessage(messageNum); + if (msg.new) buzz(msg); + if (active!=="call") {// don't switch away from incoming call + if (active!=="messages" || messageNum===mIdx) showMessage(mIdx); + } + if (active==="messages") drawFooter(); // update footer with new number of messages + }; + + const getImage = function(msg, def) { + // app icons, provided by `messages` app + return require("messageicons").getImage(msg); + }; + const getImageColor = function(msg, def) { + // app colors, provided by `messages` app + return require("messageicons").getColor(msg, {default: def}); + }; + const getIcon = function(icon) { + return require("messagegui").getIcon(icon); + }; + const getIconColor = function(icon) { + return require("messagegui").getColor(icon); + }; + + /* + * icons should be 24x24px with 1bpp colors and transparancy + */ + const getMessageImage = function(msg) { + if (msg.img) return atob(msg.img); + if (msg.id==="music") return getIcon("Music"); + if (msg.id==="back") return getIcon("Back"); + const s = (msg.src || "").toLowerCase(); + + return getImage(s, "notification"); + }; + + const showMap = function() { + setActive("map"); + delete map.new; + let m, distance, street, target, eta; + m = map.title.match(/(.*) - (.*)/); + if (m) { + distance = m[1]; + street = m[2]; + } else { + street = map.title; + } + m = map.body.match(/(.*) - (.*)/); + if (m) { + target = m[1]; + eta = m[2]; + } else { + target = map.body; + } + let layout = new Layout({ + type: "v", c: [ + {type: "txt", font: fontNormal, label: target, bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, pad: 2}, + { + type: "h", bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, c: [ + {type: "txt", font: "6x8", label: "Towards"}, + {type: "txt", font: fontNormal, label: street}, + ] + }, + { + type: "h", fillx: 1, filly: 1, c: [ + map.img ? {type: "img", src: () => atob(map.img), scale: 2} : {}, + { + type: "v", fillx: 1, c: [ + {type: "txt", font: fontNormal, label: distance || ""}, + ] + }, + ] + }, + {type: "txt", font: "6x8:2", label: eta} + ] + }); + layout.render(); + // go back on any input + setUI({ + mode: "custom", + back: goBack, + btn: b => { + if (B2 || b===2) goBack(); + }, + swipe: dir => { + if (dir===RIGHT) showMain(); + }, + }); + }; + + const toggleMusic = function() { + const mc = cmd => { + if (Bangle.musicControl) Bangle.musicControl(cmd); + }; + if (!music) { + music = {state: "play"}; + mc("play"); + } else if (music.state==="play") { + music.state = "pause"; + mc("pause"); + } else { + music.state = "play"; + mc("play"); + } + if (layout && layout.musicIcon) { + // musicIcon/musicToggle .src returns icon based on current music.state + layout.update(layout.musicIcon); + if (layout.musicToggle) layout.update(layout.musicToggle); + layout.render(); + } + }; + + const doMusic = function(action) { + if (!Bangle.musicControl) return; + Bangle.buzz(50); + if (action==="toggle") toggleMusic(); + else Bangle.musicControl(action); + }; + const showMusic = function() { + if (active!==music) setActive("music"); + if (!music) music = {track: "", artist: "", album: "", state: "pause"}; + delete music.new; + const w = Bangle.appRect.w-50; // title/album need to leave room for icon + let artist, album; + if (music.album && settings().showAlbum) { + // max 2 lines for artist/album + artist = g.setFont(fontNormal).wrapString(music.artist, w).slice(0, 2).join("\n"); + album = g.wrapString(music.album, w).slice(0, 2).join("\n"); + } else { + // no album: artist gets 3 lines + artist = g.setFont(fontNormal).wrapString(music.artist, w).slice(0, 3).join("\n"); + album = ""; + } + // place (subtitle) on a new line + let track = music.track.replace(/ \(/, "\n("); + track = g.wrapString(track, Bangle.appRect.w).slice(0, 5).join("\n"); + // "unknown" n/c/dur can show up as -1 + let num, dur; + if ("n" in music && music.n>0) { + num = "#"+music.n; + if ("c" in music && music.c>0) { + num += "/"+music.c; + } + num = {type: "txt", font: fontTiny, bgCol: g.theme.bg, label: num}; + } + if ("dur" in music && music.dur>0) { + dur = Math.floor(music.dur/60)+":"+(music.dur%60).toString().padStart(2, "0"); + dur = {type: "txt", font: fontTiny, bgCol: g.theme.bg, label: dur}; + } + let info; + if (num && dur) info = {type: "h", fillx: 1, c: [{fillx: 1}, dur, {fillx: 1}, num, {fillx: 1},]}; + else if (num) info = num; + else if (dur) info = dur; + else info = {}; + + layout = new Layout({ + type: "v", c: [ + { + type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ + { + id: "musicIcon", type: "img", pad: 10, bgCol: g.theme.bg2, col: g.theme.fg2 + , src: () => getIcon((music.state==="play") ? "music" : "pause") + }, + { + type: "v", fillx: 1, c: [ + {type: "txt", font: fontNormal, col: g.theme.fg2, bgCol: g.theme.bg2, label: artist, pad: 2, id: "artist"}, + album ? {type: "txt", font: fontNormal, col: g.theme.fg2, bgCol: g.theme.bg2, label: album, pad: 2, id: "album"} : {}, + ] + } + ] + }, + {type: "txt", halign: 0, font: fontNormal, bgCol: g.theme.bg, label: track, fillx: 1, filly: 1, pad: 2, id: "track"}, + settings().musicButtons ? { + type: "h", fillx: 1, c: [ + B2 ? {} : {width: 4}, + { + type: "btn", id: "previous", cb: () => doMusic("previous") + , src: () => getIcon("previous") + }, + {fillx: 1}, + { + type: "btn", id: "musicToggle", cb: () => doMusic("toggle") + , src: () => getIcon((music.state==="play") ? "pause" : "play") + }, + {fillx: 1}, + { + type: "btn", id: "next", cb: () => doMusic("next") + , src: () => getIcon("next") + }, + B2 ? {} : {width: 4}, + ] + } : {}, + info, + ] + }); + layout.render(); + let options = {mode: "updown"}; + // B1 with buttons: left hand side of screen is used for "previous" + if (B2 || !settings().musicButtons) options.back = goBack; + setUI(options, ud => { + if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); + else { + if (B2 || settings().musicButtons) goBack(); // B1 left-hand touch is "previous", so we need a way to go back + else doMusic("toggle"); + } + }); + + Bangle.swipeHandler = dir => { + if (dir!==0) doMusic(dir===RIGHT ? "previous" : "next"); + }; + Bangle.on("swipe", Bangle.swipeHandler); + + if (Bangle.touchHandler) Bangle.removeListener("touch", Bangle.touchHandler); + if (settings().musicButtons) { + // visible buttons + // left = previous, middle = toggle, right = next + if (B2) Bangle.touchHandler = (_side, xy) => { + // accept touches on the whole bottom and pick the closest button + if (xy.y2*Bangle.appRect.w/3) doMusic("next"); + else doMusic("toggle"); + }; + else Bangle.touchHandler = (side) => { + if (side===1) doMusic("previous"); + if (side===2) doMusic("next"); + if (side===3) doMusic("toggle"); + }; + } else { + // no buttons: touch = toggle + // B2 setUI sets touchHandler, override that (we only want up/down swipes from the UI) + Bangle.touchHandler = (side, e) => { + // B1: side 1 (left) = back, B2: only toggle for e outside widget area + if ((!B2 && side>1) || (B2 && e.y>Bangle.appRect.y)) doMusic("toggle"); + }; + } + Bangle.on("touch", Bangle.touchHandler); + }; + + let layout; + + const clearStuff = function() { + delete Bangle.appRect; + layout = undefined; + setUI(); + g.reset().clearRect(Bangle.appRect); + }; + const setActive = function(screen, args) { + clearStuff(); + if (active && screen!==active) back = active; + if (screen==="messages") messageNum = args; + active = screen; + }; + /** + * Go back to previous screen, preserving history + */ + const goBack = function() { + if (back==="call" && call) showCall(); + else if (back==="map" && map) showMap(); + else if (back==="music" && music) showMusic(); + else if (back==="messages" && MESSAGES.length) showMessage(); + else if (back) showMain(); // previous screen was "main", or no longer valid + else Bangle.showClock(); // no previous screen: go back to clock + }; + /** + * Leave screen, and make sure goBack() won't take us there anymore; + * @param {string} screen + */ + const exitScreen = function(screen) { + if (back===screen) back = (active==="main") ? undefined : "main"; + if (active===screen) { + active = undefined; + goBack(); + } + }; + const showMain = function() { + setActive("main"); + let grid = {"": {title:/*LANG*/"Messages", align: 0, back: load}}; + if (call) grid[/*LANG*/"Incoming Call"] = {icon: "Phone", cb: showCall}; + if (alarm) grid[/*LANG*/"Alarm"] = {icon: "Alarm", cb: showAlarm}; + const unread = MESSAGES.filter(m => m.new).length; + if (unread) { + grid[unread+" "+/*LANG*/"New"] = {icon: "Unread", cb: () => showMessage(MESSAGES.findIndex(m => m.new))}; + grid[/*LANG*/"All"+` (${MESSAGES.length})`] = {icon: "Notification", cb: showMessage}; + } else { + const allLabel = MESSAGES.length+" "+(MESSAGES.length===1 ?/*LANG*/"Message" :/*LANG*/"Messages"); + if (MESSAGES.length) grid[allLabel] = {icon: "Notification", cb: showMessage}; + else grid[/*LANG*/"No Messages"] = {icon: "Neg", cb: load}; + } + if (unread { + E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Dismiss Read Messages"}).then(isYes => { + if (isYes) { + MESSAGES.filter(m => !m.new).forEach(msg => { + Bangle.messageResponse(msg, false); + remove(msg); + }); + } + showMain(); + }); + } + }; + } + if (map) grid[/*LANG*/"Map"] = {icon: "Map", cb: showMap}; + if (music || settings().alwaysShowMusic) grid[/*LANG*/"Music"] = {icon: "Music", cb: showMusic}; + grid[/*LANG*/"settings"] = {icon: "settings", cb: showSettings}; + showGrid(grid); + }; + const clamp = function(val, min, max) { + if (valmax) return max; + return val; + }; + /** + * Show grid of labeled buttons, + * + * items: + * { + * cb: callback, + * img: button image, + * icon: icon name, // string, use getIcon(icon) instead of img + * col: icon color, // optional: defaults to getColor(icon) + * } + * "" item is options: + * { + * title: string, + * back: callback, + * rows/cols: (optional) fit to this many columns/rows, omit for automatic fit + * align: bottom row alignment if items don't fit perfectly into a grid + * -1: left + * 1: right + * 0: left, but move final button to the right + * undefined: spread (can be unaligned with rest of grid!) + * } + * @param items + */ + const showGrid = function(items) { + clearStuff(); + const options = items[""] || {}, + back = options.back || items["< Back"]; + const keys = Object.keys(items).filter(k => k!=="" && k!=="< Back"); + let cols; + if (options.cols) { + cols = options.cols; + } else if (options.rows) { + cols = Math.ceil(keys.length/options.rows); + } else { + const rows = Math.round(Math.sqrt(keys.length)); + cols = Math.ceil(keys.length/rows); + } + + let l = {type: "v", c: []}; + if (options.title) { + l.c.push({id: "title", type: "txt", label: options.title, font: (B2 ? "12x20" : "6x8:2"), fillx: 1}); + } + const w = Bangle.appRect.w/cols, // set explicit width, because labels can stick out + bgs = [g.theme.bgH, g.theme.bg2], // background colors used for buttons + newRow = () => ({type: "h", filly: 1, c: []}); + let row = newRow(), + cbs = [[]]; // callbacks for Bangle.js 2 touchHandler below + keys.forEach(key => { + const item = items[key], + label = g.setFont(fontTiny).wrapString(key, w).join("\n"); + let color = "col" in item ? item.col : getIconColor(item.icon || "Unknown"); + if (color && bgs.includes(g.setColor(color).getColor())) color = undefined; // make sure button is not invisible + row.c.push({ + type: "v", pad: 2, width: w, c: [ + { + type: "btn", + src: item.img || (() => getIcon(item.icon || "Unknown")), + col: color, + cb: B2 + ? undefined // We handle B2 touches below + : () => setTimeout(item.cb), // prevent MEMORY error from running cb() inside the Layout touchHandler + }, + {height: 2}, + {type: "txt", label: label, font: fontTiny}, + ] + }); + if (B2) cbs[cbs.length-1].push(item.cb); + if (row.c.length>=cols) { + l.c.push(row); + row = newRow(); + if (B2) cbs.push([]); + } + }); + if (row.c.length) { + if (options.align!==undefined) { + const filler = {width: w*(cols-row.c.length)}; + if (options.align=== -1) row.c.unshift(filler); // left + else if (options.align===1) row.c.push(filler); // right + else if (options.align===0) row.c.splice(row.c.length-1, 0, filler); // left, but final item on right + } + l.c.push(row); + } + layout = new Layout(l, {back: back}); + layout.render(); + + if (B2) { + // override touchHandler: no need to hit buttons exactly, just pick the nearest + if (Bangle.touchHandler) Bangle.removeListener("touch", Bangle.touchHandler); + Bangle.touchHandler = (side, xy) => { + if (xy.y<=Bangle.appRect.y) return; // widgetbar: ignore + let rows = l.c.length, + y = Bangle.appRect.y, h = Bangle.appRect.h; + if (options.title) { + rows--; + y += layout.title.h; + h -= layout.title.h; + } + const r = clamp(Math.floor(rows*(xy.y-y)/h), 0, rows-1); // row (0-indexed) + let c; // column (0-indexed) + if (rcbs[r].length-2) return; // gap before final item + } else { // spread + c = clamp(Math.floor(cbs[r].length*(xy.x-Bangle.appRect.x)/Bangle.appRect.w), 0, cols-1); + } + } + if (r { + setFont(); + showMain(); + }); + }; + const showCall = function() { + setActive("call"); + delete call.new; + Bangle.setLocked(false); + Bangle.setLCDPower(1); + + const w = g.getWidth()-48, + lines = g.setFont(fontNormal).wrapString(call.title, w), + title = (lines.length>2) ? lines.slice(0, 2).join("\n")+"..." : lines.join("\n"); + const respond = function(accept) { + Bangle.buzz(50); + Bangle.messageResponse(call, accept); + remove(call); + call = undefined; + goBack(); + }; + let options = {}; + if (!B2) { + options.btns = [ + { + label:/*LANG*/"accept", + cb: () => respond(true), + }, { + label:/*LANG*/"ignore", + cb: goBack, + }, { + label:/*LANG*/"reject", + cb: () => respond(false), + } + ]; + } + + layout = new Layout({ + type: "v", c: [ + { + type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ + {type: "img", pad: 10, src: () => getIcon("phone"), col: getIconColor("phone")}, + { + type: "v", fillx: 1, c: [ + {type: "txt", font: fontTiny, label: call.src ||/*LANG*/"Incoming Call", bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, pad: 2, halign: 1}, + title ? {type: "txt", font: fontNormal, label: title, bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, pad: 2} : {}, + ] + }, + ] + }, + {type: "txt", font: fontNormal, label: call.body, fillx: 1, filly: 1, pad: 2, wrap: true}, + { + type: "h", fillx: 1, c: [ + // button callbacks won't actually be used: setUI below overrides the touchHandler set by Layout + {type: B2 ? "btn" : "img", src: () => getIcon("Neg"), cb: () => respond(false)}, + {fillx: 1}, + {type: B2 ? "btn" : "img", src: () => getIcon("Pos"), cb: () => respond(true)}, + ] + } + ] + }, options); + layout.render(); + setUI({ + mode: "custom", + back: goBack, + touch: (side, xy) => { + if (B2 && xy.y { + if (B2 || b===2) goBack(); + else if (b===1) respond(true); + else respond(false); + }, + swipe: dir => { + if (dir===RIGHT) showMain(); + }, + }); + }; + const showAlarm = function() { + // dismissing alarms doesn't seem to work, so this is simple */ + setActive("alarm"); + delete alarm.new; + Bangle.setLocked(false); + Bangle.setLCDPower(1); + + const w = g.getWidth()-48, + lines = g.setFont(fontNormal).wrapString(alarm.title, w), + title = (lines.length>2) ? lines.slice(0, 2).join("\n")+"..." : lines.join("\n"); + layout = new Layout({ + type: "v", c: [ + { + type: "h", fillx: 1, bgCol: g.theme.bg2, col: g.theme.fg2, c: [ + alarm.body ? {type: "img", pad: 10, src: () => getIcon("alarm"), col: getIconColor("alarm")} : {}, + {type: "txt", font: fontNormal, label: title ||/*LANG*/"Alarm", bgCol: g.theme.bg2, col: g.theme.fg2, fillx: 1, pad: 2, halign: 1}, + ] + }, + alarm.body + ? {type: "txt", font: fontNormal, label: alarm.body, fillx: 1, filly: 1, pad: 2, wrap: true} + : {type: "img", pad: 10, scale: 3, src: () => getIcon("alarm"), col: getIconColor("alarm")}, + ] + }); + layout.render(); + setUI({ + mode: "custom", + back: goBack, + btn: b => { + if (B2 || b===2) goBack(); + }, + swipe: dir => { + if (dir===RIGHT) showMain(); + }, + }); + }; + /** + * Send message response, and delete it from list + * @param {string|boolean} reply Response text, false to dismiss (true to open on phone) + */ + const respondToMessage = function(reply) { + const msg = MESSAGES[messageNum]; + if (msg) { + Bangle.messageResponse(msg, reply); + if (reply===false) remove(msg); + } + if (MESSAGES.length<1) goBack(); // no more messages + else showMessage((msg && reply===false) ? messageNum : messageNum+1); // show next message + }; + const showMessageActions = function() { + let title = MESSAGES[messageNum].title || ""; + if (g.setFont(fontNormal).stringMetrics(title).width>Bangle.appRect.w-(B2 ? 0 : 20)) { + title = g.wrapString("..."+title, Bangle.appRect.w-(B2 ? 0 : 20))[0].substring(3)+"..."; + } + clearStuff(); + let grid = { + "": { + title: title ||/*LANG*/"Message", + back: () => showMessage(messageNum), + cols: 3, // fit all replies on first row, dismiss on bottom + } + }; + // Text replies don't work (yet) + // grid[/*LANG*/"OK"] = {icon: "Ok", col: "#0f0", cb: () => respondToMessage("\u{1F44D}")}; // "Thumbs up" emoji + // grid[/*LANG*/"Nak"] = {icon: "Nak", col: "#f00", cb: () => respondToMessage("\u{1F44E}")}; // "Thumbs down" emoji + // grid[/*LANG*/"No Phone"] = {icon: "NoPhone", col: "#f0f", cb: () => respondToMessage("\u{1F4F5}")}; // "No Mobile Phones" emoji + + grid[/*LANG*/"Dismiss"] = {icon: "Trash", col: "#ff0", cb: () => respondToMessage(false)}; + showGrid(grid); + }; + /** + * Show message + * + * @param {number} [num=0] Message to show + * @param {boolean} [bottom=false] Scroll message to bottom right away + */ + let buzzing = false, moving = false, switching = false; + let h, fh, offset; + + /** + * draw (sticky) footer + */ + const drawFooter = function() { + // left hint: swipe from left for main menu + g.reset().clearRect(Bangle.appRect.x, Bangle.appRect.y2-fh, Bangle.appRect.x2, Bangle.appRect.y2) + .setFont(fontTiny) + .setFontAlign(-1, 1) // bottom left + .drawString( + "\0"+atob("CAiBACBA/EIiAnwA")+ // back + "\0"+atob("CAiBAEgkEgkSJEgA"), // >> + Bangle.appRect.x+(B2 ? 1 : 28), Bangle.appRect.y2 + ); + // center message count+hints: swipe up/down for next/prev message + const footer = ` ${messageNum+1}/${MESSAGES.length} `, + fw = g.stringWidth(footer); + g.setFontAlign(0, 1); // bottom center + if (B2 && messageNum>0 && offset<=0) + g.drawString("\0"+atob("CAiBAABBIhRJIhQI"), Bangle.appRect.x+Bangle.appRect.w/2-fw/2, Bangle.appRect.y2); // ^ swipe to prev + g.drawString(footer, Bangle.appRect.x+Bangle.appRect.w/2, Bangle.appRect.y2); + if (B2 && messageNum=h-(Bangle.appRect.h-fh)) + g.drawString("\0"+atob("CAiBABAoRJIoRIIA"), Bangle.appRect.x+Bangle.appRect.w/2+fw/2, Bangle.appRect.y2); // v swipe to next + // right hint: swipe from right for message actions + g.setFontAlign(1, 1) // bottom right + .drawString( + "\0"+atob("CAiBABIkSJBIJBIA")+ // << + "\0"+atob("CAiBAP8AAP8AAP8A"), // = ("hamburger menu") + Bangle.appRect.x2-(B2 ? 1 : 28), Bangle.appRect.y2 + ); + }; + const showMessage = function(num, bottom) { + if (num<0) num = 0; + if (!num) num = 0; // no number: show first + if (num>=MESSAGES.length) num = MESSAGES.length-1; + setActive("messages", num); + if (!MESSAGES.length) { + // I /think/ this should never happen... + return E.showPrompt(/*LANG*/"No Messages", { + title:/*LANG*/"Messages", + img: require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")), + buttons: {/*LANG*/"Ok": 1} + }).then(showMain); + } + Bangle.setLocked(false); + Bangle.setLCDPower(1); + // only clear msg.new on user input + const msg = MESSAGES[messageNum]; // message + fh = 10; // footer height + offset = 0; + let oldOffset = 0; + const move = (dy) => { + offset = Math.max(0, Math.min(h-(Bangle.appRect.h-fh), offset+dy)); // clip at message height + dy = oldOffset-offset; // real dy + // move all elements to new offset + const offsetRecurser = function(l) { + if (l.y) l.y += dy; + if (l.c) l.c.forEach(offsetRecurser); + }; + offsetRecurser(layout.l); + oldOffset = offset; + draw(); + }; + const draw = () => { + g.reset() + .clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-fh) + .setClipRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-fh); + g.reset = () => g.setColor(g.theme.fg).setBgColor(g.theme.bg); // stop Layout resetting ClipRect + layout.render(); + if (layout.button && h>Bangle.appRect.h-fh && offset(Bangle.appRect.h-fh)) { + const sbh = (Bangle.appRect.h-fh)/h*(Bangle.appRect.h-fh), // scrollbar height + y1 = Bangle.appRect.y+offset/h*(Bangle.appRect.h-fh), y2 = y1+sbh; + g.setColor(g.theme.bg).drawLine(Bangle.appRect.x2, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-fh); + g.setColor(g.theme.fg).drawLine(Bangle.appRect.x2, y1, Bangle.appRect.x2, y2); + } + drawFooter(); + }; + const buzzOnce = () => { + if (buzzing) return; + buzzing = true; + Bangle.buzz(50).then(() => setTimeout(() => {buzzing = false;}, 500)); + }; + + layout = getMessageLayout(msg); + h = layout.l.h; // message height + if (bottom) move(h); // scrolling backwards: jump to bottom of message + else draw(); + const PAGE_SIZE = Bangle.appRect.h-fh; + const // shared B1/B2 handlers + back = () => { + delete msg.new; // we mark messages as read on any input + goBack(); + }, + swipe = dir => { + delete msg.new; + if (dir===RIGHT) showMain(); + else if (dir===LEFT) showMessageActions(); + }, + touch = (side, xy) => { + delete msg.new; + if (h<=Bangle.appRect.h-fh || offset>=h-(Bangle.appRect.h-fh)) { // already at bottom + // B2: check for button-press + // setUI overrides Layout listeners, so we need to check for button presses ourselves + if (B2 && layout.button) { + const b = layout.button; + // the button is at the bottom of the screen, so we accept touches all the way down + if (xy.x>=b.x && xy.y>=b.y && xy.x<=b.x+b.w /*&& xy.y<=b.y+b.h*/) return b.cb(); + } + if (B2 && xy.yBangle.appRect.h-fh && offset { + delete msg.new; + if (!switching) { + const dy = -e.dy; + if (dy>0) { // up + if (h>Bangle.appRect.h-fh && offset0) { + moving = true; // prevent scrolling right into prev message + move(dy); + } else if (messageNum>0) { // already at top: show prev + if (!moving) { // don't scroll right through to previous message + Bangle.buzz(30); + switching = true; // don't process any more drag events until we lift our finger + showMessage(messageNum-1, true); + } + } else { // already at top of first message + buzzOnce(); + } + } + } + if (!e.b) { + // touch end: we can swipe to another message (if we reached the top/bottom) or move the new message + moving = false; + switching = false; + } + }, + touch: touch, + }); + } else { // Bangle.js 1 + setUI({ + mode: "updown", + back: back, + }, dir => { + delete msg.new; + if (dir===DOWN) { + if (h>Bangle.appRect.h-fh && offset0) { + move(-PAGE_SIZE); + } else if (messageNum>0) { // top reached: show previous + Bangle.buzz(30); + showMessage(messageNum-1, true); + } else { + buzzOnce(); // already at top of first message + } + } else { // button + showMessageActions(); + } + }); + Bangle.swipeHandler = swipe; + Bangle.on("swipe", Bangle.swipeHandler); + Bangle.touchHandler = touch; + Bangle.on("touch", Bangle.touchHandler); + } // Bangle.js 1/2 + }; + /** + * Determine message layout information: size, fonts, and wrapped title/body texts + * + * @param msg + * @returns {{h: number, w: number, + * src: (string), + * title: (string), titleFont: (string), + * body: (string), bodyFont: (string)}} + */ + const getMessageLayoutInfo = function(msg) { + // header: [icon][title] + // [ src] + // + // But: no title? -> use src as title + let w, src = msg.src || "", + title = msg.title || "", + body = msg.body || "", + h = 0, // total height + th = 0, // title height + ih = 46; // icon height: // icon(24) + internal padding(20) + icon<->src spacer(2) + if (!title) { + title = src; + src = ""; + } + + // top bar + if (title) { + w = Bangle.appRect.w-59; // icon(24) + padding:left(5) + padding:btn-txt(5) + internal btn padding(20) + padding:right(5) + title = g.setFont(fontNormal).wrapString(title, w).join("\n"); + th += 2+g.stringMetrics(title).height; // 2px padding + } + if (src) { + w = 59; // icon(24) + padding:left(5) + padding:btn-txt(5) + internal btn padding(20) + padding:right(5) + src = g.setFont(fontTiny).wrapString(src, w).join("\n"); + ih += g.stringMetrics(src).height; + } + + h = Math.max(ih, th); // maximum of icon/title + + // body + w = Bangle.appRect.w-4; // padding(2x2) + body = g.setFont(fontNormal).wrapString(msg.body, w).join("\n"); + h += 4+g.stringMetrics(body).height; // padding(2x2) + + if (settings().button) h += 44; // icon(24) + padding(2x2) + internal btn padding(16) + + w = Bangle.appRect.w; + // always expand to -<(10x)footer> + h = Math.max(h, Bangle.appRect.h-10); + + return { + src: src, + title: title, + body: body, + h: h, + w: w, + }; + }; + + const getMessageLayout = function(msg) { + // Crafted so that on B2, with "medium" font, a message with + // icon + src + 2-line title + 2-line body + button + // fits exactly, i.e. no need for scrolling + const info = getMessageLayoutInfo(msg); + const hCol = msg.new ? g.theme.fgH : g.theme.fg2, + hBg = msg.new ? g.theme.bgH : g.theme.bg2; + + // lie to Layout library about available space + Bangle.appRect = Object.assign({}, Bangle.appRect, + {w: info.w, h: info.h, x2: Bangle.appRect.x+info.w-1, y2: Bangle.appRect.y+info.h-1}); + + // make sure icon is not invisible + let imageCol = getImageColor(msg); + if (g.setColor(imageCol).getColor()==hBg) imageCol = hCol; + + layout = new Layout({ + type: "v", c: [ + { + type: "h", fillx: 1, bgCol: hBg, col: hCol, c: [ + {width: 3}, + { + type: "v", c: [ + {type: "img", /*pad: 2,*/ src: () => getMessageImage(msg), col: imageCol}, + {height: 2}, + info.src ? {type: "txt", font: fontTiny, label: info.src, bgCol: hBg, col: hCol} : {}, + ] + }, + info.title ? {type: "txt", font: fontNormal, label: info.title, bgCol: hBg, col: hCol, fillx: 1, pad: 2} : {}, + {width: 3}, + ] + }, + {type: "txt", font: fontNormal, label: info.body, fillx: 1, filly: 1, pad: 2}, + {filly: 1}, + settings().button ? { + type: "h", c: [ + B2 ? {} : {fillx: 1}, // Bangle.js 1: touching right side = press button + {id: "button", type: "btn", pad: 2, src: () => getIcon("trash"), cb: () => respondToMessage(false)}, + ] + } : {}, + ] + }); + layout.update(); + delete Bangle.appRect; + return layout; + }; + + /** this is a timeout if the app has started and is showing a single message + but the user hasn't seen it (e.g. no user input) - in which case + we should start a timeout for settings().unreadTimeout to return + to the clock. */ + let unreadTimeout; + /** + * Stop auto-unload timeout and buzzing, remove listeners for this function + */ + const clearUnreadStuff = function() { + require("messages").stopBuzz(); + if (unreadTimeout) clearTimeout(unreadTimeout); + unreadTimeout = undefined; + ["touch", "drag", "swipe"].forEach(l => Bangle.removeListener(l, clearUnreadStuff)); + watches.forEach(w => clearWatch(w)); + watches = []; + }; + + let messageNum, // currently visible message + watches = [], // button watches + savedMusic = false; // did we find a stored "music" message when loading? +// special messages + let call, music, map, alarm; + /** + * Find special messages, and remove them from MESSAGES + */ + const findSpecials = function() { + let idx = MESSAGES.findIndex(m => m.id==="call"); + if (idx>=0) call = MESSAGES.splice(idx, 1)[0]; + idx = MESSAGES.findIndex(m => m.id==="music"); + if (idx>=0) { + music = MESSAGES.splice(idx, 1)[0]; + savedMusic = true; + } + idx = MESSAGES.findIndex(m => m.id==="map"); + if (idx>=0) map = MESSAGES.splice(idx, 1)[0]; + idx = MESSAGES.findIndex(m => m.src && m.src.toLowerCase().startsWith("alarm")); + if (idx>=0) alarm = MESSAGES.splice(idx, 1)[0]; + }; + if (MESSAGES!==undefined) { // only if loading MESSAGES worked + g.reset().clear(); + Bangle.loadWidgets(); + require("messages").toggleWidget(false); + Bangle.drawWidgets(); + findSpecials(); // sets global vars for special messages + // any message we asked to show? + const showIdx = MESSAGES.findIndex(m => m.show); + // any new text messages? + const newIdx = MESSAGES.findIndex(m => m.new); + + // figure out why the app was loaded + if (showIdx>=0) show(showIdx); + else if (call && call.new) showCall(); + else if (alarm && alarm.new) showAlarm(); + else if (map && map.new) showMap(); + else if (music && music.new && settings().openMusic) { + if (settings().alwaysShowMusic===undefined) { + // if not explicitly disabled, enable this the first time we see music + let s = settings(); + s.alwaysShowMusic = true; + require("Storage").writeJSON("messages.settings.json", s); + } + showMusic(); + } + // check for new message last: Maybe we already showed it, but timed out before + // if that happened, and we're loading for e.g. music now, we want to show the music screen + else if (newIdx>=0) { + showMessage(newIdx); + // auto-loaded for message(s): auto-close after timeout + let unreadTimeoutSecs = settings().unreadTimeout; + if (unreadTimeoutSecs===undefined) unreadTimeoutSecs = 60; + if (unreadTimeoutSecs) { + unreadTimeout = setTimeout(load, unreadTimeoutSecs*1000); + } + } else if (MESSAGES.length) { // not autoloaded, but we have messages to show + back = "main"; // prevent "back" from loading clock + showMessage(); + } else showMain(); + + // stop buzzing, auto-close timeout on input + ["touch", "drag", "swipe"].forEach(l => Bangle.on(l, clearUnreadStuff)); + (B2 ? [BTN1] : [BTN1, BTN2, BTN3]).forEach(b => watches.push(setWatch(clearUnreadStuff, b, false))); + } +} \ No newline at end of file diff --git a/apps/messagelist/app.png b/apps/messagelist/app.png new file mode 100644 index 0000000000000000000000000000000000000000..6eae4bb9691a39f7239c11fbde094a34558d53e0 GIT binary patch literal 8988 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 G3;rJ{)jY%i literal 0 HcmV?d00001 diff --git a/apps/messagelist/boot.js b/apps/messagelist/boot.js new file mode 100644 index 000000000..994a2cfed --- /dev/null +++ b/apps/messagelist/boot.js @@ -0,0 +1,3 @@ +(function() { + Bangle.on("message", require("messagegui").messageListener); +})(); \ No newline at end of file diff --git a/apps/messagelist/lib.js b/apps/messagelist/lib.js new file mode 100644 index 000000000..33b6d9d69 --- /dev/null +++ b/apps/messagelist/lib.js @@ -0,0 +1,246 @@ +// Handle incoming messages while the app is not loaded +// The messages app overrides Bangle.messageListener +// (placed in separate file, so we don't read this all at boot time) +exports.messageListener = function(type, msg) { + if (msg.handled || (global.__FILE__ && __FILE__.startsWith("messagelist."))) return; // already handled/app open + // clean up, in case previous message didn't load the app after all + if (exports.loadTimeout) clearTimeout(exports.loadTimeout); + delete exports.loadTimeout; + delete exports.buzz; + const quiet = () => (require("Storage").readJSON("setting.json", 1) || {}).quiet; + /** + * Quietly load the app for music/map, if not already loading + */ + function loadQuietly(msg) { + if (exports.loadTimeout) return; // already loading + exports.loadTimeout = setTimeout(function() { + Bangle.load("messagelist.app.js"); + }, 500); + } + function loadNormal(msg) { + if (exports.loadTimeout) clearTimeout(exports.loadTimeout); // restart timeout + exports.loadTimeout = setTimeout(function() { + delete exports.loadTimeout; + // check there are still new messages (for #1362) + let messages = require("messages").getMessages(msg); + (Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, messages)); + if (!messages.some(m => m.new)) return; // don't use `status()`: also load for new music! + // if we're in a clock, or it's important, open app + if (Bangle.CLOCK || msg.important) { + if (exports.buzz) require("messages").buzz(msg.src); + Bangle.load("messagelist.app.js"); + } + }, 500); + } + + /** + * Mark message as handled, and save it for the app + */ + const handled = () => { + if (!Bangle.MESSAGES) Bangle.MESSAGES = []; + require("messages").apply(msg, Bangle.MESSAGES); + if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES; + if (msg.t==="remove") require("messages").save(msg); + else msg.handled = true; + }; + /** + * Write messages to flash after all, when not laoding the app + */ + const saveToFlash = () => { + (Bangle.MESSAGES||[]).forEach(m=>require("messages").save(m)); + delete Bangle.MESSAGES; + } + + switch(type) { + case "music": + if (!Bangle.CLOCK) return; + // only load app if we are playing, and we know which song + if (msg.state!=="play" || !msg.title) return; + if (exports.openMusic===undefined) { + // only read settings for first music message + exports.openMusic = !!(exports.settings().openMusic); + } + if (!exports.openMusic) return; // we don't care about music + if (quiet()) return; + msg.new = true; + handled(); + return loadQuietly(); + + case "map": + handled(); + if (msg.t!=="remove" && Bangle.CLOCK) loadQuietly(); + else saveToFlash(); + return; + + case "text": + handled(); + if (exports.settings().autoOpen===false) return saveToFlash(); + if (quiet()) return saveToFlash(); + if (msg.t!=="add" || !msg.new || !(Bangle.CLOCK || msg.important)) { + // not important enough to load the app + if (msg.t==="add" && msg.new) require("messages").buzz(msg); + return saveToFlash(); + } + if (msg.t==="add" && msg.new) exports.buzz = true; + return loadNormal(msg); + + case "alarm": + if (quiet()<2) return saveToFlash(); + // fall through + case "call": + handled(); + exports.buzz = true; + return loadNormal(msg); + + // case "clearAll": do nothing + } +}; + +exports.settings = function() { + return Object.assign({ + // Interface // + fontSize: 1, + onTap: 0, // [Message menu, Dismiss, Back, Nothing] + button: true, + + // Behaviour // + vibrate: ":", + vibrateCalls: ":", + vibrateAlarms: ":", + repeat: 4, + vibrateTimeout: 60, + unreadTimeout: 60, + autoOpen: true, + + // Music // + openMusic: true, + // no default: alwaysShowMusic (auto-enabled by app when music happens) + showAlbum: true, + musicButtons: false, + + // Widget // + flash: true, + // showRead: false, + + // Utils // + }, + // fall back to default app settings if not set for messagelist + (require("Storage").readJSON("messages.settings.json", true) || {}), + (require("Storage").readJSON("messagelist.settings.json", true) || {})); +}; + +/** + * @param {string} icon Icon name + * @returns string Icon image string, for use with g.drawImage() + */ +exports.getIcon = function(icon) { + // TODO: icons should be 24x24px with 1bpp colors + switch(icon.toLowerCase()) { + // generic icons: + case "alert": + return atob("GBgBAAAAAP8AA//AD8PwHwD4HBg4ODwcODwccDwOcDwOYDwGYDwGYBgGYBgGcBgOcAAOOBgcODwcHDw4Hxj4D8PwA//AAP8AAAAA"); + case "alarm": + case "alarmclockreceiver": + return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); + case "back": // TODO: 22x22 + return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); + case "calendar": + return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + case "mail": // TODO: 28x18 + case "sms message": + case "notification": + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); + case "map": // TODO: 25x25, + return atob("GRmBAAAAAAAAAAAAAAIAYAHx/wH//+D/+fhz75w/P/4f//8P//uH///D///h3f/w4P+4eO/8PHZ+HJ/nDu//g///wH+HwAYAIAAAAAAAAAAAAAA="); + case "menu": + return atob("GBiBAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAA=="); + case "music": // TODO: 22x22 + return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + case "nak": // TODO: 22x25 + return atob("FhmBAA//wH//j//+P//8///7///v//+///7//////////////v//////////z//+D8AAPwAAfgAB+AAD4AAPgAAeAAB4AAHAAA=="); + case "neg": // TODO: 22x22 + return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="); + case "next": + return atob("GBiBAAAAAAAAAAAAAAwAcB8A+B+A+B/g+B/4+B/8+B//+B//+B//+B//+B//+B//+B/8+B/4+B/g+B+A+B8A+AwAcAAAAAAAAAAAAA=="); + case "nophone": // TODO: 30x30 + return atob("Hh6BAAAAAAGAAAAHAAAADgAAABwADwA4Af8AcA/8AOB/+AHH/+ADv/8AB//wAA/HAAAeAAACOAAADHAAAHjgAAPhwAAfg4AAfgcAAfwOAA/wHAA/wDgA/gBwA/gA4AfAAcAfAAOAGAAHAAAADgAAABgAAAAA"); + case "ok": // TODO: 22x25 + return atob("FhmBAAHAAAeAAB4AAPgAA+AAH4AAfgAD8AAPwAD//+//////////////7//////////////v//+///7///v//8///gf/+A//wA=="); + case "pause": + return atob("GBiBAAAAAAAAAAAAAAOBwAfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AOBwAAAAAAAAAAAAA=="); + case "phone": // TODO: 23x23 + case "call": + return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); + case "play": + return atob("GBiBAAAAAAAAAAAAAAcAAA+AAA/gAA/4AA/8AA//AA//wA//4A//8A//8A//4A//wA//AA/8AA/4AA/gAA+AAAcAAAAAAAAAAAAAAA=="); + case "pos": // TODO: 25x20 + return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="); + case "previous": + return atob("GBiBAAAAAAAAAAAAAA4AMB8A+B8B+B8H+B8f+B8/+B//+B//+B//+B//+B//+B//+B8/+B8f+B8H+B8B+B8A+A4AMAAAAAAAAAAAAA=="); + case "settings": // TODO: 20x20 + return atob("FBSBAAAAAA8AAPABzzgf/4H/+A//APnwfw/n4H5+B+fw/g+fAP/wH/+B//gc84APAADwAAAA"); + case "to do": + return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); + case "trash": + return atob("GBiBAAAAAAAAAAB+AA//8A//8AYAYAYAYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAYAYAYAYAf/4AP/wAAAAAAAAA=="); + case "unknown": // TODO: 30x30 + return atob("Hh6BAAAAAAAAAAAAAAAAAAPwAAA/8AAB/+AAD//AAD4fAAHwPgAHwPgAAAPgAAAfAAAA/AAAD+AAAH8AAAHwAAAPgAAAPgAAAPgAAAAAAAAAAAAAAAAAAHAAAAPgAAAPgAAAPgAAAHAAAAAAAAAAAAAAAAAA"); + case "unread": // TODO: 29x24 + return atob("HRiBAAAAH4AAAf4AAB/4AAHz4AAfn4AA/Pz/5+fj/z8/j/n5/j/P//j/Pn3j+PPPx+P8fx+Pw/x+AF/B4A78RiP3xwOPvHw+Pcf/+Ox//+NH//+If//+B///+A=="); + default: //should never happen + return exports.getIcon("unknown"); + } +}; +/** + * @param {string} icon Icon + * @returns {string} Color to use with g.setColor() + */ +exports.getColor = function(icon) { + switch(icon.toLowerCase()) { + // generic colors, using B2-safe colors + case "alert": + return "#ff0"; + case "alarm": + return "#fff"; + case "calendar": + return "#f00"; + case "mail": + return "#ff0"; + case "map": + return "#f0f"; + case "music": + return "#f0f"; + case "neg": + return "#f00"; + case "notification": + return "#0ff"; + case "phone": + case "call": + return "#0f0"; + case "settings": + return "#000"; + case "sms message": + return "#0ff"; + case "trash": + return "#f00"; + case "unknown": + return g.theme.fg; + case "unread": + return "#ff0"; + default: + return g.theme.fg; + } +}; + +/** + * Launch GUI app with given message + * @param {object} msg + */ +exports.open = function(msg) { + if (msg && msg.id && !msg.show) { + // store which message to load + msg.show = 1; + } + + Bangle.load((msg && msg.new && msg.id!=="music") ? "messagelist.new.js" : "messagelist.app.js"); +}; diff --git a/apps/messagelist/metadata.json b/apps/messagelist/metadata.json new file mode 100644 index 000000000..7947e2db4 --- /dev/null +++ b/apps/messagelist/metadata.json @@ -0,0 +1,28 @@ +{ + "id": "messagelist", + "name": "Message List", + "version": "0.01", + "description": "Display notifications from iOS and Gadgetbridge/Android as a list", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "screenshots": [ + {"url": "screenshot0.png"}, + {"url": "screenshot1.png"}, + {"url": "screenshot2.png"}, + {"url": "screenshot3.png"} + ], + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module" }, + "provides_modules": ["messagegui"], + "readme": "README.md", + "storage": [ + {"name":"messagelist.boot.js","url":"boot.js"}, + {"name":"messagegui","url":"lib.js"}, + {"name":"messagelist.app.js","url":"app.js"}, + {"name":"messagelist.settings.js","url":"settings.js"}, + {"name":"messagelist.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"messagelist.settings.json"}], + "sortorder": -9 +} diff --git a/apps/messagelist/screenshot0.png b/apps/messagelist/screenshot0.png new file mode 100644 index 0000000000000000000000000000000000000000..b6f37c05339a18e22f99416b654884a8730e623e GIT binary patch literal 19719 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 literal 0 HcmV?d00001 diff --git a/apps/messagelist/screenshot1.png b/apps/messagelist/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d4db9fa1e08faa61ae66dd8eb4ed9708d0dada GIT binary patch literal 20953 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 oTU`-?hP3%$;(j`?Rm^sEqj;q6)Fvoz!`Iz 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 updateSetting(setting, v) + }; + } + + function showIfMenu() { + const tapOptions = [/*LANG*/"Message menu",/*LANG*/"Dismiss",/*LANG*/"Back",/*LANG*/"Nothing"]; + E.showMenu({ + "": {"title": /*LANG*/"Interface"}, + "< Back": () => showMainMenu(), + /*LANG*/"Font size": { + value: 0|settings.fontSize, + min: 0, max: 2, + format: v => [/*LANG*/"Small",/*LANG*/"Medium",/*LANG*/"Large",/*LANG*/"Huge"][v], + onchange: v => updateSetting("fontSize", v) + }, + /*LANG*/"On Tap": { + value: settings.onTap, + min: 0, max: tapOptions.length-1, wrap: true, + format: v => tapOptions[v], + onchange: v => updateSetting("onTap", v) + }, + /*LANG*/"Dismiss button": toggler("button"), + }); + } + + function showBMenu() { + E.showMenu({ + "": {"title": /*LANG*/"Behaviour"}, + "< Back": () => showMainMenu(), + /*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => updateSetting("vibrate", v)), + /*LANG*/"Vibrate for calls": require("buzz_menu").pattern(settings.vibrateCalls, v => updateSetting("vibrateCalls", v)), + /*LANG*/"Vibrate for alarms": require("buzz_menu").pattern(settings.vibrateAlarms, v => updateSetting("vibrateAlarms", v)), + /*LANG*/"Repeat": { + value: settings.repeat, + min: 0, max: 10, + format: v => v ? v+"s" :/*LANG*/"Off", + onchange: v => updateSetting("repeat", v) + }, + /*LANG*/"Vibrate timer": { + value: settings.vibrateTimeout, + min: 0, max: 240, step: 10, + format: v => v ? v+"s" :/*LANG*/"Forever", + onchange: v => updateSetting("vibrateTimeout", v) + }, + /*LANG*/"Unread timer": { + value: settings.unreadTimeout, + min: 0, max: 240, step: 10, + format: v => v ? v+"s" :/*LANG*/"Off", + onchange: v => updateSetting("unreadTimeout", v) + }, + /*LANG*/"Auto-open": toggler("autoOpen"), + }); + } + + function showMusicMenu() { + E.showMenu({ + "": {"title": /*LANG*/"Music"}, + "< Back": () => showMainMenu(), + /*LANG*/"Auto-open": toggler("openMusic"), + /*LANG*/"Always visible": toggler("alwaysShowMusic"), + /*LANG*/"Buttons": toggler("musicButtons"), + /*LANG*/"Show album": toggler("showAlbum"), + }); + } + + function showWidMenu() { + E.showMenu({ + "": {"title": /*LANG*/"Widget"}, + "< Back": () => showMainMenu(), + /*LANG*/"Flash icon": toggler("flash"), + // /*LANG*/"Show Read": toggler("showRead"), + }); + } + + function showUtilsMenu() { + let m = E.showMenu({ + "": {"title": /*LANG*/"Utilities"}, + "< Back": () => showMainMenu(), + /*LANG*/"Delete all": () => { + E.showPrompt(/*LANG*/"Are you sure?", + {title:/*LANG*/"Delete All Messages"}) + .then(isYes => { + if (isYes) require("messages").write([]); + showUtilsMenu(); + }); + } + }); + } + + function showMainMenu() { + E.showMenu({ + "": {"title": inApp ?/*LANG*/"Settings" :/*LANG*/"Messages"}, + "< Back": back, + /*LANG*/"Interface": () => showIfMenu(), + /*LANG*/"Behaviour": () => showBMenu(), + /*LANG*/"Music": () => showMusicMenu(), + /*LANG*/"Widget": () => showWidMenu(), + /*LANG*/"Utils": () => showUtilsMenu(), + }); + } + + showMainMenu(); +}); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js old mode 100644 new mode 100755 index 838f99895..82b2896b8 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -94,6 +94,7 @@ const INTERNAL_FILES_IN_APP_TYPE = { // list of app types and files they SHOULD var KNOWN_WARNINGS = [ "App gpsrec data file wildcard .gpsrc? does not include app ID", "App owmweather data file weather.json is also listed as data file for app weather", + "App messagegui storage file messagegui is also listed as storage file for app messagelist", ]; function globToRegex(pattern) { From c0b7ccf94950db883b91ba7ec0bbcd2de0eab20a Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Wed, 14 Dec 2022 20:51:33 +0100 Subject: [PATCH 32/82] messagelist: no fast unloading Disabled because it is leaking memory (and we don't know why) --- apps/messagelist/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/messagelist/app.js b/apps/messagelist/app.js index c10ac726d..ebd5d4217 100644 --- a/apps/messagelist/app.js +++ b/apps/messagelist/app.js @@ -63,6 +63,7 @@ clearUnreadStuff(); delete Bangle.appRect; }; + const quitApp = () => load(); // TODO: revert to Bangle.showClock after fixing memory leaks try { MESSAGES = require("messages").getMessages(); // Apply fast loaded messages @@ -167,12 +168,12 @@ // auto-close after being paused if (music.state!=="play") musicTimeout = setTimeout(function() { musicTimeout = undefined; - if (active==="music" && (!music || music.state!=="play")) Bangle.showClock(); + if (active==="music" && (!music || music.state!=="play")) quitApp(); }, 60*1000); // paused for 1 minute // auto-close after "playing" way beyond song duration (because "stop" messages don't seem to exist) else musicTimeout = setTimeout(function() { musicTimeout = undefined; - if (active==="music" && (!music || music.state==="play")) Bangle.showClock(); + if (active==="music" && (!music || music.state==="play")) quitApp(); }, 2*Math.max(music.dur || 0, 5*60)*1000); // playing: assume ended after twice song duration, or at least 10 minutes if (active==="music") showMusic(); // update music screen @@ -460,7 +461,7 @@ else if (back==="music" && music) showMusic(); else if (back==="messages" && MESSAGES.length) showMessage(); else if (back) showMain(); // previous screen was "main", or no longer valid - else Bangle.showClock(); // no previous screen: go back to clock + else quitApp(); // no previous screen: go back to clock }; /** * Leave screen, and make sure goBack() won't take us there anymore; From 9a44643d0100a20147b78611eb0f20a09178337f Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Wed, 14 Dec 2022 22:14:01 +0100 Subject: [PATCH 33/82] kbswipe: Input of symbols. --- apps/kbswipe/ChangeLog | 1 + apps/kbswipe/README.md | 2 +- apps/kbswipe/lib.js | 63 +++++++++++++++++++++++++++++++++----- apps/kbswipe/metadata.json | 2 +- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index 568db372e..a7b2d44c2 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -4,3 +4,4 @@ 0.04: Fix issue if going back without typing. 0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat. 0.06: Support input of numbers and uppercase characters. +0.07: Support input of symbols. diff --git a/apps/kbswipe/README.md b/apps/kbswipe/README.md index c4c3fbd99..105d7cd9b 100644 --- a/apps/kbswipe/README.md +++ b/apps/kbswipe/README.md @@ -4,7 +4,7 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty To get a legend of available characters, just tap the screen. -To switch between the input of alphabetic and numeric characters tap the widget which displays either "123" or "ABC". +To switch between the input of alphabetic, numeric and symbol characters tap the widget which displays either "123", "ABC" or "?:$". To switch between lowercase and uppercase characters do an up swipe. diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 75a434999..cd17d42f4 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -1,5 +1,6 @@ exports.INPUT_MODE_ALPHA = 0; exports.INPUT_MODE_NUM = 1; +exports.INPUT_MODE_SYM = 2; /* To make your own strokes, type: @@ -47,9 +48,42 @@ exports.getStrokes = function(mode, cb) { cb("7", new Uint8Array([47, 38, 48, 38, 53, 38, 66, 38, 85, 38, 103, 38, 117, 38, 125, 38, 129, 38, 134, 41, 135, 47, 135, 54, 135, 66, 131, 93, 124, 126, 116, 149, 109, 161, 105, 168])); cb("8", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85])); cb("9", new Uint8Array([122, 58, 117, 55, 112, 51, 104, 51, 95, 51, 86, 51, 77, 51, 68, 51, 60, 51, 54, 56, 47, 64, 46, 77, 46, 89, 46, 96, 51, 103, 64, 109, 74, 110, 83, 110, 94, 107, 106, 102, 116, 94, 124, 84, 127, 79, 128, 78, 128, 94, 128, 123, 128, 161, 128, 175])); + } else if (mode === exports.INPUT_MODE_SYM) { + cb("?", new Uint8Array([36, 69, 39, 68, 44, 65, 52, 60, 61, 56, 70, 51, 78, 47, 87, 46, 96, 46, 108, 46, 121, 49, 128, 56, 129, 63, 126, 76, 119, 91, 108, 105, 103, 114, 98, 118, 93, 124, 91, 131, 91, 143, 91, 155, 91, 163])); + cb(".", new Uint8Array([105, 158, 97, 157, 80, 150, 60, 140, 44, 127, 34, 110, 31, 97, 31, 84, 35, 74, 48, 59, 78, 55, 115, 57, 145, 70, 159, 89, 162, 112, 160, 138, 153, 153, 144, 164, 125, 170, 103, 171])); + cb(",", new Uint8Array([140, 44, 139, 45, 138, 46, 137, 47, 135, 49, 132, 51, 127, 55, 123, 58, 117, 62, 112, 67, 105, 71, 100, 77, 93, 82, 86, 86, 80, 91, 74, 96, 69, 101, 64, 105, 60, 108, 57, 112, 53, 115, 51, 117, 49, 119, 48, 121, 47, 122, 46, 122, 46, 123])); + cb("'", new Uint8Array([100, 50, 100, 160])); + cb("`", new Uint8Array([111, 170, 110, 168, 107, 163, 105, 154, 100, 145, 93, 133, 82, 120, 72, 107, 63, 96, 55, 86, 49, 79, 45, 74, 40, 70, 38, 67, 36, 65, 35, 63, 37, 64, 42, 68, 55, 78, 71, 90, 89, 106, 107, 122, 121, 136, 130, 145, 137, 151, 141, 156])); + cb("-", new Uint8Array([34, 63, 36, 63, 40, 63, 46, 63, 54, 63, 63, 63, 72, 63, 82, 63, 92, 63, 103, 63, 113, 63, 124, 63, 132, 63, 139, 63, 143, 63, 145, 63, 147, 63, 149, 63, 152, 63])); + cb("_", new Uint8Array([34, 84, 36, 84, 40, 84, 47, 84, 56, 84, 67, 84, 81, 84, 95, 84, 108, 84, 120, 84, 131, 84, 139, 84, 146, 84, 149, 84, 151, 84, 154, 84, 155, 83, 154, 81, 150, 78, 143, 74, 130, 71, 111, 68, 90, 65, 73, 64, 60, 64, 51, 64, 46, 64])); + cb("\"", new Uint8Array([24, 168, 24, 158, 28, 132, 33, 102, 37, 82, 41, 66, 46, 54, 50, 47, 54, 46, 60, 49, 67, 64, 73, 88, 80, 114, 87, 138, 95, 149, 109, 145, 123, 128, 130, 108, 135, 87, 136, 70, 136, 57, 136, 50])); + cb(":", new Uint8Array([24, 62, 24, 63, 24, 68, 26, 73, 27, 80, 29, 88, 31, 94, 33, 101, 35, 108, 37, 114, 39, 121, 39, 127, 39, 131, 39, 134, 39, 135, 39, 133, 39, 130, 41, 125, 45, 114, 48, 100, 51, 89, 52, 81, 52, 74, 52, 70, 52, 67, 52, 63, 52, 60, 52, 57])); + cb(";", new Uint8Array([142, 58, 139, 59, 136, 61, 131, 65, 124, 71, 116, 79, 105, 87, 94, 98, 82, 109, 70, 121, 58, 132, 49, 141, 40, 149, 33, 156, 28, 160, 24, 164, 23, 166, 22, 164, 25, 156, 33, 138, 47, 111, 66, 81, 82, 58, 95, 41, 103, 30])); + cb("(", new Uint8Array([72, 51, 70, 51, 68, 51, 66, 54, 63, 56, 61, 59, 58, 61, 56, 65, 54, 70, 51, 74, 49, 79, 47, 83, 45, 87, 44, 92, 44, 94, 44, 96, 44, 99, 44, 101, 44, 104, 44, 107, 44, 114, 44, 120, 46, 127, 49, 135, 52, 141, 56, 145])); + cb(")", new Uint8Array([18, 42, 21, 43, 24, 45, 28, 47, 32, 50, 37, 53, 40, 58, 44, 62, 46, 69, 48, 76, 50, 81, 52, 85, 53, 90, 53, 94, 53, 98, 53, 103, 53, 106, 53, 111, 53, 119, 53, 129, 52, 137, 50, 142, 47, 146])); + cb("[", new Uint8Array([121, 138, 118, 143, 114, 146, 110, 149, 105, 152, 98, 152, 91, 152, 83, 152, 77, 152, 67, 151, 59, 146, 52, 138, 47, 131, 47, 124, 48, 118, 57, 115, 64, 115, 67, 113, 64, 106, 59, 95, 53, 85, 48, 80, 47, 74, 47, 64, 53, 57, 65, 56, 83, 56, 99, 61])); + cb("]", new Uint8Array([36, 136, 42, 140, 54, 145, 70, 149, 84, 151, 98, 149, 109, 143, 113, 135, 113, 127, 104, 115, 87, 105, 75, 103, 76, 98, 87, 84, 96, 67, 100, 54, 97, 48, 90, 45, 76, 45, 60, 47, 44, 52])); + cb("<", new Uint8Array([154, 122, 151, 122, 149, 121, 147, 118, 144, 116, 139, 114, 133, 112, 126, 110, 118, 107, 108, 105, 97, 102, 86, 97, 75, 93, 64, 90, 56, 88, 49, 85, 46, 84, 41, 82, 40, 80, 47, 76, 63, 69, 86, 59, 106, 50, 121, 44, 128, 40])); + cb(">", new Uint8Array([28, 115, 31, 115, 38, 113, 48, 110, 57, 107, 68, 103, 79, 98, 90, 94, 98, 92, 104, 90, 111, 88, 117, 85, 122, 83, 125, 81, 127, 80, 129, 80, 132, 80, 130, 78, 126, 75, 120, 72, 110, 69, 98, 66, 85, 63, 72, 60, 59, 57, 45, 53, 36, 49, 30, 46])); + cb("@", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58])); + cb("#", new Uint8Array([23, 70, 23, 76, 26, 85, 30, 97, 36, 112, 40, 129, 45, 142, 49, 152, 53, 158, 59, 161, 67, 155, 78, 130, 84, 98, 88, 76, 90, 68, 96, 62, 102, 61, 108, 61, 119, 67, 126, 80, 131, 101, 135, 129, 136, 152])); + cb("$", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158])); + cb("%", new Uint8Array([31, 39, 39, 54, 51, 78, 60, 97, 62, 107, 59, 118, 47, 118, 44, 109, 46, 92, 56, 73, 69, 62, 92, 61, 115, 70, 125, 90, 126, 110, 125, 122, 118, 127, 111, 127, 105, 124, 105, 115, 105, 97, 109, 75, 117, 56, 124, 45])); + cb("^", new Uint8Array([28, 175, 28, 168, 33, 156, 37, 142, 41, 128, 46, 111, 51, 95, 58, 82, 62, 75, 68, 68, 74, 57, 81, 49, 88, 44, 93, 44, 102, 56, 113, 79, 118, 95, 123, 110, 131, 130, 135, 146, 136, 158])); + cb("&", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85])); + cb("*", new Uint8Array([35, 61, 41, 62, 53, 68, 72, 78, 91, 91, 103, 99, 113, 103, 119, 106, 124, 107, 131, 107, 139, 107, 150, 107, 161, 104, 166, 97, 166, 89, 165, 78, 162, 70, 158, 61, 151, 54, 144, 51, 132, 51, 115, 57, 98, 66, 82, 78, 65, 89, 52, 100, 44, 109])); + cb("!", new Uint8Array([100, 160, 100, 50])); + cb("~", new Uint8Array([133, 40, 133, 48, 133, 65, 133, 87, 133, 105, 132, 116, 128, 125, 124, 133, 120, 140, 114, 146, 107, 148, 101, 147, 91, 139, 82, 126, 74, 108, 70, 91, 70, 82, 70, 75, 70, 65, 68, 57, 62, 51, 57, 50, 49, 57, 41, 76, 36, 96, 33, 114, 33, 132])); + cb("+", new Uint8Array([151, 41, 146, 46, 133, 55, 116, 71, 101, 87, 87, 98, 74, 105, 63, 109, 54, 110, 43, 106, 36, 94, 36, 80, 36, 68, 42, 60, 60, 58, 91, 64, 115, 77, 129, 88, 139, 99, 144, 106])); + cb("=", new Uint8Array([34, 46, 47, 46, 70, 46, 87, 46, 96, 46, 101, 46, 104, 46, 102, 50, 96, 58, 80, 78, 62, 100, 49, 117, 40, 127, 43, 132, 61, 132, 84, 132, 99, 132])); + cb("\\", new Uint8Array([25, 38, 26, 40, 30, 43, 35, 48, 43, 54, 54, 63, 65, 74, 76, 85, 87, 96, 98, 108, 108, 121, 116, 131, 123, 138, 127, 144, 131, 148, 134, 152, 136, 155])); + cb("|", new Uint8Array([66, 146, 66, 144, 66, 140, 66, 134, 66, 125, 66, 114, 66, 102, 66, 92, 66, 83, 66, 77, 66, 71, 66, 67, 66, 62, 66, 58, 66, 53, 66, 49, 66, 48, 66, 46, 64, 42, 61, 41, 58, 42, 54, 47, 51, 55, 46, 67, 40, 81, 37, 93, 34, 102, 30, 109, 28, 116])); + cb("/", new Uint8Array([24, 173, 26, 171, 30, 166, 36, 158, 44, 148, 53, 137, 63, 126, 73, 115, 82, 104, 91, 95, 99, 87, 105, 80, 112, 74, 117, 70, 122, 65, 125, 61, 127, 60, 129, 57, 133, 53, 136, 50, 137, 47])); + } + if (mode === exports.INPUT_MODE_ALPHA || mode === exports.INPUT_MODE_NUM) { + cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); + cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); } - cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); - cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); }; exports.input = function(options) { @@ -129,14 +163,25 @@ exports.input = function(options) { flashInterval = undefined; g.reset(); - g.clearRect(R).setColor("#f00"); - var n=0; + g.setFont("6x8"); + g.clearRect(R); + let n=0; exports.getStrokes(input_mode, (id,s) => { - var x = n%6; - var y = (n-x)/6; + let x = n%6; + let y = (n-x)/6; s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4}); g.fillRect(s[0]-1,s[1]-2,s[0]+1,s[1]+1); - g.drawPoly(s); + g.setColor("#f00").drawPoly(s); + switch(id) { + case 'SHIFT': + g.setBgColor(0).setColor("#00f").drawImage(atob("CgqBAfP4fh8D4fh+H4fh+HA="), R.x+x*30+20, R.y+y*30+20); + break; + case '\b': + case ' ': + break; + default: + g.setColor("#00f").drawString(shift ? id.toUpperCase() : id, R.x+x*30+20, R.y+y*30+20); + } n++; }); } @@ -177,7 +222,7 @@ exports.input = function(options) { // Switches between alphabetic and numeric input function cycleInput() { input_mode++; - if (input_mode > exports.INPUT_MODE_NUM) input_mode = 0; + if (input_mode > exports.INPUT_MODE_SYM) input_mode = 0; shift = false; setupStrokes(); show(); @@ -201,6 +246,8 @@ exports.input = function(options) { g.drawString(shift ? "ABC" : "abc", this.x, this.y); } else if (input_mode === exports.INPUT_MODE_NUM) { g.drawString("123", this.x, this.y); + } else if (input_mode === exports.INPUT_MODE_SYM) { + g.drawString("?:$", this.x, this.y); } } }; diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 5e290e1dd..6b597a371 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.06", + "version":"0.07", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", From 6427068d722323371c78b1cf07c1662392badd2d Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Wed, 14 Dec 2022 23:03:33 +0100 Subject: [PATCH 34/82] widmessages: Fix messages not showing if UI auto-open is disabled Fixes #2390 --- apps/widmessages/ChangeLog | 1 + apps/widmessages/metadata.json | 2 +- apps/widmessages/widget.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog index 598068920..709fb19c3 100644 --- a/apps/widmessages/ChangeLog +++ b/apps/widmessages/ChangeLog @@ -1,3 +1,4 @@ 0.01: Moved messages widget into standalone widget app 0.02: Fix 'srcs' being defined in global scope Remove library stub +0.03: Fix messages not showing if UI auto-open is disabled \ No newline at end of file diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json index a8a23df19..47a1151da 100644 --- a/apps/widmessages/metadata.json +++ b/apps/widmessages/metadata.json @@ -1,7 +1,7 @@ { "id": "widmessages", "name": "Message Widget", - "version": "0.02", + "version": "0.03", "description": "Widget showing new messages", "icon": "app.png", "type": "widget", diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js index 316957e29..bbf980fd3 100644 --- a/apps/widmessages/widget.js +++ b/apps/widmessages/widget.js @@ -72,6 +72,6 @@ } }; - Bangle.on("message", WIDGETS["messages"].onMsg); + Bangle.on("message", WIDGETS["messages"].onMsg.bind(WIDGETS["messages"])); WIDGETS["messages"].onMsg("init", {}); // abuse type="init" to prevent Bangle.drawWidgets(); })(); From e9c83cbaa818cfa6858639bd9dbe0dde919f582c Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 15 Dec 2022 07:02:59 +0100 Subject: [PATCH 35/82] kbswipe - Add return to alpha - Enable backspace in symbols --- apps/kbswipe/lib.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index cd17d42f4..64984bc67 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -80,9 +80,10 @@ exports.getStrokes = function(mode, cb) { cb("|", new Uint8Array([66, 146, 66, 144, 66, 140, 66, 134, 66, 125, 66, 114, 66, 102, 66, 92, 66, 83, 66, 77, 66, 71, 66, 67, 66, 62, 66, 58, 66, 53, 66, 49, 66, 48, 66, 46, 64, 42, 61, 41, 58, 42, 54, 47, 51, 55, 46, 67, 40, 81, 37, 93, 34, 102, 30, 109, 28, 116])); cb("/", new Uint8Array([24, 173, 26, 171, 30, 166, 36, 158, 44, 148, 53, 137, 63, 126, 73, 115, 82, 104, 91, 95, 99, 87, 105, 80, 112, 74, 117, 70, 122, 65, 125, 61, 127, 60, 129, 57, 133, 53, 136, 50, 137, 47])); } + cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); if (mode === exports.INPUT_MODE_ALPHA || mode === exports.INPUT_MODE_NUM) { - cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); + cb("\n", new Uint8Array([140, 44, 139, 45, 138, 46, 137, 47, 135, 49, 132, 51, 127, 55, 123, 58, 117, 62, 112, 67, 105, 71, 100, 77, 93, 82, 86, 86, 80, 91, 74, 96, 69, 101, 64, 105, 60, 108, 57, 112, 53, 115, 51, 117, 49, 119, 48, 121, 47, 122, 46, 122, 46, 123])); } }; @@ -177,6 +178,7 @@ exports.input = function(options) { g.setBgColor(0).setColor("#00f").drawImage(atob("CgqBAfP4fh8D4fh+H4fh+HA="), R.x+x*30+20, R.y+y*30+20); break; case '\b': + case '\n': case ' ': break; default: From 147283dc743fc5c901ac16aa61b519b717480144 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 15 Dec 2022 07:24:25 +0100 Subject: [PATCH 36/82] kbswipe: remove backtick from symbols --- apps/kbswipe/lib.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 64984bc67..61ac3d240 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -53,7 +53,6 @@ exports.getStrokes = function(mode, cb) { cb(".", new Uint8Array([105, 158, 97, 157, 80, 150, 60, 140, 44, 127, 34, 110, 31, 97, 31, 84, 35, 74, 48, 59, 78, 55, 115, 57, 145, 70, 159, 89, 162, 112, 160, 138, 153, 153, 144, 164, 125, 170, 103, 171])); cb(",", new Uint8Array([140, 44, 139, 45, 138, 46, 137, 47, 135, 49, 132, 51, 127, 55, 123, 58, 117, 62, 112, 67, 105, 71, 100, 77, 93, 82, 86, 86, 80, 91, 74, 96, 69, 101, 64, 105, 60, 108, 57, 112, 53, 115, 51, 117, 49, 119, 48, 121, 47, 122, 46, 122, 46, 123])); cb("'", new Uint8Array([100, 50, 100, 160])); - cb("`", new Uint8Array([111, 170, 110, 168, 107, 163, 105, 154, 100, 145, 93, 133, 82, 120, 72, 107, 63, 96, 55, 86, 49, 79, 45, 74, 40, 70, 38, 67, 36, 65, 35, 63, 37, 64, 42, 68, 55, 78, 71, 90, 89, 106, 107, 122, 121, 136, 130, 145, 137, 151, 141, 156])); cb("-", new Uint8Array([34, 63, 36, 63, 40, 63, 46, 63, 54, 63, 63, 63, 72, 63, 82, 63, 92, 63, 103, 63, 113, 63, 124, 63, 132, 63, 139, 63, 143, 63, 145, 63, 147, 63, 149, 63, 152, 63])); cb("_", new Uint8Array([34, 84, 36, 84, 40, 84, 47, 84, 56, 84, 67, 84, 81, 84, 95, 84, 108, 84, 120, 84, 131, 84, 139, 84, 146, 84, 149, 84, 151, 84, 154, 84, 155, 83, 154, 81, 150, 78, 143, 74, 130, 71, 111, 68, 90, 65, 73, 64, 60, 64, 51, 64, 46, 64])); cb("\"", new Uint8Array([24, 168, 24, 158, 28, 132, 33, 102, 37, 82, 41, 66, 46, 54, 50, 47, 54, 46, 60, 49, 67, 64, 73, 88, 80, 114, 87, 138, 95, 149, 109, 145, 123, 128, 130, 108, 135, 87, 136, 70, 136, 57, 136, 50])); From 34aeae7449df6dbfcef613859dd0f54531ee9597 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 15 Dec 2022 07:38:44 +0100 Subject: [PATCH 37/82] kbswipe: remove return --- apps/kbswipe/lib.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 61ac3d240..ea6d78255 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -82,7 +82,6 @@ exports.getStrokes = function(mode, cb) { cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); if (mode === exports.INPUT_MODE_ALPHA || mode === exports.INPUT_MODE_NUM) { cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); - cb("\n", new Uint8Array([140, 44, 139, 45, 138, 46, 137, 47, 135, 49, 132, 51, 127, 55, 123, 58, 117, 62, 112, 67, 105, 71, 100, 77, 93, 82, 86, 86, 80, 91, 74, 96, 69, 101, 64, 105, 60, 108, 57, 112, 53, 115, 51, 117, 49, 119, 48, 121, 47, 122, 46, 122, 46, 123])); } }; From a567016172b678e6e2a7638b8e74be9bb853f291 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 15 Dec 2022 09:19:10 +0000 Subject: [PATCH 38/82] iOS Making ANCS message receive more resilient (#2402) --- apps/ios/ChangeLog | 1 + apps/ios/boot.js | 12 ++++++++++-- apps/ios/metadata.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 5763801f5..8ab99b4db 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -10,3 +10,4 @@ 0.10: Added more bundleIds 0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language 0.12: Use new message library +0.13: Making ANCS message receive more resilient (#2402) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index ee19ca14b..8952a047e 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -26,7 +26,7 @@ E.on('ANCS',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 => { + NRF.ancsGetNotificationInfo( msg.uid ).then( info => { // success if(msg.preExisting === true){ info.new = false; @@ -38,6 +38,10 @@ E.on('ANCS',msg=>{ Bangle.ancsMessageQueue.shift(); if (Bangle.ancsMessageQueue.length) ancsHandler(); + }, err=>{ // failure + console.log("ancsGetNotificationInfo failed",err); + if (Bangle.ancsMessageQueue.length) + ancsHandler(); }); } Bangle.ancsMessageQueue.push(msg); @@ -196,7 +200,11 @@ Bangle.messageResponse = (msg,response) => { // error/warn here? }; // remove all messages on disconnect -NRF.on("disconnect", () => require("messages").clearAll()); +NRF.on("disconnect", () => { + require("messages").clearAll(); + // Remove any messages from the ANCS queue + Bangle.ancsMessageQueue = []; +}); /* // For testing... diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json index 5042ff1c0..42e0060d0 100644 --- a/apps/ios/metadata.json +++ b/apps/ios/metadata.json @@ -1,7 +1,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.12", + "version": "0.13", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", From 1c7eb92ad5e2672154565b6ac0c487f50aa0a366 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 15 Dec 2022 10:34:40 +0000 Subject: [PATCH 39/82] messages Remove '.show' field, tidyup and fix .open if fast load not enabled --- apps/messagegui/ChangeLog | 1 + apps/messagegui/app.js | 31 ++++++++++++++----------------- apps/messagegui/boot.js | 4 +--- apps/messagegui/lib.js | 27 +++++++++++++++++++-------- apps/messagegui/metadata.json | 2 +- apps/messages/lib.js | 2 +- apps/messagesmusic/ChangeLog | 3 ++- apps/messagesmusic/app.js | 3 ++- apps/messagesmusic/metadata.json | 3 ++- 9 files changed, 43 insertions(+), 33 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 834d703ce..46634985a 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -84,3 +84,4 @@ 0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373) 0.60: Fix saving of removal messages if UI not open 0.61: Fix regression where loading into messages app stops back from working (#2398) +0.62: Remove '.show' field, tidyup and fix .open if fast load not enabled diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 9f8a20219..b158310a1 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -72,10 +72,7 @@ var onMessagesModified = function(type,msg) { Bangle.on("message", onMessagesModified); function saveMessages() { - require("messages").write(MESSAGES.map(m => { - delete m.show; - return m; - })); + require("messages").write(MESSAGES); } E.on("kill", saveMessages); @@ -116,11 +113,12 @@ function showMapMessage(msg) { Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back } -let updateLabelsInterval, - music = {artist: "", album: "", title: ""}; // defaults, so e.g. msg.title.length doesn't error +let updateLabelsInterval; + function showMusicMessage(msg) { active = "music"; - msg = Object.assign(music, msg); // combine+remember "musicinfo" and "musicstate" messages + // defaults, so e.g. msg.xyz.length doesn't error. `msg` should contain up to date info + msg = Object.assign({artist: "", album: "", track: "Music"}, msg); openMusic = msg.state=="play"; var trackScrollOffset = 0; var artistScrollOffset = 0; @@ -349,6 +347,7 @@ function showMessage(msgid) { clockIfNoMsg : bool clockIfAllRead : bool showMsgIfUnread : bool + openMusic : bool // open music if it's playing } */ function checkMessages(options) { @@ -364,12 +363,8 @@ function checkMessages(options) { } // we have >0 messages var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music"); - var toShow = MESSAGES.find(m=>m.show); - if (toShow) { - newMessages.unshift(toShow); - } // If we have a new message, show it - if ((toShow||options.showMsgIfUnread) && newMessages.length) { + if (options.showMsgIfUnread && newMessages.length) { delete newMessages[0].show; // stop us getting stuck here if we're called a second time showMessage(newMessages[0].id); // buzz after showMessage, so being busy during layout doesn't affect the buzz pattern @@ -382,8 +377,8 @@ function checkMessages(options) { } return; } - // no new messages: show playing music? (only if we have playing music to show) - if (options.openMusic && MESSAGES.some(m=>m.id=="music" && m.track && m.state=="play")) + // no new messages: show playing music? Only if we have playing music, or state=="show" (set by messagesmusic) + if (options.openMusic && MESSAGES.some(m=>m.id=="music" && ((m.track && m.state=="play") || m.state=="show"))) return showMessage('music'); // no new messages - go to clock? if (options.clockIfAllRead && newMessages.length==0) @@ -449,7 +444,9 @@ setTimeout(() => { if (!isFinite(settings.unreadTimeout)) settings.unreadTimeout=60; if (settings.unreadTimeout) unreadTimeout = setTimeout(load, settings.unreadTimeout*1000); - // only openMusic on launch if music is new - var newMusic = MESSAGES.some(m => m.id === "music" && m.new); - checkMessages({ clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1, openMusic: newMusic && settings.openMusic }); + // only openMusic on launch if music is new, or state=="show" (set by messagesmusic) + var musicMsg = MESSAGES.find(m => m.id === "music"); + checkMessages({ + clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1, + openMusic: ((musicMsg&&musicMsg.new) && settings.openMusic) || (musicMsg&&musicMsg.state=="show") }); }, 10); // if checkMessages wants to 'load', do that diff --git a/apps/messagegui/boot.js b/apps/messagegui/boot.js index 4592f825d..ce7f1b99c 100644 --- a/apps/messagegui/boot.js +++ b/apps/messagegui/boot.js @@ -1,3 +1 @@ -(function() { - Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg)); -})(); \ No newline at end of file +Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg)); diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js index 57dc3c1e2..90791b5d6 100644 --- a/apps/messagegui/lib.js +++ b/apps/messagegui/lib.js @@ -1,3 +1,11 @@ +// Will calling Bangle.load reset everything? if false, we fast load +function loadWillReset() { + return Bangle.load === load || !Bangle.uiRemove; + /* FIXME: Maybe we need a better way of deciding if an app will + be fast loaded than just hard-coding a Bangle.uiRemove check. + Bangle.load could return a bool (as the load doesn't happen immediately). */ +} + /** * Listener set up in boot.js, calls into here to keep boot.js short */ @@ -26,11 +34,8 @@ exports.listener = function(type, msg) { if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true; else return; } - if (Bangle.load === load || !Bangle.uiRemove) { + if (loadWillReset()) { // no fast loading: store message to flash - /* FIXME: Maybe we need a better way of deciding if an app will - be fast loaded than just hard-coding a Bangle.uiRemove check. - Bangle.load could return a bool (as the load doesn't happen immediately). */ require("messages").save(msg); } else { if (!Bangle.MESSAGES) Bangle.MESSAGES = []; @@ -79,11 +84,17 @@ exports.listener = function(type, msg) { * @param {object} msg */ exports.open = function(msg) { - if (msg && msg.id && !msg.show) { - msg.show = 1; - if (Bangle.load === load) { - // no fast loading: store message to load in flash + if (msg && msg.id) { + // force a display by setting it as new and ensuring it ends up at the beginning of messages list + msg.new = 1; + if (loadWillReset()) { + // no fast loading: store message to load in flash - `msg` will be put in first require("messages").save(msg, {force: 1}); + } else { + // fast load - putting it at the end of Bangle.MESSAGES ensures it goes at the start of messages list + if (!Bangle.MESSAGES) Bangle.MESSAGES=[]; + Bangle.MESSAGES = Bangle.MESSAGES.filter(m => m.id!=msg.id) + Bangle.MESSAGES.push(msg); // putting at the } } diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 811f9baff..270d80543 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.61", + "version": "0.62", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/messages/lib.js b/apps/messages/lib.js index a710c81c4..f301a91cd 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -72,7 +72,7 @@ exports.apply = function(event, messages) { messages.splice(mIdx, 1); } else if (event.t==="add") { if (mIdx>=0) messages.splice(mIdx, 1); // duplicate ID! erase previous version - messages.unshift(event); + messages.unshift(event); // add at the beginning } else if (event.t==="modify") { if (mIdx>=0) messages[mIdx] = Object.assign(messages[mIdx], event); else messages.unshift(event); diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog index 8cc3079a3..cd1c49b60 100644 --- a/apps/messagesmusic/ChangeLog +++ b/apps/messagesmusic/ChangeLog @@ -2,4 +2,5 @@ 0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app. 0.03: Use the new messages library 0.04: Fix dependency on messages library - Fix loading message UI \ No newline at end of file + Fix loading message UI +0.05: Ensure we don't clear artist info diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js index 26fedecc1..68e88c2d8 100644 --- a/apps/messagesmusic/app.js +++ b/apps/messagesmusic/app.js @@ -1 +1,2 @@ -setTimeout(()=>require('messages').openGUI({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true})); +// don't define artist/etc here so we don't wipe them out of memory if they were stored from before +setTimeout(()=>require('messages').openGUI({"t":"add","id":"music","state":"show","new":true})); diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json index 22e0eff52..eef528f55 100644 --- a/apps/messagesmusic/metadata.json +++ b/apps/messagesmusic/metadata.json @@ -1,7 +1,8 @@ { "id": "messagesmusic", "name":"Messages Music", - "version":"0.04", + "shortName": "Music", + "version":"0.05", "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", "icon":"app.png", "type": "app", From 964ee01617fb2773a27e78047cace0291bc5b8ec Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 15 Dec 2022 11:41:27 +0000 Subject: [PATCH 40/82] Ensure that we hide widgets if in fullscreen mode (So that widgets are still hidden if launcher is fast-loaded) --- apps/iconlaunch/ChangeLog | 4 +++- apps/iconlaunch/app.js | 5 +++++ apps/iconlaunch/metadata.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog index b03599ae6..0c33a4871 100644 --- a/apps/iconlaunch/ChangeLog +++ b/apps/iconlaunch/ChangeLog @@ -17,4 +17,6 @@ 0.12: Use Bangle.load and Bangle.showClock 0.13: Fix automatic switch to clock 0.14: Revert use of Bangle.load to classic load calls since widgets would -still be loaded when they weren't supposed to. + still be loaded when they weren't supposed to. +0.15: Ensure that we hide widgets if in fullscreen mode + (So that widgets are still hidden if launcher is fast-loaded) diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js index ccc39f3bb..acf695ddb 100644 --- a/apps/iconlaunch/app.js +++ b/apps/iconlaunch/app.js @@ -12,6 +12,8 @@ if (!settings.fullscreen) { Bangle.loadWidgets(); Bangle.drawWidgets(); + } else { // for fast-load, if we had widgets then we should hide them + require("widget_utils").hide(); } let launchCache = s.readJSON("iconlaunch.cache.json", true)||{}; let launchHash = s.hash(/\.info/); @@ -190,6 +192,9 @@ btn: _=> { if (settings.oneClickExit) Bangle.showClock(); }, remove: function() { if (timeout) clearTimeout(timeout); + if (settings.fullscreen) { // for fast-load, if we hid widgets then we should show them again + require("widget_utils").show(); + } } }; diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json index 155e7bd9b..27f6386d3 100644 --- a/apps/iconlaunch/metadata.json +++ b/apps/iconlaunch/metadata.json @@ -2,7 +2,7 @@ "id": "iconlaunch", "name": "Icon Launcher", "shortName" : "Icon launcher", - "version": "0.14", + "version": "0.15", "icon": "app.png", "description": "A launcher inspired by smartphones, with an icon-only scrollable menu.", "tags": "tool,system,launcher", From 1fb5c0433d22a2af2b7f5b7e9045fa4703b21281 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 15 Dec 2022 21:05:52 +0300 Subject: [PATCH 41/82] clkinfo: repeating selection if menu is empty --- modules/clock_info.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/clock_info.js b/modules/clock_info.js index 6f37e5d3d..643a9f6f7 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -239,10 +239,15 @@ exports.addInteractive = function(menu, options) { } else if (lr) { if (menu.length==1) return; // 1 item - can't move oldMenuItem = menu[options.menuA].items[options.menuB]; - options.menuA += lr; - if (options.menuA<0) options.menuA = menu.length-1; - if (options.menuA>=menu.length) options.menuA = 0; - options.menuB = 0; + do { + options.menuA += lr; + if (options.menuA<0) options.menuA = menu.length-1; + if (options.menuA>=menu.length) options.menuA = 0; + options.menuB = 0; + //get the next one if the menu is empty + //can happen for dynamic ones (alarms, events) + //in the worst case we come back to 0 + } while(menu[options.menuA].items.length==0); } if (oldMenuItem) { menuHideItem(oldMenuItem); From 533d6693951d235e33bc82e38d112241b7404cc7 Mon Sep 17 00:00:00 2001 From: lauzonhomeschool <85599144+lauzonhomeschool@users.noreply.github.com> Date: Thu, 15 Dec 2022 18:57:48 -0500 Subject: [PATCH 42/82] boot.js - fix midnight in local timezone --- apps/sched/boot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sched/boot.js b/apps/sched/boot.js index c1bb1fc66..d736dd0e7 100644 --- a/apps/sched/boot.js +++ b/apps/sched/boot.js @@ -26,7 +26,7 @@ If active[0].js is defined, just run that code as-is and not alarm.js */ Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t); } else { // check for new alarms at midnight (so day of week works) - Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000)); + Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - currentTime); } })(); /* DEBUGGING From 68fa1253a55bff3fa29a1954099eb39cfd2bdf60 Mon Sep 17 00:00:00 2001 From: lauzonhomeschool <85599144+lauzonhomeschool@users.noreply.github.com> Date: Thu, 15 Dec 2022 19:58:20 -0500 Subject: [PATCH 43/82] ChangeLog 0.17 midnight in local timezone --- apps/sched/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 6d9d5caee..c99fe9858 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -17,3 +17,4 @@ 0.14: Added clkinfo for alarms and timers 0.15: Automatic translation of some string in clkinfo 0.16: Improve support for date timezone +0.17: Fix midnight in local timezone (alarms wouldn't always fire as expected in timezone != 0) From 1a7f37ee8e5696e2f31447b1bb8fa855fb730413 Mon Sep 17 00:00:00 2001 From: lauzonhomeschool <85599144+lauzonhomeschool@users.noreply.github.com> Date: Thu, 15 Dec 2022 19:58:36 -0500 Subject: [PATCH 44/82] Update metadata.json --- apps/sched/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 9c1a1d6b0..05e829d83 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.16", + "version": "0.17", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", From a0746c0eebb1264fe4a381b32a39fea61e784660 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 08:29:34 +0000 Subject: [PATCH 45/82] update changelog --- apps/widclkbttm/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widclkbttm/ChangeLog b/apps/widclkbttm/ChangeLog index a7141a7dd..373337378 100644 --- a/apps/widclkbttm/ChangeLog +++ b/apps/widclkbttm/ChangeLog @@ -2,5 +2,5 @@ 0.02: Modification for bottom widget area and text color 0.03: based in widclk v0.05 compatible at same time, bottom area and color 0.04: refactored to use less memory, and allow turning on/off when quick-switching apps -0.05: Different font size for bangle.js2 and bangle.js1 +0.05: Remove cyan color, use theme foreground instead From b89a368bd996b3cbfdbf728c9af8d9fa51329472 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 10:04:27 +0000 Subject: [PATCH 46/82] Removed duplication in node.js apps with 2 new libraries, added an 'app test' program - not finished yet --- bin/apploader.js | 42 +++---------- bin/firmwaremaker.js | 59 +++--------------- bin/firmwaremaker_c.js | 63 ++++--------------- bin/lib/apploader.js | 82 ++++++++++++++++++++++++ bin/lib/emulator.js | 115 ++++++++++++++++++++++++++++++++++ bin/runapptests.js | 138 +++++++++++++++++++++++++++++++++++++++++ bin/thumbnailer.js | 121 +++++++++--------------------------- 7 files changed, 391 insertions(+), 229 deletions(-) create mode 100644 bin/lib/apploader.js create mode 100644 bin/lib/emulator.js create mode 100755 bin/runapptests.js diff --git a/bin/apploader.js b/bin/apploader.js index 26c4c1f09..d4a5f828e 100755 --- a/bin/apploader.js +++ b/bin/apploader.js @@ -14,7 +14,6 @@ for Noble. var SETTINGS = { pretokenise : true }; -var APPSDIR = __dirname+"/../apps/"; var noble; ["@abandonware/noble", "noble"].forEach(module => { if (!noble) try { @@ -37,36 +36,18 @@ function ERROR(msg) { process.exit(1); } -//eval(require("fs").readFileSync(__dirname+"../core/js/utils.js")); -var AppInfo = require("../core/js/appinfo.js"); -global.Const = { - /* Are we only putting a single app on a device? If so - apps should all be saved as .bootcde and we write info - about the current app into app.info */ - SINGLE_APP_ONLY : false, -}; var deviceId = "BANGLEJS2"; -var apps = []; -var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true}); -dirs.forEach(dir => { - var appsFile; - if (dir.name.startsWith("_example") || !dir.isDirectory()) - return; - try { - appsFile = require("fs").readFileSync(APPSDIR+dir.name+"/metadata.json").toString(); - } catch (e) { - ERROR(dir.name+"/metadata.json does not exist"); - return; - } - apps.push(JSON.parse(appsFile)); -}); +var apploader = require("./lib/apploader.js"); var args = process.argv; var bangleParam = args.findIndex(arg => /-b\d/.test(arg)); if (bangleParam!==-1) { deviceId = "BANGLEJS"+args.splice(bangleParam, 1)[0][2]; } +apploader.init({ + DEVICEID : deviceId +}); if (args.length==3 && args[2]=="list") cmdListApps(); else if (args.length==3 && args[2]=="devices") cmdListDevices(); else if (args.length==4 && args[2]=="install") cmdInstallApp(args[3]); @@ -90,7 +71,7 @@ process.exit(0); } function cmdListApps() { - console.log(apps.map(a=>a.id).join("\n")); + console.log(apploader.apps.map(a=>a.id).join("\n")); } function cmdListDevices() { var foundDevices = []; @@ -113,19 +94,10 @@ function cmdListDevices() { } function cmdInstallApp(appId, deviceAddress) { - var app = apps.find(a=>a.id==appId); + var app = apploader.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 AppInfo.getFiles(app, { - fileGetter:function(url) { - console.log(__dirname+"/"+url); - return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, - settings : SETTINGS, - device : { id : deviceId } - }).then(files => { - //console.log(files); - var command = files.map(f=>f.cmd).join("\n")+"\n"; + return apploader.getAppFilesString(app).then(command => { bangleSend(command, deviceAddress).then(() => process.exit(0)); }); } diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 1dc5ec073..4535c4a5e 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -1,17 +1,12 @@ -#!/usr/bin/env nodejs +#!/usr/bin/env node /* Mashes together a bunch of different apps to make a single firmware JS file which can be uploaded. */ -var SETTINGS = { - pretokenise : true -}; - var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); -var APPDIR = ROOTDIR+'/apps'; var OUTFILE = ROOTDIR+'/firmware.js'; -var DEVICE = "BANGLEJS"; +var DEVICEID = "BANGLEJS"; var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","widbat","widbt","welcome" @@ -19,53 +14,17 @@ var APPS = [ // IDs of apps to install var MINIFY = true; var fs = require("fs"); -global.Const = { - /* Are we only putting a single app on a device? If so - apps should all be saved as .bootcde and we write info - about the current app into app.info */ - SINGLE_APP_ONLY : false, -}; +var apploader = require("./lib/apploader.js"); +apploader.init({ + DEVICEID : DEVICEID +}); -var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); var appfiles = []; -function fileGetter(url) { - console.log("Loading "+url) - if (MINIFY) { - /*if (url.endsWith(".js")) { - var f = url.slice(0,-3); - console.log("MINIFYING "+f); - const execSync = require('child_process').execSync; - // --config PRETOKENISE=true - // --minify - code = execSync(`espruino --config SET_TIME_ON_WRITE=false --minify --board BANGLEJS ${f}.js -o ${f}.min.js`); - console.log(code.toString()); - url = f+".min.js"; - }*/ - if (url.endsWith(".json")) { - var f = url.slice(0,-5); - console.log("MINIFYING JSON "+f); - var j = eval("("+fs.readFileSync(url).toString("binary")+")"); - var code = JSON.stringify(j); - //console.log(code); - url = f+".min.json"; - fs.writeFileSync(url, code); - } - } - return Promise.resolve(fs.readFileSync(url).toString("binary")); -} - Promise.all(APPS.map(appid => { - try { - var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "/metadata.json").toString()); - } catch (e) { - throw new Error(`App ${appid} not found`); - } - return AppInfo.getFiles(app, { - fileGetter : fileGetter, - settings : SETTINGS, - device : { id : DEVICE } - }).then(files => { + var app = apploader.apps.find(a => a.id==appid); + if (!app) throw new Error(`App ${appid} not found`); + return apploader.getAppFiles(app).then(files => { appfiles = appfiles.concat(files); }); })).then(() => { diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 08fc6fe83..54b63d5d9 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -7,25 +7,20 @@ to populate Storage initially. Bangle.js 1 doesn't really have anough flash space for this, but we have enough on v2. */ -var SETTINGS = { - pretokenise : true -}; - -var DEVICE = process.argv[2]; +var DEVICEID = process.argv[2]; var path = require('path'); +var fs = require("fs"); var ROOTDIR = path.join(__dirname, '..'); -var APPDIR = ROOTDIR+'/apps'; -var MINIFY = true; var OUTFILE, APPS; -if (DEVICE=="BANGLEJS") { +if (DEVICEID=="BANGLEJS") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","sched","widbat","widbt","welcome" ]; -} else if (DEVICE=="BANGLEJS2") { +} else if (DEVICEID=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launch","antonclk","setting", @@ -37,16 +32,12 @@ if (DEVICE=="BANGLEJS") { console.log(" bin/firmwaremaker_c.js BANGLEJS2"); process.exit(1); } -console.log("Device = ",DEVICE); +console.log("Device = ",DEVICEID); - -var fs = require("fs"); -global.Const = { - /* Are we only putting a single app on a device? If so - apps should all be saved as .bootcde and we write info - about the current app into app.info */ - SINGLE_APP_ONLY : false, -}; +var apploader = require("./lib/apploader.js"); +apploader.init({ + DEVICEID : DEVICEID +}); function atob(input) { @@ -84,31 +75,8 @@ function atob(input) { return new Uint8Array(output); } -var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); var appfiles = []; -function fileGetter(url) { - console.log("Loading "+url) - if (MINIFY) { - if (url.endsWith(".json")) { - var f = url.slice(0,-5); - console.log("MINIFYING JSON "+f); - var j = eval("("+fs.readFileSync(url).toString("binary")+")"); - var code = JSON.stringify(j); - //console.log(code); - url = f+".min.json"; - fs.writeFileSync(url, code); - } - } - 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... function evaluateFile(file) { var hsStart = 'require("heatshrink").decompress(atob("'; @@ -132,16 +100,9 @@ function evaluateFile(file) { } Promise.all(APPS.map(appid => { - try { - var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "/metadata.json").toString()); - } catch (e) { - throw new Error(`App ${appid} not found`); - } - return AppInfo.getFiles(app, { - fileGetter : fileGetter, - settings : SETTINGS, - device : { id : DEVICE } - }).then(files => { + var app = apploader.apps.find(a => a.id==appid); + if (!app) throw new Error(`App ${appid} not found`); + return apploader.getAppFiles(app).then(files => { appfiles = appfiles.concat(files); }); })).then(() => { diff --git a/bin/lib/apploader.js b/bin/lib/apploader.js new file mode 100644 index 000000000..6bf74eb7e --- /dev/null +++ b/bin/lib/apploader.js @@ -0,0 +1,82 @@ +/* Node.js library with utilities to handle using the app loader from node.js */ + +var DEVICEID = "BANGLEJS2"; +var MINIFY = true; // minify JSON? +var BASE_DIR = __dirname + "/../.."; +var APPSDIR = BASE_DIR+"/apps/"; + +//eval(require("fs").readFileSync(__dirname+"../core/js/utils.js")); +var Espruino = require(__dirname + "/../../core/lib/espruinotools.js"); +//eval(require("fs").readFileSync(__dirname + "/../../core/lib/espruinotools.js").toString()); +//eval(require("fs").readFileSync(__dirname + "/../../core/js/utils.js").toString()); +var AppInfo = require(__dirname+"/../../core/js/appinfo.js"); + +var SETTINGS = { + pretokenise : true +}; +global.Const = { + /* Are we only putting a single app on a device? If so + apps should all be saved as .bootcde and we write info + about the current app into app.info */ + SINGLE_APP_ONLY : false, +}; + +var apps = []; + +// call with {DEVICEID:"BANGLEJS/BANGLEJS2"} +exports.init = function(options) { + if (options.DEVICEID) + DEVICEID = options.DEVICEID; + // Load app metadata + var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true}); + dirs.forEach(dir => { + var appsFile; + if (dir.name.startsWith("_example") || !dir.isDirectory()) + return; + try { + appsFile = require("fs").readFileSync(APPSDIR+dir.name+"/metadata.json").toString(); + } catch (e) { + ERROR(dir.name+"/metadata.json does not exist"); + return; + } + apps.push(JSON.parse(appsFile)); + }); +}; + +exports.AppInfo = AppInfo; +exports.apps = apps; + +// used by getAppFiles +function fileGetter(url) { + url = BASE_DIR+"/"+url; + console.log("Loading "+url) + var data; + if (MINIFY && url.endsWith(".json")) { + var f = url.slice(0,-5); + console.log("MINIFYING JSON "+f); + var j = eval("("+require("fs").readFileSync(url).toString("binary")+")"); + data = JSON.stringify(j); + } else { + var blob = require("fs").readFileSync(url); + 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); +} + +exports.getAppFiles = function(app) { + return AppInfo.getFiles(app, { + fileGetter:fileGetter, + settings : SETTINGS, + device : { id : DEVICEID } + }); +}; + +// Get all the files for this app as a string of Storage.write commands +exports.getAppFilesString = function(app) { + return exports.getAppFiles(app).then(files => { + return files.map(f=>f.cmd).join("\n")+"\n" + }) +}; diff --git a/bin/lib/emulator.js b/bin/lib/emulator.js new file mode 100644 index 000000000..f7c82ec3c --- /dev/null +++ b/bin/lib/emulator.js @@ -0,0 +1,115 @@ +/* Node.js library with utilities to handle using the emulator from node.js */ + +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; + +var BASE_DIR = __dirname + "/../.."; +var DIR_IDE = BASE_DIR + "/../EspruinoWebIDE"; + +/* we factory reset ONCE, get this, then we can use it to reset +state quickly for each new app */ +var factoryFlashMemory; + +// Log of messages from app +var appLog = ""; +var lastOutputLine = ""; + +function onConsoleOutput(txt) { + appLog += txt + "\n"; + lastOutputLine = txt; +} + +exports.init = function(options) { + if (options.EMULATOR) + EMULATOR = options.EMULATOR; + if (options.DEVICEID) + DEVICEID = options.DEVICEID; + + eval(require("fs").readFileSync(DIR_IDE + "/emu/emulator_"+EMULATOR+".js").toString()); + eval(require("fs").readFileSync(DIR_IDE + "/emu/emu_"+EMULATOR+".js").toString()); + eval(require("fs").readFileSync(DIR_IDE + "/emu/common.js").toString()/*.replace('console.log("EMSCRIPTEN:"', '//console.log("EMSCRIPTEN:"')*/); + + jsRXCallback = function() {}; + jsUpdateGfx = function() {}; + + factoryFlashMemory = new Uint8Array(FLASH_SIZE); + factoryFlashMemory.fill(255); + + exports.flashMemory = flashMemory; + exports.GFX_WIDTH = GFX_WIDTH; + exports.GFX_HEIGHT = GFX_HEIGHT; + exports.tx = jsTransmitString; + exports.idle = jsIdle; + exports.stopIdle = jsStopIdle; + exports.getGfxContents = jsGetGfxContents; + + return new Promise(resolve => { + setTimeout(function() { + console.log("Emulator Loaded..."); + jsInit(); + jsIdle(); + console.log("Emulator Factory reset"); + exports.tx("Bangle.factoryReset()\n"); + factoryFlashMemory.set(flashMemory); + console.log("Emulator Ready!"); + + resolve(); + },0); + }); +}; + +// Factory reset +exports.factoryReset = function() { + exports.flashMemory.set(factoryFlashMemory); + exports.tx("reset()\n"); + appLog=""; +}; + +// Transmit a string +exports.tx = function() {}; // placeholder +exports.idle = function() {}; // placeholder +exports.stopIdle = function() {}; // placeholder +exports.getGfxContents = function() {}; // placeholder + +exports.flashMemory = undefined; // placeholder +exports.GFX_WIDTH = undefined; // placeholder +exports.GFX_HEIGHT = undefined; // placeholder + +// Get last line sent to console +exports.getLastLine = function() { + return lastOutputLine; +}; + +// Gets the screenshot as RGBA Uint32Array +exports.getScreenshot = function() { + var rgba = new Uint8Array(exports.GFX_WIDTH*exports.GFX_HEIGHT*4); + exports.getGfxContents(rgba); + var rgba32 = new Uint32Array(rgba.buffer); + return rgba32; +} + +// Write the screenshot to a file options={errorIfBlank} +exports.writeScreenshot = function(imageFn, options) { + options = options||{}; + return new Promise((resolve,reject) => { + var rgba32 = exports.getScreenshot(); + + if (options.errorIfBlank) { + var firstPixel = rgba32[0]; + var blankImage = rgba32.every(col=>col==firstPixel); + if (blankImage) reject("Image is blank"); + } + + var Jimp = require("jimp"); + let image = new Jimp(exports.GFX_WIDTH, exports.GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(new Uint8Array(rgba32.buffer)); + image.write(imageFn, (err) => { + if (err) return reject(err); + console.log("Image written as "+imageFn); + resolve(); + }); + }); + }); +} diff --git a/bin/runapptests.js b/bin/runapptests.js new file mode 100755 index 000000000..8a415b109 --- /dev/null +++ b/bin/runapptests.js @@ -0,0 +1,138 @@ +#!/usr/bin/node +/* + +This allows us to test apps using the Bangle.js emulator + +IT IS UNFINISHED + +It searches for `test.json` in each app's directory and will +run them in sequence. + +TODO: + +* more code to test with +* run tests that we have found and loaded (currently we just use TEST) +* documentation +* actual tests +* detecting 'Uncaught Error' +* logging of success/fail +* ... + +*/ + +// A si +var TEST = { + app : "android", + tests : [ { + load : "messagesgui.app.js", + steps : [ + {t:"gb", "obj":{"t":"notify","id":1234,"src":"Twitter","title":"A Name","body":"message contents"}}, + {t:"cmd", "js":"X='hello';"}, + {t:"eval", "js":"X", eq:"hello"} + ] + }] +}; + +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; + +var BASE_DIR = __dirname + "/.."; +var APP_DIR = BASE_DIR + "/apps"; +var DIR_IDE = BASE_DIR + "/../EspruinoWebIDE"; + + +if (!require("fs").existsSync(DIR_IDE)) { + 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); +} + +var apploader = require(BASE_DIR+"/bin/lib/apploader.js"); +apploader.init({ + DEVICEID : DEVICEID +}); +var emu = require(BASE_DIR+"/bin/lib/emulator.js"); + +// Last set of text received +var lastTxt; + +function ERROR(s) { + console.error(s); + process.exit(1); +} + +function runTest(test) { + var app = apploader.apps.find(a=>a.id==test.app); + if (!app) ERROR(`App ${JSON.stringify(test.app)} not found`); + if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + return apploader.getAppFilesString(app).then(command => { + // What about dependencies?? + test.tests.forEach((subtest,subtestIdx) => { + console.log(`==============================`); + console.log(`"${test.app}" Test ${subtestIdx}`); + console.log(`==============================`); + emu.factoryReset(); + console.log("Sending app "+test.app); + emu.tx(command); + console.log("Sent app"); + emu.tx(test.load ? `load(${JSON.stringify(test.load)})\n` : "load()\n"); + console.log("App Loaded."); + var ok = true; + subtest.steps.forEach(step => { + if (ok) switch(step.t) { + case "cmd" : emu.tx(`${step.js}\n`); break; + case "gb" : emu.tx(`GB(${JSON.stringify(step.obj)})\n`); break; + case "tap" : emu.tx(`Bangle.emit(...)\n`); break; + case "eval" : + emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); + var result = emu.getLastLine(); + var expected = JSON.stringify(step.eq); + console.log("GOT "+result); + if (result!=expected) { + console.log("EXPECTED "+expected); + ok = false; + } + break; + // tap/touch/drag/button press + // delay X milliseconds? + case "screenshot" : + console.log("Compare screenshots"); + break; + default: ERROR("Unknown step type "+step.t); + } + }); + }); + emu.stopIdle(); + }); +} + + +emu.init({ + EMULATOR : EMULATOR, + DEVICEID : DEVICEID +}).then(function() { + // Emulator is now loaded + console.log("Loading tests"); + var tests = []; + apploader.apps.forEach(app => { + var testFile = APP_DIR+"/"+app.id+"/test.json"; + if (!require("fs").existsSync(testFile)) return; + var test = JSON.parse(require("fs").readFileSync(testFile).toString()); + test.app = app.id; + tests.push(test); + }); + // Running tests + runTest(TEST); +}); +/* + if (erroredApps.length) { + erroredApps.forEach(app => { + console.log(`::error file=${app.id}::${app.id}`); + console.log("::group::Log"); + app.log.split("\n").forEach(line => console.log(`\u001b[38;2;255;0;0m${line}`)); + console.log("::endgroup::"); + }); + process.exit(1); + } +*/ diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index 0895098e9..e9eb2ff61 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -7,6 +7,9 @@ var DEVICEID = "BANGLEJS2"; var EMULATOR = "banglejs1"; var DEVICEID = "BANGLEJS"; +var emu = require("./lib/emulator.js"); +var apploader = require("./lib/apploader.js"); + var singleAppId; if (process.argv.length!=3 && process.argv.length!=2) { @@ -20,126 +23,58 @@ if (process.argv.length!=3 && process.argv.length!=2) { if (process.argv.length==3) singleAppId = process.argv[2]; -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; -var Espruino = require(__dirname + "/../core/lib/espruinotools.js"); -//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")); - -/* 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() {}; - function ERROR(s) { console.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); + var app = apploader.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, - device : { id : DEVICEID } - }).then(files => { - console.log(`AppInfo returned for ${appId}`);//, 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`; - appLog = ""; - jsTransmitString(command); - console.log("Done."); - jsTransmitString("Bangle.setLCDMode();clearInterval();clearTimeout();\n"); - 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 (appLog.replace("Uncaught Storage Updated!", "").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) { - 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); - } + return apploader.getAppFilesString(app).then(command => { + console.log(`AppInfo returned for ${appId}`);//, files); + emu.factoryReset(); + console.log("Uploading..."); + emu.tx("g.clear()\n"); + command += `load("${appId}.app.js")\n`; + appLog = ""; + emu.tx(command); + console.log("Done."); + emu.tx("Bangle.setLCDMode();clearInterval();clearTimeout();\n"); + emu.stopIdle(); + return emu.writeScreenshot(imageFn, { errorIfBlank : true }).then(() => console.log("X")).catch( err => { + console.log("Error", err); }); }); } var screenshots = []; +apploader.init({ + EMULATOR : EMULATOR, + DEVICEID : DEVICEID +}); // wait until loaded... -setTimeout(function() { - console.log("Loaded..."); - jsInit(); - jsIdle(); - console.log("Factory reset"); - jsTransmitString("Bangle.factoryReset()\n"); - factoryFlashMemory.set(flashMemory); - console.log("Ready!"); - +emu.init({ + EMULATOR : EMULATOR, + DEVICEID : DEVICEID +}).then(function() { if (singleAppId) { + console.log("Single Screenshot"); getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); return; } - var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom); + console.log("Screenshot ALL"); + var appList = apploader.apps.filter(app => (!app.type || app.type=="clock") && !app.custom); appList = appList.filter(app => !app.screenshots && app.supports.includes(DEVICEID)); var promise = Promise.resolve(); From 009d93bdca262bf2c93e0cc5deec89aadb51f298 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 10:05:19 +0000 Subject: [PATCH 47/82] fix screenshot dir --- bin/thumbnailer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index e9eb2ff61..22cdc27a5 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -6,6 +6,7 @@ var DEVICEID = "BANGLEJS2"; */ var EMULATOR = "banglejs1"; var DEVICEID = "BANGLEJS"; +var SCREENSHOT_DIR = __dirname+"/../screenshots/"; var emu = require("./lib/emulator.js"); var apploader = require("./lib/apploader.js"); @@ -69,7 +70,7 @@ emu.init({ }).then(function() { if (singleAppId) { console.log("Single Screenshot"); - getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); + getThumbnail(singleAppId, SCREENSHOT_DIR+singleAppId+"-"+EMULATOR+".png"); return; } @@ -79,6 +80,10 @@ emu.init({ var promise = Promise.resolve(); appList.forEach(app => { + if (!app.supports.includes(DEVICEID)) { + console.log(`App ${app.id} isn't designed for ${DEVICEID}`); + return; + } promise = promise.then(() => { var imageFile = "screenshots/"+app.id+"-"+EMULATOR+".png"; return getThumbnail(app.id, imageFile).then(ok => { From e7edd54017b8f58ee25922fd31184814abab2af4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 11:00:00 +0000 Subject: [PATCH 48/82] Fix messages app loading on clock without fast load --- apps/messagegui/ChangeLog | 1 + apps/messagegui/lib.js | 31 +++++++++++++++---------------- apps/messagegui/metadata.json | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 46634985a..228a952de 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -85,3 +85,4 @@ 0.60: Fix saving of removal messages if UI not open 0.61: Fix regression where loading into messages app stops back from working (#2398) 0.62: Remove '.show' field, tidyup and fix .open if fast load not enabled +0.63: Fix messages app loading on clock without fast load diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js index 90791b5d6..a9436a77b 100644 --- a/apps/messagegui/lib.js +++ b/apps/messagegui/lib.js @@ -29,36 +29,33 @@ exports.listener = function(type, msg) { } const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; - let loadMessages = (Bangle.CLOCK || event.important); + let loadMessages = (Bangle.CLOCK || event.important); // should we load the messages app? if (type==="music") { if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true; else return; } - if (loadWillReset()) { - // no fast loading: store message to flash - require("messages").save(msg); - } else { - if (!Bangle.MESSAGES) Bangle.MESSAGES = []; - require("messages").apply(msg, Bangle.MESSAGES); - if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES; - } + // Write the message to Bangle.MESSAGES. We'll deal with it in messageTimeout below + if (!Bangle.MESSAGES) Bangle.MESSAGES = []; + require("messages").apply(msg, Bangle.MESSAGES); + if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES; const saveToFlash = () => { - // save messages from RAM to flash after all, if we decide not to launch app - if (Bangle.MESSAGES) Bangle.MESSAGES.forEach(m => require("messages").save(m)); + // save messages from RAM to flash if we decide not to launch app + // We apply all of Bangle.MESSAGES here in one write + if (!Bangle.MESSAGES || !Bangle.MESSAGES.length) return; + let messages = require("messages").getMessages(msg); + (Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, messages)); + require("messages").write(messages); delete Bangle.MESSAGES; } msg.handled = true; - if ((msg.t!=="add" || !msg.new) && (type!=="music")) { // music always has t:"modify" - saveToFlash(); - return; - } + if ((msg.t!=="add" || !msg.new) && (type!=="music")) // music always has t:"modify" + return saveToFlash(); const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet; const unlockWatch = appSettings.unlockWatch; // don't auto-open messages in quiet mode if quietNoAutOpn is true if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) loadMessages = false; - // after a delay load the app, to ensure we have all the messages if (exports.messageTimeout) clearTimeout(exports.messageTimeout); exports.messageTimeout = setTimeout(function() { @@ -75,6 +72,8 @@ exports.listener = function(type, msg) { Bangle.setLCDPower(1); // turn screen on } } + // if loading the gui would reload everything, we must save our messages + if (loadWillReset()) saveToFlash(); exports.open(msg); }, 500); }; diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 270d80543..1a7a6c750 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.62", + "version": "0.63", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From 99fa71bf4e33147ad640264a2eabb521cf747eec Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 15:16:25 +0000 Subject: [PATCH 49/82] new lcd clock app --- apps/lcdclock/ChangeLog | 1 + apps/lcdclock/app-icon.js | 1 + apps/lcdclock/app.js | 84 +++++++++++++++++++++++++++++++++++ apps/lcdclock/app.png | Bin 0 -> 12258 bytes apps/lcdclock/metadata.json | 14 ++++++ apps/lcdclock/screenshot.png | Bin 0 -> 2474 bytes 6 files changed, 100 insertions(+) create mode 100644 apps/lcdclock/ChangeLog create mode 100644 apps/lcdclock/app-icon.js create mode 100644 apps/lcdclock/app.js create mode 100644 apps/lcdclock/app.png create mode 100644 apps/lcdclock/metadata.json create mode 100644 apps/lcdclock/screenshot.png diff --git a/apps/lcdclock/ChangeLog b/apps/lcdclock/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/lcdclock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/lcdclock/app-icon.js b/apps/lcdclock/app-icon.js new file mode 100644 index 000000000..ed3161c41 --- /dev/null +++ b/apps/lcdclock/app-icon.js @@ -0,0 +1 @@ +atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///T//+f///T///Q///Qf//Sf//Qf//Pf//Qf//AP//Yf//f///c///f///f///fiD/f8f/fur/f9f/fir/f9f/f67/f9f/fy7/f8f/fz//f+//AAAAAAAAAAAAAAAAf///////TD/u///vQB/EX/9PUV/d3/9fSd/F3/9HWd/d3/9Xf9/d//9Hf///////f///////f///8Hg/fmefwPAffmecz/Offmecz/Offmecz/OffmAcwHOffngc8DPffn+c/zOffn+c/zOffn+c/zOffn+f8DAff///8Hg/f///8Pw/f///////f//////+P//////8AAAAAAAAAAAAAAAAAAAAAAAA") diff --git a/apps/lcdclock/app.js b/apps/lcdclock/app.js new file mode 100644 index 000000000..2bc23247c --- /dev/null +++ b/apps/lcdclock/app.js @@ -0,0 +1,84 @@ +Graphics.prototype.setFont7Seg = function() { + return this.setFontCustom(atob("AAAAAAAAAAAACAQCAAAAAAIAd0BgMBdwAAAAAAAADuAAAB0RiMRcAAAAAiMRiLuAAAcAQCAQdwAADgiMRiIOAAAd0RiMRBwAAAAgEAgDuAAAd0RiMRdwAADgiMRiLuAAAABsAAAd0QiEQdwAADuCIRCIOAAAd0BgMBAAAAAOCIRCLuAAAd0RiMRAAAADuiEQiAAAAAd0BgMBBwAADuCAQCDuAAAdwAAAAAAAAAAAIBALuAAAdwQCAQdwAADuAIBAIAAAAd0AgEAcEAgEAdwAd0AgEAdwAADugMBgLuAAAd0QiEQcAAADgiEQiDuAAAd0AgEAAAAADgiMRiIOAAAAEAgEAdwAADuAIBALuAAAdwBAIBdwAADuAIBAIOAIBALuADuCAQCDuAAAcAQCAQdwAAAOiMRiLgAAAA=="), 32, atob("BwAAAAAAAAAAAAAAAAcCAAcHBwcHBwcHBwcEAAAAAAAABwcHBwcHBwcHBwcHCgcHBwcHBwcHBwoHBwc="), 9); +} + + +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global +let drawTimeout; + +// Actually draw the watch face +let draw = function() { + var x = R.x + R.w/2; + var y = R.y + R.h/2; + g.reset().setColor(g.theme.bg).setBgColor(g.theme.fg); + g.clearRect(R.x,barY+2,R.x2,R.y2-8); + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("7Seg:5").drawString(timeStr, x, y+39); + // Show date and day of week + g.setFontAlign(0, 0).setFont("7Seg:2"); + g.setFontAlign(-1, 0).drawString(require("locale").meridian(date).toUpperCase(), R.x+6, y); + g.setFontAlign(0, 0).drawString(require("locale").dow(date, 1).toUpperCase(), x, y); + g.setFontAlign(1, 0).drawString(date.getDate(), R.x2 - 6, y); + + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFont7Seg; + // remove info menu + clockInfoMenu.remove(); + delete clockInfoMenu; + clockInfoMenu2.remove(); + delete clockInfoMenu2; + // reset theme + g.setTheme(oldTheme); + }}); +// Load widgets +Bangle.loadWidgets(); +var R = Bangle.appRect; +R.x+=1; +R.y+=1; +R.x2-=1; +R.y2-=1; +R.w-=2; +R.h-=2; +var midX = R.x+R.w/2; +var barY = 80; +// Clear the screen once, at startup +let oldTheme = g.theme; +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(1); +g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8}).clearRect(R.x,barY,R.w,barY+1).clearRect(midX,R.y,midX+1,barY); +draw(); +setTimeout(Bangle.drawWidgets,0); + +let clockInfoDraw = (itm, info, options) => { + let texty = options.y+41; + g.reset().setFont("7Seg").setColor(g.theme.bg).setBgColor(g.theme.fg); + if (options.focus) g.setBgColor("#FF0"); + g.clearRect({x:options.x,y:options.y,w:options.w,h:options.h,r:8}); + + if (info.img) g.drawImage(info.img, options.x+2, options.y+2); + var title = clockInfoItems[options.menuA].name; + var text = info.text.toString().toUpperCase(); + if (title!="Bangle") g.setFontAlign(1,0).drawString(title.toUpperCase(), options.x+options.w-2, options.y+14); + if (g.setFont("7Seg:2").stringWidth(text)+8>options.w) g.setFont("7Seg"); + g.setFontAlign(0,0).drawString(text, options.x+options.w/2, options.y+40); + +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:R.x, y:R.y, w:midX-2, h:barY-R.y-2, draw : clockInfoDraw}); +let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { x:midX+2, y:R.y, w:midX-3, h:barY-R.y-2, draw : clockInfoDraw}); +} diff --git a/apps/lcdclock/app.png b/apps/lcdclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..6a117b52598202f852ce5013571c533806e30cc1 GIT binary patch literal 12258 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 literal 0 HcmV?d00001 diff --git a/apps/lcdclock/metadata.json b/apps/lcdclock/metadata.json new file mode 100644 index 000000000..d7d09b106 --- /dev/null +++ b/apps/lcdclock/metadata.json @@ -0,0 +1,14 @@ +{ "id": "lcdclock", + "name": "LCD Clock", + "version":"0.01", + "description": "A Casio-style clock, with ClockInfo areas at the top and bottom. Tap them and swipe up/down to toggle between different information", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock,clkinfo", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"lcdclock.app.js","url":"app.js"}, + {"name":"lcdclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/lcdclock/screenshot.png b/apps/lcdclock/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b0bb5934a9ce315db242f7da0fbd61afb93dcc7d GIT binary patch literal 2474 zcmV;b303xqP)Px;WJyFpRCr$Poa=g{It&2Q_kZY~kDSsa)F4?GTfpqkjVB;q^^F$sM7 zWqZDfYklL^+kw*;)V=@$PXeEQS?yV{a}S)p4&?>-MTQd8WrnqG>;jYL7a4&t2?c=` z0iK1xi%Szg;7gZ}|=PqHRU~=)lOE2m6fe zwjk6%SA&(=ufTxQf$JS5b>P;=BDK&_{&*f3XgX-Uqbve1*PtFU>0g0?CBw3gKwv95 z-Mse}0)cg~EThf@KKqkR)I>)+MBt?m0B-}g3d}ZQkigPMq%Mv7j*|HFc41B|K3#Ej z;5~f0@ZUFLq`;h5H27-w!24~&zA#du?%QL}S0-`McHpCNPj4Ek4!r-^Ix31(tS3q= zG0}>t9rrZG-2!g`wch>Q(CzkUKfi=H>XceBwVfJ)cLaL33R1^;9AH7|#yU5(b;pkKcfsF!t?{L`<|Jxo&EUnuJDehew z!O=RJ9k{1!oB@i8bLApv86dE&%6|mb0@q$HI-d5rRmSzK|7Z)LxzVe#-qCq{2rS+0 zdXszN$6b`bEBkGYp2&0WAOd^emBRcUf!U7r7en<$Mt@jl>^r8XTT1)mC(}mYXD*~Wb!ndTdM?U# z^vVKP)(g}-AAvmta_7>4bNO zz{$nGL-@)9--2(YQ;0hN-LC# zUYn^)zWXwAzy&zbI}rbjbE;70kakgfABWuPtrWR zm-0FaFnXT2kck7VPFPb$;0?LaB3iF!E%u0c2t4tZyjGz`&l@44^%@9_z*cuk%tK%V zE+L{d2?R!9t2-s;l@fR*5`FYhg7irePh0QEdPK8LJqI2MCh}U*kRx-udaP)+u4A<5 zbJ2arAN^CO(WeR4>s6N6x`nW^Kw!>+L*EYw%uQUVP@{t(FanQuF7$c`jKHBnjShyu z2t3-k(Cd{FICNzMgTSUa?O|9?oK)w)kcU7NfuZNXQAraA0$Bv;z*%I8JA?oN55deu zvIrn>7FpsBA%MU`FmsVC0tlQ%mbgO*An*{(TqKJC0%wsW?hpb9JOs1tB1eb*nrPN< zwvJ*~%4M4)0&8rTG^Ua?m2yGgNkaBswvsfJazWr;lO~O?Bu%AU5O|W1y_c;dO{H9> z3fuyrz2Wf8ZOIcIuUF8ILVPW^BcK1YTs+qu!6lRP@S8Z#Vmxz46v`t~^x7)rlDRen zzG>G(U=M+lxt@2UXD%IBU(hRYrUTP~|Co)y5ADF7!h7cGd#i3_bS}~HIEDBj5DnH7 z&ofu2rG8X&F46H27=cf1jutLD9s(mU0_#7JK1X`qjh?w9P9Z$`dFEPcaWpSFm*{wO zU^*}z*!r&byc<1p>A?DmRp(3xrUTP~_3vl)z#3N3T~BjO_P_>8oixPzn97QrA4g$+i5m;t4Z3F_#=!Xj&eTOSA@pwdZF46I3tu1;T(eb99LS!zc zx8TvaM91r0&qrY$KN5{zM|3;{{vi+zHaZ>x%LLd$>==1YWDvM5TqYL;M&LizMGF@l zk1YgSh||yJXyKycAus|Xu)Mt;dp$nl#6E_(OoZ^OLV-crx4MFx^ui%;kE1aJkz0l zL(Iwrfwkl(k3(R&0K-oq3m1pfWC92*Gh#$x0tj5VIHV>MKwy~> zBMK9E3%ukP{q$1dHIv#+2rS=JBJfR$`NU;>2uv4#q5{`l^ZJ~~ZwFqN!dBwCMSV)< o+lQR~2#m;2(cpUPY(1U#A8YpJ;fbu0#sB~S07*qoM6N<$g72V_`v3p{ literal 0 HcmV?d00001 From 260bb1bcda6b89ba22ef77162bb2a567d4f92eb6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 15:16:33 +0000 Subject: [PATCH 50/82] notes on stuff to test --- apps/messages/TEST_ME.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 apps/messages/TEST_ME.txt diff --git a/apps/messages/TEST_ME.txt b/apps/messages/TEST_ME.txt new file mode 100644 index 000000000..8ce50d8b6 --- /dev/null +++ b/apps/messages/TEST_ME.txt @@ -0,0 +1,7 @@ +We need automated tests for this. Specifically: + + +* send notification in clock with fast load -> messagesgui appears +* send notification in clock without fast load -> messagesgui appears +* send notification and delete notification quick -> messagesgui doesn't load +* music? From 8425f0b50aea39a9083b95ccc9f4211d9c5d556a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 16 Dec 2022 15:40:58 +0000 Subject: [PATCH 51/82] Update clock_info to avoid a redraw/general tidyups --- apps/agenda/ChangeLog | 1 + apps/agenda/agenda.clkinfo.js | 2 +- apps/agenda/metadata.json | 2 +- apps/bwclk/ChangeLog | 3 ++- apps/bwclk/app.js | 2 +- apps/bwclk/metadata.json | 2 +- apps/clkinfofw/ChangeLog | 1 + apps/clkinfofw/clkinfo.js | 19 +++---------------- apps/clkinfofw/metadata.json | 2 +- apps/ha/ChangeLog | 3 ++- apps/ha/ha.clkinfo.js | 4 ++-- apps/ha/metadata.json | 2 +- apps/linuxclock/ChangeLog | 3 ++- apps/linuxclock/app.js | 8 ++++---- apps/linuxclock/metadata.json | 2 +- apps/sched/ChangeLog | 3 ++- apps/sched/clkinfo.js | 2 +- apps/sched/metadata.json | 2 +- apps/smpltmr/ChangeLog | 1 + apps/smpltmr/clkinfo.js | 4 ++-- apps/smpltmr/metadata.json | 2 +- apps/weather/ChangeLog | 1 + apps/weather/clkinfo.js | 10 +++++----- apps/weather/metadata.json | 2 +- 24 files changed, 39 insertions(+), 44 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index cb928213e..77e11c92e 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -8,3 +8,4 @@ 0.08: Fix error in clkinfo (didn't require Storage & locale) Fix clkinfo icon 0.09: Ensure Agenda supplies an image for clkinfo items +0.10: Update clock_info to avoid a redraw diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index 54677327b..7c89446a2 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -20,7 +20,7 @@ agendaItems.items.push({ name: "Agenda "+i, get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }), - show: function() { agendaItems.items[i].emit("redraw"); }, + show: function() {}, hide: function () {} }); }); diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 58a5091cd..8253b36bc 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -1,7 +1,7 @@ { "id": "agenda", "name": "Agenda", - "version": "0.09", + "version": "0.10", "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index 546c83894..e3e059318 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -20,4 +20,5 @@ 0.20: Better handling of async data such as getPressure. 0.21: On the default menu the week of year can be shown. 0.22: Use the new clkinfo module for the menu. -0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo. \ No newline at end of file +0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo. +0.24: Update clock_info to avoid a redraw diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index 7dcca9d75..c29fdf2ef 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -93,7 +93,7 @@ var bwItems = { items: [ { name: "WeekOfYear", get: () => ({ text: "Week " + weekOfYear(), img: null}), - show: function() { bwItems.items[0].emit("redraw"); }, + show: function() {}, hide: function () {} }, ] diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index fa0f7b01f..8ef812f41 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.23", + "version": "0.24", "description": "A very minimalistic clock to mainly show date and time.", "readme": "README.md", "icon": "app.png", diff --git a/apps/clkinfofw/ChangeLog b/apps/clkinfofw/ChangeLog index 7b83706bf..10810802b 100644 --- a/apps/clkinfofw/ChangeLog +++ b/apps/clkinfofw/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Update clock_info to avoid a redraw and image allocation diff --git a/apps/clkinfofw/clkinfo.js b/apps/clkinfofw/clkinfo.js index 9815ca87f..2b3cb32ba 100644 --- a/apps/clkinfofw/clkinfo.js +++ b/apps/clkinfofw/clkinfo.js @@ -4,26 +4,13 @@ items: [ { name : "FW", get : () => { - let d = new Date(); - let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); - g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0); return { text : process.env.VERSION, - img : g.asImage("string") + img : atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV") }; }, - show : function() { - this.interval = setTimeout(()=>{ - this.emit("redraw"); - this.interval = setInterval(()=>{ - this.emit("redraw"); - }, 86400000); - }, 86400000 - (Date.now() % 86400000)); - }, - hide : function() { - clearInterval(this.interval); - this.interval = undefined; - } + show : function() {}, + hide : function() {} } ] }; diff --git a/apps/clkinfofw/metadata.json b/apps/clkinfofw/metadata.json index 924297ca3..720a5baa5 100644 --- a/apps/clkinfofw/metadata.json +++ b/apps/clkinfofw/metadata.json @@ -1,6 +1,6 @@ { "id": "clkinfofw", "name": "Firmware Clockinfo", - "version":"0.01", + "version":"0.02", "description": "For clocks that display 'clockinfo', this displays the firmware version string", "icon": "app.png", "type": "clkinfo", diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog index f9ca3c16d..c0d58e5bc 100644 --- a/apps/ha/ChangeLog +++ b/apps/ha/ChangeLog @@ -3,4 +3,5 @@ 0.03: Added clkinfo for clocks. 0.04: Feedback if clkinfo run is called. 0.05: Clkinfo improvements. -0.06: Updated clkinfo icon. \ No newline at end of file +0.06: Updated clkinfo icon. +0.07: Update clock_info to avoid a redraw diff --git a/apps/ha/ha.clkinfo.js b/apps/ha/ha.clkinfo.js index d6a0f72a0..09724ba45 100644 --- a/apps/ha/ha.clkinfo.js +++ b/apps/ha/ha.clkinfo.js @@ -12,7 +12,7 @@ haItems.items.push({ name: null, get: () => ({ text: trigger.display, img: trigger.getIcon()}), - show: function() { haItems.items[i].emit("redraw"); }, + show: function() {}, hide: function () {}, run: function() { ha.sendTrigger("TRIGGER_BW"); @@ -23,4 +23,4 @@ }); return haItems; -}) \ No newline at end of file +}) diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json index 089450f55..1432e010e 100644 --- a/apps/ha/metadata.json +++ b/apps/ha/metadata.json @@ -1,7 +1,7 @@ { "id": "ha", "name": "HomeAssistant", - "version": "0.06", + "version": "0.07", "description": "Integrates your BangleJS into HomeAssistant.", "icon": "ha.png", "type": "app", diff --git a/apps/linuxclock/ChangeLog b/apps/linuxclock/ChangeLog index 3f1ef5c55..1c4f7d79b 100644 --- a/apps/linuxclock/ChangeLog +++ b/apps/linuxclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App. -0.02: Performance improvements. \ No newline at end of file +0.02: Performance improvements. +0.03: Update clock_info to avoid a redraw diff --git a/apps/linuxclock/app.js b/apps/linuxclock/app.js index 02676310e..9470b803c 100644 --- a/apps/linuxclock/app.js +++ b/apps/linuxclock/app.js @@ -52,22 +52,22 @@ var H = g.getHeight(); items: [ { name: "time", get: () => ({ text: getTime(), img: null}), - show: function() { dateMenu.items[0].emit("redraw"); }, + show: function() {}, hide: function () {} }, { name: "day", get: () => ({ text: getDay(), img: null}), - show: function() { dateMenu.items[2].emit("redraw"); }, + show: function() {}, hide: function () {} }, { name: "date", get: () => ({ text: getDate(), img: null}), - show: function() { dateMenu.items[1].emit("redraw"); }, + show: function() {}, hide: function () {} }, { name: "week", get: () => ({ text: weekOfYear(), img: null}), - show: function() { dateMenu.items[3].emit("redraw"); }, + show: function() {}, hide: function () {} }, ] diff --git a/apps/linuxclock/metadata.json b/apps/linuxclock/metadata.json index dfb17a315..06ef66498 100644 --- a/apps/linuxclock/metadata.json +++ b/apps/linuxclock/metadata.json @@ -1,7 +1,7 @@ { "id": "linuxclock", "name": "Linux Clock", - "version": "0.02", + "version": "0.03", "description": "A Linux inspired clock.", "readme": "README.md", "icon": "app.png", diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index c99fe9858..b24ba9266 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -1,6 +1,6 @@ 0.01: New App! 0.02: Fix scheduling of other alarms if there is a pending alarm from the past (fix #1667) -0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers. +0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers. 0.04: Fix `getTimeToAlarm` to check for next dow if alarm.t lower currentTime. 0.05: Export new functions (`newDefaultAlarm/Timer`), add Settings page 0.06: Refactor some methods to library @@ -18,3 +18,4 @@ 0.15: Automatic translation of some string in clkinfo 0.16: Improve support for date timezone 0.17: Fix midnight in local timezone (alarms wouldn't always fire as expected in timezone != 0) +0.18: Update clock_info to avoid a redraw diff --git a/apps/sched/clkinfo.js b/apps/sched/clkinfo.js index 3bd11f70b..439784039 100644 --- a/apps/sched/clkinfo.js +++ b/apps/sched/clkinfo.js @@ -63,7 +63,7 @@ hasRange: true, get: () => ({ text: getAlarmText(a), img: getAlarmIcon(a), v: getAlarmValue(a), min:0, max:getAlarmMax(a)}), - show: function() { alarmItems.items[i].emit("redraw"); }, + show: function() {}, hide: function () {}, run: function() { } })), diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 05e829d83..a99e62089 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.17", + "version": "0.18", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog index 12b77aacd..61111482e 100644 --- a/apps/smpltmr/ChangeLog +++ b/apps/smpltmr/ChangeLog @@ -4,3 +4,4 @@ 0.04: Improvements of clock infos. 0.05: Updated clkinfo icon. 0.06: Ensure Timer supplies an image for clkinfo items +0.07: Update clock_info to avoid a redraw diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js index 270a14fc4..ac01cfb59 100644 --- a/apps/smpltmr/clkinfo.js +++ b/apps/smpltmr/clkinfo.js @@ -70,7 +70,7 @@ { name: null, get: () => ({ text: getAlarmMinutesText() + (isAlarmEnabled() ? " min" : ""), img: smpltmrItems.img }), - show: function() { smpltmrItems.items[0].emit("redraw"); }, + show: function() {}, hide: function () {}, run: function() { } }, @@ -82,7 +82,7 @@ smpltmrItems.items = smpltmrItems.items.concat({ name: null, get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }), - show: function() { smpltmrItems.items[i+1].emit("redraw"); }, + show: function() {}, hide: function () {}, run: function() { if(o > 0) increaseAlarm(o); diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json index 71e793cc2..b0d1a34da 100644 --- a/apps/smpltmr/metadata.json +++ b/apps/smpltmr/metadata.json @@ -2,7 +2,7 @@ "id": "smpltmr", "name": "Simple Timer", "shortName": "Simple Timer", - "version": "0.06", + "version": "0.07", "description": "A very simple app to start a timer.", "icon": "app.png", "tags": "tool,alarm,timer,clkinfo", diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index f1d001c81..4b70d3531 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -19,3 +19,4 @@ 0.20: Added weather condition with temperature to clkinfo. 0.21: Updated clkinfo icon. 0.22: Automatic translation of strings, some left untranslated. +0.23: Update clock_info to avoid a redraw diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js index 3cdd31c59..f40924e06 100644 --- a/apps/weather/clkinfo.js +++ b/apps/weather/clkinfo.js @@ -34,14 +34,14 @@ name: "conditionWithTemperature", get: () => ({ text: weather.temp, img: weatherIcon(weather.code), v: parseInt(weather.temp), min: -30, max: 55}), - show: function() { this.emit("redraw"); }, + show: function() {}, hide: function () {} }, { name: "condition", get: () => ({ text: weather.txt, img: weatherIcon(weather.code), v: weather.code}), - show: function() { this.emit("redraw"); }, + show: function() {}, hide: function () {} }, { @@ -49,7 +49,7 @@ hasRange : true, get: () => ({ text: weather.temp, img: atob("GBiBAAA8AAB+AADnAADDAADDAADDAADDAADDAADbAADbAADbAADbAADbAADbAAHbgAGZgAM8wAN+wAN+wAM8wAGZgAHDgAD/AAA8AA=="), v: parseInt(weather.temp), min: -30, max: 55}), - show: function() { this.emit("redraw"); }, + show: function() {}, hide: function () {} }, { @@ -57,7 +57,7 @@ hasRange : true, get: () => ({ text: weather.hum, img: atob("GBiBAAAEAAAMAAAOAAAfAAAfAAA/gAA/gAI/gAY/AAcfAA+AQA+A4B/A4D/B8D/h+D/j+H/n/D/n/D/n/B/H/A+H/AAH/AAD+AAA8A=="), v: parseInt(weather.hum), min: 0, max: 100}), - show: function() { this.emit("redraw"); }, + show: function() {}, hide: function () {} }, { @@ -65,7 +65,7 @@ hasRange : true, get: () => ({ text: weather.wind, img: atob("GBiBAAHgAAPwAAYYAAwYAAwMfAAY/gAZh3/xg//hgwAAAwAABg///g//+AAAAAAAAP//wH//4AAAMAAAMAAYMAAYMAAMcAAP4AADwA=="), v: parseInt(weather.wind), min: 0, max: 118}), - show: function() { this.emit("redraw"); }, + show: function() {}, hide: function () {} }, ] diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json index 7fefb7685..77ca37721 100644 --- a/apps/weather/metadata.json +++ b/apps/weather/metadata.json @@ -1,7 +1,7 @@ { "id": "weather", "name": "Weather", - "version": "0.22", + "version": "0.23", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], From 67e269486bab419ac7f2968484f55beda2ecd14b Mon Sep 17 00:00:00 2001 From: Mo Abrahams <3750013+dashavoo@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:56:41 +0000 Subject: [PATCH 52/82] Add Xiaomi Mijia temperature sensor display --- apps/mitherm/ChangeLog | 1 + apps/mitherm/README.md | 22 +++++ apps/mitherm/app-icon.js | 1 + apps/mitherm/app.js | 172 +++++++++++++++++++++++++++++++++++++ apps/mitherm/app.png | Bin 0 -> 863 bytes apps/mitherm/metadata.json | 15 ++++ 6 files changed, 211 insertions(+) create mode 100644 apps/mitherm/ChangeLog create mode 100644 apps/mitherm/README.md create mode 100644 apps/mitherm/app-icon.js create mode 100644 apps/mitherm/app.js create mode 100644 apps/mitherm/app.png create mode 100644 apps/mitherm/metadata.json diff --git a/apps/mitherm/ChangeLog b/apps/mitherm/ChangeLog new file mode 100644 index 000000000..630459c15 --- /dev/null +++ b/apps/mitherm/ChangeLog @@ -0,0 +1 @@ +0.01: Create mitherm app with support for pvvx firmware only diff --git a/apps/mitherm/README.md b/apps/mitherm/README.md new file mode 100644 index 000000000..cdf3daa61 --- /dev/null +++ b/apps/mitherm/README.md @@ -0,0 +1,22 @@ +Reads BLE advertisement data from Xiaomi temperature/humidity sensors running the +`pvvx` custom firmware (https://github.com/pvvx/ATC_MiThermometer). + +## Features + +* Display temperature +* Display humidity +* Display battery state of sensor +* Auto-refresh every 5 minutes +* Manual refresh on demand +* Add aliases for MAC addresses to easily recognise devices + +## Planned features + +* Supprt for other advertising formats: + * atc1441 format + * BTHome + * Xiaomi Mijia format +* Configurable auto-refresh interval +* Configurable scan length (currently 30s) +* Alerts when temperature outside defined limits (with a widget or bootcode to + work when app is inactive) diff --git a/apps/mitherm/app-icon.js b/apps/mitherm/app-icon.js new file mode 100644 index 000000000..2e8737704 --- /dev/null +++ b/apps/mitherm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4Ac5gWVhnM4AWVAAIYTCwQABCywYRIoYADJJwWHDB4RD5sz7hJPFIlP//0MRxFE6f/AAM9JJgWE4gWCAANMDBZcEn4XE+ZiKFwhcBCYPdDYRiEGAoXDLgf97vfMQwXILggXFMQYXHLgoXB6czMQoXHLgQXJMQQXG4YWEI44ABngXGh4XHF4v/+DAGC6DXGC5BHGC509F4IXTdwIABV4gXOIwIABJAoX/C6p3Xa4a/UABAXfgczABswC/4XmAH4A/ABY")) diff --git a/apps/mitherm/app.js b/apps/mitherm/app.js new file mode 100644 index 000000000..b7abdb2fc --- /dev/null +++ b/apps/mitherm/app.js @@ -0,0 +1,172 @@ +var filterTemperature = [{ + serviceData: { + "181a": {} + } +}]; +var results = {}; +var macs = []; + +var aliases = require("Storage").readJSON("mitherm.json", true); +if (!aliases) aliases = {}; + +var lastSeen = {}; +var current = 0; +var scanning = false; +var timeoutDraw; +var timeoutScan; + + +const scan = function() { + if (!scanning) { // Don't start scanning if already doing so. + scanning = true; + if (timeoutScan) clearTimeout(timeoutScan); + timeoutScan = setTimeout(scan, 300000); // Scan again in 5 minutes. + drawScanState(scanning); + NRF.findDevices(function(devices) { + onDevices(devices); + }, { + filters: filterTemperature, + timeout: 30000 // Scan for 30s + }); + } +}; + + +const onDevices = function(devices) { + let now = Date.now(); + for (let i = 0; i < devices.length; i++) { + let device = devices[i]; + + let processedData = extractData(device.data); + console.log({ + rssi: device.rssi, + data: processedData + }); + if (!macs.includes(processedData.MAC)) { + macs.push(processedData.MAC); + } + results[processedData.MAC] = processedData; + lastSeen[processedData.MAC] = now; + } + console.log("Scan complete."); + scanning = false; + writeOutput(); +}; + + +const extractData = function(thedata) { + let data = DataView(thedata); + let MAC = []; + for (let i = 9; i > 3; i--) { + MAC.push(data.getUint8(i, true).toString(16).padStart(2, "0")); + } + out = { + size: data.getUint8(0, true), + uid: data.getUint8(1, true), + UUID: data.getUint16(2, true), + MAC: MAC.join(":"), + temperature: data.getInt16(10, true) * 0.01, + humidity: data.getUint16(12, true) * 0.01, + battery_mv: data.getUint16(14, true), + battery_level: data.getUint8(16, true), + }; + return out; +}; + + +const writeOutput = function() { + let now = Date.now(); + if (timeoutDraw) clearTimeout(timeoutDraw); + timeoutDraw = setTimeout(writeOutput, 60000); // Refresh in 1 minute. + g.clear(true); + Bangle.drawWidgets(); + g.reset(); + drawScanState(scanning); + + if (macs.length == 0) return; + + processedData = results[macs[current]]; + g.setFont12x20(2); + g.drawString(`${processedData.temperature.toFixed(2)}°C`, 10, 30); + g.drawString(`${processedData.humidity.toFixed(2)} %`, 10, 70); + + g.setFont6x15(); + g.drawString(`${((now - lastSeen[macs[current]]) / 60000).toFixed(0)} min ago`, 10, 130); + g.drawString(`${processedData.battery_level} % battery`, 80, 130); + g.drawString(` ${processedData.MAC in aliases ? aliases[processedData.MAC] : processedData.MAC}: ${current + 1} / ${macs.length}`, 10, 150); +}; + + +const scrollDevices = function(directionLR) { + // Swipe left or right to move between devices. + current -= directionLR; // inverted feels a more familiar gesture. + if (current + 1 > macs.length) + current = 0; + if (current < 0) + current = macs.length - 1; + writeOutput(); +}; + +const drawScanState = function(state) { + if (state) + g.fillRect(160, 160, 170, 170); + else + g.clearRect(160, 160, 170, 170); +}; + +const setAlias = function(mac, alias) { + if (alias === "") { + delete aliases[mac]; + } + else { + aliases[mac] = alias; + require("Storage").writeJSON("mitherm.json", aliases); + } +}; + +const changeAlias = function(mac) { + g.clear(); + require("textinput").input((mac in aliases) ? aliases[mac] : "").then(function(text) { + setAlias(mac, text); + setUI(); + writeOutput(); + }); +}; + + +const setUI = function() { + Bangle.setUI({ + mode: "custom", + swipe: scrollDevices, + btn: function() { + E.showMenu(actionsMenu); + } + }); +}; + + +const actionsMenu = { + "": { + "title": "-- Actions --", + "back": function() { + E.showMenu(); + }, + "remove": function() { + setUI(); + writeOutput(); + }, + }, + "Scan now": function() { + scan(); + E.showMenu(); + }, + "Edit alias": function() { + changeAlias(macs[current]); + }, +}; + +setUI(); +Bangle.loadWidgets(); +g.setClipRect(Bangle.appRect); +scan(); +writeOutput(); diff --git a/apps/mitherm/app.png b/apps/mitherm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..81d6bb24f8dd9d8bc1e59ecc20ffbf2744ffd293 GIT binary patch literal 863 zcmV-l1EBngP)C~OT?JZL=V9)^A6*YCOc`M522%(A>ADh3-C*JXLD!YY+l{;(AndvldAp(8 zfjqjdM1RJHNck(w(7dF}0n)B>{$e<;K7jN3BXEHdt*~K>fC*YRs2rf_Iv)(d*>)T7 z`TzjZx33U-Ejm2FDWOJnXFA#b!5xG%1F0WU&0L~YH{md;`_a0Sz zzp$ko{O9ai7Yj>}?0KU&SA02ybZ11>ja&kB{f+X$5M0p_)#3rH6b}G=^1b@p-3Q@} zj=)xbf~poP&17zUQMT**8mN*iGx+qkh`L(&xxf{ygZ=Cs@KyC#ycvO%{HPg|*a1Em zg6rO>?&4|SD-XdH9VK#uWDg{qSNZ~cu#viu;wJZBzdt=(ppGHF7{vGXrfE*Xrd)eK zYYFvdY>4o|#sqcd9B3_}T=0A9AX%}4%brCtR0WidqZt6=~0Vm>iN^ekTt5x~b|@Sm@iJ@=oh0UwVs&1HxH&;52-QgiZT^Rp=EyUa9~Ap-1U z&vcFH!I(Xe$tVtSqR2FrAp+9d_p6N6GR Date: Sun, 18 Dec 2022 11:17:28 +0200 Subject: [PATCH 53/82] Update app.js --- apps/hworldclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index 2eba0fb71..c5ab16b32 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -257,7 +257,7 @@ let draw = function() { minutes = doublenum(dx.getMinutes()); - if (offsets.length === 1) { + if (offsets.length === 1 && false) { let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; From c873bbe0dd6c4aa55d145a6b25b22d4cdf286f08 Mon Sep 17 00:00:00 2001 From: rnist Date: Sun, 18 Dec 2022 11:59:07 +0200 Subject: [PATCH 54/82] Added setting to show single offset small, as if there is more than one --- apps/hworldclock/README.md | 1 + apps/hworldclock/app.js | 3 ++- apps/hworldclock/settings.js | 21 ++++++++++++++------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/hworldclock/README.md b/apps/hworldclock/README.md index 273389775..d8343e5b9 100644 --- a/apps/hworldclock/README.md +++ b/apps/hworldclock/README.md @@ -9,6 +9,7 @@ All this is configurable: - Show seconds only when unlocked (saves battery) / always / do not show seconds - Green color on dark mode (on/off) + - 1 Offset Small: single location shows as small (like more than 1) - Show sun info (on/off) (set your location in the mylocation app) - Rotation degree on swipe (off / 90 / 180 / 270) diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index c5ab16b32..79e916ea9 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -104,6 +104,7 @@ let def = function(value, def) { let settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; secondsMode = def(settings.secondsMode, "when unlocked"); showSunInfo = def(settings.showSunInfo, true); +singleOffsetSmall = def(settings.singleOffsetSmall, false); colorWhenDark = def(settings.colorWhenDark, "green"); rotationTarget = def(settings.rotationTarget, "90"); rotationTarget = parseInt(rotationTarget) || 0; @@ -257,7 +258,7 @@ let draw = function() { minutes = doublenum(dx.getMinutes()); - if (offsets.length === 1 && false) { + if (offsets.length === 1 && !singleOffsetSmall) { let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; diff --git a/apps/hworldclock/settings.js b/apps/hworldclock/settings.js index ad97c161a..fd61baa76 100644 --- a/apps/hworldclock/settings.js +++ b/apps/hworldclock/settings.js @@ -36,13 +36,20 @@ "< Back": () => back(), "Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]), "Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]), - "Show SunInfo": { - value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true), - onchange: v => { - settings.showSunInfo = v; - writeSettings(); - } - }, + "1 Offset Small": { + value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : true), + onchange: v=> { + settings.singleOffsetSmall = v; + writeSettings(); + } + }, + "Show SunInfo": { + value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true), + onchange: v => { + settings.showSunInfo = v; + writeSettings(); + } + }, "Rotation": stringInSettings("rotationTarget", ["off", "90", "180", "270"]), }; From d3e5a95854f19cc1a030d56674926212f8be4d74 Mon Sep 17 00:00:00 2001 From: rnist Date: Sun, 18 Dec 2022 12:06:16 +0200 Subject: [PATCH 55/82] Updated changelog --- apps/hworldclock/ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index 87c7cf288..99727c50b 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -15,4 +15,5 @@ 0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps 0.30: BJS2: swipe seems to be working now 0.31: Tweaking the swipe option; Added mylocation as a dependency. - Remove calls to Bangle.loadWidgets as they are not needed and create warnings \ No newline at end of file + Remove calls to Bangle.loadWidgets as they are not needed and create warnings +0.32: Added setting to show single timezone small, like where multiple ones are configured \ No newline at end of file From 419c123d6dd7c5f47b642b9449d43890bb9d90a4 Mon Sep 17 00:00:00 2001 From: rnist Date: Sun, 18 Dec 2022 12:07:22 +0200 Subject: [PATCH 56/82] Updated version --- apps/hworldclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index 590f1dc86..fe5865ad0 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.31", + "version": "0.32", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", From 8d06e3089800d5aca67ddd59dbd4826654549ecd Mon Sep 17 00:00:00 2001 From: rnist Date: Sun, 18 Dec 2022 12:20:20 +0200 Subject: [PATCH 57/82] Default single offset small to false in the menu --- apps/hworldclock/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hworldclock/settings.js b/apps/hworldclock/settings.js index fd61baa76..98b91dc7b 100644 --- a/apps/hworldclock/settings.js +++ b/apps/hworldclock/settings.js @@ -37,7 +37,7 @@ "Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]), "Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]), "1 Offset Small": { - value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : true), + value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : false), onchange: v=> { settings.singleOffsetSmall = v; writeSettings(); From 45b6108a20b33e22baea445e32f3dc52127c348f Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 18 Dec 2022 13:26:45 +0100 Subject: [PATCH 58/82] Update changelog link to https This fixes Bangle.JS Gadgetbridge from failing to load the ChangeLog with net::ERR_CLEARTEXT_NOT_PERMITTED --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index c525fd963..a6e51192e 100644 --- a/loader.js +++ b/loader.js @@ -49,7 +49,7 @@ 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}) and ${RECOMMENDED_VERSION} is available (see changes). You can update ${fwExtraText}with the instructions here` ,"warning", 20000); + showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (see changes). You can update ${fwExtraText}with the instructions here` ,"warning", 20000); } // check against features shown? filterAppsForDevice(deviceId); From 3e32c00192902af145bcef5fca16abd816b1d9b7 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 14:19:06 +0100 Subject: [PATCH 59/82] choozi - Work indepent of the theme --- apps/choozi/appb2.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js index 5f217f638..0ed4af25d 100644 --- a/apps/choozi/appb2.js +++ b/apps/choozi/appb2.js @@ -81,6 +81,7 @@ function arc(minR, maxR, minAngle, maxAngle) { // draw the arc segments around the perimeter function drawPerimeter() { + g.setBgColor('#000000'); g.clear(); for (var i = 0; i < N; i++) { g.setColor(colours[i%colours.length]); @@ -152,7 +153,7 @@ function choose() { // draw the current value of N in the middle of the screen, with // up/down arrows function drawN() { - g.setColor(g.theme.fg); + g.setColor('#ffffff'); g.setFont("Vector",fontSize); g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2); if (N < maxN) From b128d512a403746051e1d7168d2db62e39b05634 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 14:21:02 +0100 Subject: [PATCH 60/82] choozi - Do not keep backlight on --- apps/choozi/appb2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js index 0ed4af25d..20fbfb18a 100644 --- a/apps/choozi/appb2.js +++ b/apps/choozi/appb2.js @@ -187,7 +187,6 @@ function readN() { } shuffle(colours); // is this really best? -Bangle.setLCDTimeout(0); // keep screen on readN(); drawN(); From bca6722a1be92d2bb7b17a755fa12b4ed110b31e Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 14:23:16 +0100 Subject: [PATCH 61/82] choozi - Better colors for B2 and prefer solid colors --- apps/choozi/appb2.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js index 20fbfb18a..f60c16300 100644 --- a/apps/choozi/appb2.js +++ b/apps/choozi/appb2.js @@ -4,7 +4,8 @@ * James Stanley 2021 */ -var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff']; +var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff']; +var colours2 = ['#808080', '#800080', '#808000', '#008080', '#ff4040', '#40ff40', '#4040ff']; var stepAngle = 0.18; // radians - resolution of polygon var gapAngle = 0.035; // radians - gap between segments @@ -32,12 +33,11 @@ var radians = 2*Math.PI; // radians per circle var defaultN = 3; // default value for N var minN = 2; -var maxN = colours.length; var N; var arclen; // https://www.frankmitchell.org/2015/01/fisher-yates/ -function shuffle (array) { +function shuffle(array) { var i = 0 , j = 0 , temp = null; @@ -187,6 +187,10 @@ function readN() { } shuffle(colours); // is this really best? +shuffle(colours2); +colours=colours.concat(colours2); +var maxN = colours.length; + readN(); drawN(); From 7c8d941e69448cc5a0f8cce7369fdb58bfd2863f Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 15:42:35 +0100 Subject: [PATCH 62/82] choozi - Recombine code for B1/B2 and use setUI --- apps/choozi/README.md | 9 +- apps/choozi/app.js | 52 +++++----- apps/choozi/appb2.js | 211 -------------------------------------- apps/choozi/metadata.json | 3 +- 4 files changed, 35 insertions(+), 240 deletions(-) delete mode 100644 apps/choozi/appb2.js diff --git a/apps/choozi/README.md b/apps/choozi/README.md index f1e4255bc..8d0aa97f4 100644 --- a/apps/choozi/README.md +++ b/apps/choozi/README.md @@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number of players, ensure that each person knows which colour represents them, and then choose a segment. After a short animation, the chosen segment will fill the screen. -You can use Choozi to randomly select an element from any set with 2 to 13 members, +You can use Choozi to randomly select an element from any set with 2 to 14 members, as long as you can define a bijection between members of the set and coloured segments on the Bangle.js display. -## Controls +## Controls Bangle 1 BTN1: increase the number of segments BTN2: choose a segment at random BTN3: decrease the number of segments +## Controls Bangle 2 + +Swipe up/down: increase/decrease the number of segments +BTN1 or tap: choose a segment at random + ## Creator James Stanley diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 1a5b2f17e..8ba040aab 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -5,14 +5,15 @@ * James Stanley 2021 */ -var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff']; +var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff']; +var colours2 = ['#808080', '#800080', '#808000', '#008080', '#ff4040', '#40ff40', '#4040ff']; var stepAngle = 0.18; // radians - resolution of polygon var gapAngle = 0.035; // radians - gap between segments -var perimMin = 110; // px - min. radius of perimeter -var perimMax = 120; // px - max. radius of perimeter +var perimMin = g.getWidth()*0.44; // px - min. radius of perimeter +var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter -var segmentMax = 106; // px - max radius of filled-in segment +var segmentMax = g.getWidth()*0.42; // px - max radius of filled-in segment var segmentStep = 5; // px - step size of segment fill animation var circleStep = 4; // px - step size of circle fill animation @@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec var animStartSteps = 300; // how many steps before it can start slowing? var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate var ballSize = 3; // px - ball radius -var ballTrack = 100; // px - radius of ball path +var ballTrack = g.getWidth()*0.40; // px - radius of ball path -var centreX = 120; // px - centre of screen -var centreY = 120; // px - centre of screen +var centreX = g.getWidth()*0.5; // px - centre of screen +var centreY = g.getWidth()*0.5; // px - centre of screen var fontSize = 50; // px @@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle var defaultN = 3; // default value for N var minN = 2; -var maxN = colours.length; var N; var arclen; @@ -82,6 +82,7 @@ function arc(minR, maxR, minAngle, maxAngle) { // draw the arc segments around the perimeter function drawPerimeter() { + g.setBgColor('#000000'); g.clear(); for (var i = 0; i < N; i++) { g.setColor(colours[i%colours.length]); @@ -131,6 +132,7 @@ function animateChoice(target) { g.fillCircle(x, y, ballSize); oldx=x; oldy=y; + if (process.env.HWVERSION == 2) g.flip(); } } @@ -186,23 +188,23 @@ function readN() { } shuffle(colours); // is this really best? -Bangle.setLCDMode("direct"); -Bangle.setLCDTimeout(0); // keep screen on +shuffle(colours2); +colours=colours.concat(colours2); +var maxN = colours.length; +if (process.env.HWVERSION == 1){ + Bangle.setLCDMode("direct"); + Bangle.setLCDTimeout(0); // keep screen on +} readN(); drawN(); -setWatch(() => { - setN(N+1); - drawN(); -}, BTN1, {repeat:true}); - -setWatch(() => { - writeN(); - drawPerimeter(); - choose(); -}, BTN2, {repeat:true}); - -setWatch(() => { - setN(N-1); - drawN(); -}, BTN3, {repeat:true}); +Bangle.setUI("updown", (v)=>{ + if (!v){ + writeN(); + drawPerimeter(); + choose(); + } else { + setN(N+v); + drawN(); + } +}); diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js deleted file mode 100644 index f60c16300..000000000 --- a/apps/choozi/appb2.js +++ /dev/null @@ -1,211 +0,0 @@ -/* Choozi - Choose people or things at random using Bangle.js. - * Inspired by the "Chwazi" Android app - * - * James Stanley 2021 - */ - -var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff']; -var colours2 = ['#808080', '#800080', '#808000', '#008080', '#ff4040', '#40ff40', '#4040ff']; - -var stepAngle = 0.18; // radians - resolution of polygon -var gapAngle = 0.035; // radians - gap between segments -var perimMin = 80; // px - min. radius of perimeter -var perimMax = 87; // px - max. radius of perimeter - -var segmentMax = 70; // px - max radius of filled-in segment -var segmentStep = 5; // px - step size of segment fill animation -var circleStep = 4; // px - step size of circle fill animation - -// rolling ball animation: -var maxSpeed = 0.08; // rad/sec -var minSpeed = 0.001; // rad/sec -var animStartSteps = 300; // how many steps before it can start slowing? -var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate -var ballSize = 3; // px - ball radius -var ballTrack = 75; // px - radius of ball path - -var centreX = 88; // px - centre of screen -var centreY = 88; // px - centre of screen - -var fontSize = 50; // px - -var radians = 2*Math.PI; // radians per circle - -var defaultN = 3; // default value for N -var minN = 2; -var N; -var arclen; - -// https://www.frankmitchell.org/2015/01/fisher-yates/ -function shuffle(array) { - var i = 0 - , j = 0 - , temp = null; - - for (i = array.length - 1; i > 0; i -= 1) { - j = Math.floor(Math.random() * (i + 1)); - temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } -} - -// draw an arc between radii minR and maxR, and between -// angles minAngle and maxAngle -function arc(minR, maxR, minAngle, maxAngle) { - var step = stepAngle; - var angle = minAngle; - var inside = []; - var outside = []; - var c, s; - while (angle < maxAngle) { - c = Math.cos(angle); - s = Math.sin(angle); - inside.push(centreX+c*minR); // x - inside.push(centreY+s*minR); // y - // outside coordinates are built up in reverse order - outside.unshift(centreY+s*maxR); // y - outside.unshift(centreX+c*maxR); // x - angle += step; - } - c = Math.cos(maxAngle); - s = Math.sin(maxAngle); - inside.push(centreX+c*minR); - inside.push(centreY+s*minR); - outside.unshift(centreY+s*maxR); - outside.unshift(centreX+c*maxR); - - var vertices = inside.concat(outside); - g.fillPoly(vertices, true); -} - -// draw the arc segments around the perimeter -function drawPerimeter() { - g.setBgColor('#000000'); - g.clear(); - for (var i = 0; i < N; i++) { - g.setColor(colours[i%colours.length]); - var minAngle = (i/N)*radians; - arc(perimMin,perimMax,minAngle,minAngle+arclen); - } -} - -// animate a ball rolling around and settling at "target" radians -function animateChoice(target) { - var angle = 0; - var speed = 0; - var oldx = -10; - var oldy = -10; - var decelFromAngle = -1; - var allowDecel = false; - for (var i = 0; true; i++) { - angle = angle + speed; - if (angle > radians) angle -= radians; - if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) { - speed = speed + accel; - if (speed > maxSpeed) { - speed = maxSpeed; - /* when we reach max speed, we know how long it takes - * to accelerate, and therefore how long to decelerate, so - * we can work out what angle to start decelerating from */ - if (decelFromAngle < 0) { - decelFromAngle = target-angle; - while (decelFromAngle < 0) decelFromAngle += radians; - while (decelFromAngle > radians) decelFromAngle -= radians; - } - } - } else { - if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true; - if (allowDecel) speed = speed - accel; - if (speed < minSpeed) speed = minSpeed; - if (speed == minSpeed && angle < target && angle+speed >= target) return; - } - - var r = i/2; - if (r > ballTrack) r = ballTrack; - var x = centreX+Math.cos(angle)*r; - var y = centreY+Math.sin(angle)*r; - g.setColor('#000000'); - g.fillCircle(oldx,oldy,ballSize+1); - g.setColor('#ffffff'); - g.fillCircle(x, y, ballSize); - oldx=x; - oldy=y; - g.flip(); - } -} - -// choose a winning segment and animate its selection -function choose() { - var chosen = Math.floor(Math.random()*N); - var minAngle = (chosen/N)*radians; - var maxAngle = minAngle + arclen; - animateChoice((minAngle+maxAngle)/2); - g.setColor(colours[chosen%colours.length]); - for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep) - arc(i, perimMax, minAngle, maxAngle); - arc(0, perimMax, minAngle, maxAngle); - for (var r = 1; r < segmentMax; r += circleStep) - g.fillCircle(centreX,centreY,r); - g.fillCircle(centreX,centreY,segmentMax); -} - -// draw the current value of N in the middle of the screen, with -// up/down arrows -function drawN() { - g.setColor('#ffffff'); - g.setFont("Vector",fontSize); - g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2); - if (N < maxN) - g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]); - if (N > minN) - g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]); -} - -// update number of segments, with min/max limit, "arclen" update, -// and screen reset -function setN(n) { - N = n; - if (N < minN) N = minN; - if (N > maxN) N = maxN; - arclen = radians/N - gapAngle; - drawPerimeter(); -} - -// save N to choozi.txt -function writeN() { - var file = require("Storage").open("choozi.txt","w"); - file.write(N); -} - -// load N from choozi.txt -function readN() { - var file = require("Storage").open("choozi.txt","r"); - var n = file.readLine(); - if (n !== undefined) setN(parseInt(n)); - else setN(defaultN); -} - -shuffle(colours); // is this really best? -shuffle(colours2); -colours=colours.concat(colours2); -var maxN = colours.length; - -readN(); -drawN(); - -setWatch(() => { - writeN(); - drawPerimeter(); - choose(); -}, BTN1, {repeat:true}); - -Bangle.on('touch', function(zone,e) { - if(e.x>+88){ - setN(N-1); - drawN(); - }else{ - setN(N+1); - drawN(); - } -}); diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json index 79af76fa2..f0d309560 100644 --- a/apps/choozi/metadata.json +++ b/apps/choozi/metadata.json @@ -10,8 +10,7 @@ "allow_emulator": true, "screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}], "storage": [ - {"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]}, - {"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]}, + {"name":"choozi.app.js","url":"app.js"}, {"name":"choozi.img","url":"app-icon.js","evaluate":true} ] } From 4c16e3163141d98f3cec8baeef1b23d168cb4978 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 16:38:07 +0100 Subject: [PATCH 63/82] choozi - Better colors --- apps/choozi/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 8ba040aab..ff8b18e91 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -6,7 +6,7 @@ */ var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff']; -var colours2 = ['#808080', '#800080', '#808000', '#008080', '#ff4040', '#40ff40', '#4040ff']; +var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0']; var stepAngle = 0.18; // radians - resolution of polygon var gapAngle = 0.035; // radians - gap between segments From 5307fc83c7adba71a3c1f97162612c94f42e6638 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 16:38:49 +0100 Subject: [PATCH 64/82] choozi - Bigger segments to make distiguishing dithered colors easier --- apps/choozi/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index ff8b18e91..18d91cd34 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -10,10 +10,10 @@ var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000' var stepAngle = 0.18; // radians - resolution of polygon var gapAngle = 0.035; // radians - gap between segments -var perimMin = g.getWidth()*0.44; // px - min. radius of perimeter +var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter -var segmentMax = g.getWidth()*0.42; // px - max radius of filled-in segment +var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment var segmentStep = 5; // px - step size of segment fill animation var circleStep = 4; // px - step size of circle fill animation @@ -23,7 +23,7 @@ var minSpeed = 0.001; // rad/sec var animStartSteps = 300; // how many steps before it can start slowing? var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate var ballSize = 3; // px - ball radius -var ballTrack = g.getWidth()*0.40; // px - radius of ball path +var ballTrack = perimMin - ballSize*2; // px - radius of ball path var centreX = g.getWidth()*0.5; // px - centre of screen var centreY = g.getWidth()*0.5; // px - centre of screen From f01a673effd8e53879e51108440a674f1176a0f8 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 16:39:11 +0100 Subject: [PATCH 65/82] choozi - Fix direction of UI --- apps/choozi/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 18d91cd34..5370da714 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -204,7 +204,7 @@ Bangle.setUI("updown", (v)=>{ drawPerimeter(); choose(); } else { - setN(N+v); + setN(N-v); drawN(); } }); From 7da2e3857e9aea84f9adfcad1ff429abe4bf73ba Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 16:41:57 +0100 Subject: [PATCH 66/82] choozi - Update screenshots --- apps/choozi/bangle1-choozi-screenshot1.png | Bin 3893 -> 3859 bytes apps/choozi/bangle1-choozi-screenshot2.png | Bin 3822 -> 3718 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/choozi/bangle1-choozi-screenshot1.png b/apps/choozi/bangle1-choozi-screenshot1.png index 104024958033693784f6457771d2343d0eb5340c..ee422ed10d90832816e05b4b34eb4aaffa3f8a40 100644 GIT binary patch delta 3848 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 E9|_G-F#rGn literal 3893 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 eO6=JFMh5d6vj diff --git a/apps/choozi/bangle1-choozi-screenshot2.png b/apps/choozi/bangle1-choozi-screenshot2.png index f3b6868bf9587e9939785ea901c57213dad56281..20edf4c7860746c8005d5e41793174a324cde94f 100644 GIT binary patch delta 3705 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 Jy#?*s{{cC|)F}V} From f8d241bac4dab65f899e8749d186bee005b6fbfc Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 20:08:01 +0100 Subject: [PATCH 67/82] choozi - Prefer undithered colors only on B2 --- apps/choozi/app.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 5370da714..4c3f47071 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -187,9 +187,16 @@ function readN() { else setN(defaultN); } -shuffle(colours); // is this really best? -shuffle(colours2); -colours=colours.concat(colours2); + +if (process.env.HWVERSION == 1){ + colours=colours.concat(colours2); + shuffle(colours); +} else { + shuffle(colours); + shuffle(colours2); + colours=colours.concat(colours2); +} + var maxN = colours.length; if (process.env.HWVERSION == 1){ Bangle.setLCDMode("direct"); From f467227d8d37b317d80b7bebd567cece7888ffb8 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 20:32:53 +0100 Subject: [PATCH 68/82] choozi - Extract drawing of arcs into lib for others to use --- apps/choozi/app.js | 37 ++++--------------------------------- modules/graphics_utils.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 33 deletions(-) create mode 100644 modules/graphics_utils.js diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 4c3f47071..725c4969a 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -4,7 +4,7 @@ * * James Stanley 2021 */ - +const GU = require("graphics_utils"); var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff']; var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0']; @@ -51,35 +51,6 @@ function shuffle (array) { } } -// draw an arc between radii minR and maxR, and between -// angles minAngle and maxAngle -function arc(minR, maxR, minAngle, maxAngle) { - var step = stepAngle; - var angle = minAngle; - var inside = []; - var outside = []; - var c, s; - while (angle < maxAngle) { - c = Math.cos(angle); - s = Math.sin(angle); - inside.push(centreX+c*minR); // x - inside.push(centreY+s*minR); // y - // outside coordinates are built up in reverse order - outside.unshift(centreY+s*maxR); // y - outside.unshift(centreX+c*maxR); // x - angle += step; - } - c = Math.cos(maxAngle); - s = Math.sin(maxAngle); - inside.push(centreX+c*minR); - inside.push(centreY+s*minR); - outside.unshift(centreY+s*maxR); - outside.unshift(centreX+c*maxR); - - var vertices = inside.concat(outside); - g.fillPoly(vertices, true); -} - // draw the arc segments around the perimeter function drawPerimeter() { g.setBgColor('#000000'); @@ -87,7 +58,7 @@ function drawPerimeter() { for (var i = 0; i < N; i++) { g.setColor(colours[i%colours.length]); var minAngle = (i/N)*radians; - arc(perimMin,perimMax,minAngle,minAngle+arclen); + GU.drawArc(g, perimMin,perimMax,minAngle,minAngle+arclen); } } @@ -144,8 +115,8 @@ function choose() { animateChoice((minAngle+maxAngle)/2); g.setColor(colours[chosen%colours.length]); for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep) - arc(i, perimMax, minAngle, maxAngle); - arc(0, perimMax, minAngle, maxAngle); + GU.drawArc(g, i, perimMax, minAngle, maxAngle); + GU.drawArc(g, 0, perimMax, minAngle, maxAngle); for (var r = 1; r < segmentMax; r += circleStep) g.fillCircle(centreX,centreY,r); g.fillCircle(centreX,centreY,segmentMax); diff --git a/modules/graphics_utils.js b/modules/graphics_utils.js new file mode 100644 index 000000000..8e4b893ba --- /dev/null +++ b/modules/graphics_utils.js @@ -0,0 +1,27 @@ +// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle +exports.drawArc = function(graphics, minR, maxR, minAngle, maxAngle) { + var step = stepAngle; + var angle = minAngle; + var inside = []; + var outside = []; + var c, s; + while (angle < maxAngle) { + c = Math.cos(angle); + s = Math.sin(angle); + inside.push(centreX+c*minR); // x + inside.push(centreY+s*minR); // y + // outside coordinates are built up in reverse order + outside.unshift(centreY+s*maxR); // y + outside.unshift(centreX+c*maxR); // x + angle += step; + } + c = Math.cos(maxAngle); + s = Math.sin(maxAngle); + inside.push(centreX+c*minR); + inside.push(centreY+s*minR); + outside.unshift(centreY+s*maxR); + outside.unshift(centreX+c*maxR); + + var vertices = inside.concat(outside); + graphics.fillPoly(vertices, true); +} \ No newline at end of file From 6539dc48b1985f4cafbe39c00df97f194e458ad6 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 21:05:24 +0100 Subject: [PATCH 69/82] choozi - Save into normal file instead of storage file --- apps/choozi/app.js | 11 ++++------- apps/choozi/metadata.json | 3 +++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 725c4969a..9842c8d5a 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -144,21 +144,18 @@ function setN(n) { drawPerimeter(); } -// save N to choozi.txt +// save N to choozi.save function writeN() { - var file = require("Storage").open("choozi.txt","w"); - file.write(N); + require("Storage").write("choozi.save","" + N); } -// load N from choozi.txt +// load N from choozi.save function readN() { - var file = require("Storage").open("choozi.txt","r"); - var n = file.readLine(); + var n = require("Storage").read("choozi.save"); if (n !== undefined) setN(parseInt(n)); else setN(defaultN); } - if (process.env.HWVERSION == 1){ colours=colours.concat(colours2); shuffle(colours); diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json index f0d309560..d997534e8 100644 --- a/apps/choozi/metadata.json +++ b/apps/choozi/metadata.json @@ -12,5 +12,8 @@ "storage": [ {"name":"choozi.app.js","url":"app.js"}, {"name":"choozi.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"choozi.save"} ] } From 6204206df321fbfe268379acc34c630ed7e5ba1d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 21:11:59 +0100 Subject: [PATCH 70/82] choozi - Only write if actually changed --- apps/choozi/app.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 9842c8d5a..478cef009 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -146,14 +146,19 @@ function setN(n) { // save N to choozi.save function writeN() { - require("Storage").write("choozi.save","" + N); + var savedN = read(); + if (savedN != N) require("Storage").write("choozi.save","" + N); +} + +function read(){ + var n = require("Storage").read("choozi.save"); + if (n !== undefined) return parseInt(n); + return defaultN; } // load N from choozi.save function readN() { - var n = require("Storage").read("choozi.save"); - if (n !== undefined) setN(parseInt(n)); - else setN(defaultN); + setN(read()); } if (process.env.HWVERSION == 1){ From c0be24a5520a444c229d9ab2ffc031b48b8fe2d9 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 21:27:33 +0100 Subject: [PATCH 71/82] choozi - New icons --- apps/choozi/app-icon.js | 2 +- apps/choozi/app.png | Bin 7307 -> 551 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/choozi/app-icon.js b/apps/choozi/app-icon.js index 51b3bead3..560286098 100644 --- a/apps/choozi/app-icon.js +++ b/apps/choozi/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA==")) +require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4=")) \ No newline at end of file diff --git a/apps/choozi/app.png b/apps/choozi/app.png index 99c9fa07a55195ec45adbc70cc6e3521cdccaeda..50f09f164b2331133498bd8a3b762ccc09bea20f 100644 GIT binary patch delta 527 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 Date: Sun, 18 Dec 2022 21:46:05 +0100 Subject: [PATCH 72/82] choozi - Fix selection animation on B2 --- apps/choozi/app.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 478cef009..577ef606a 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -114,11 +114,15 @@ function choose() { var maxAngle = minAngle + arclen; animateChoice((minAngle+maxAngle)/2); g.setColor(colours[chosen%colours.length]); - for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep) + for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){ GU.drawArc(g, i, perimMax, minAngle, maxAngle); + if (process.env.HWVERSION == 2) g.flip(); + } GU.drawArc(g, 0, perimMax, minAngle, maxAngle); - for (var r = 1; r < segmentMax; r += circleStep) + for (var r = 1; r < segmentMax; r += circleStep){ g.fillCircle(centreX,centreY,r); + if (process.env.HWVERSION == 2) g.flip(); + } g.fillCircle(centreX,centreY,segmentMax); } From 68523875a8e0c1476bf65d5c3832fc6449059641 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 18 Dec 2022 22:17:23 +0100 Subject: [PATCH 73/82] choozi - Bump version --- apps/choozi/ChangeLog | 6 ++++++ apps/choozi/metadata.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/choozi/ChangeLog b/apps/choozi/ChangeLog index 03f7ef832..35adc7430 100644 --- a/apps/choozi/ChangeLog +++ b/apps/choozi/ChangeLog @@ -1,3 +1,9 @@ 0.01: New App! 0.02: Support Bangle.js 2 0.03: Fix bug for Bangle.js 2 where g.flip was not being called. +0.04: Combine code for both apps + Better colors for Bangle.js 2 + Fix selection animation for Bangle.js 2 + New icon + Slightly wider arc segments for better visibility + Extract arc drawing code in library diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json index d997534e8..c42abe079 100644 --- a/apps/choozi/metadata.json +++ b/apps/choozi/metadata.json @@ -1,7 +1,7 @@ { "id": "choozi", "name": "Choozi", - "version": "0.03", + "version": "0.04", "description": "Choose people or things at random using Bangle.js.", "icon": "app.png", "tags": "tool", From 285b03dbb9fc632ffb35116b001855216baa29eb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 19 Dec 2022 09:18:45 +0000 Subject: [PATCH 74/82] Ensure that widgets_utils swipeOn falls back on Bangle.js 1 - fix #2414 --- modules/widget_utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/widget_utils.js b/modules/widget_utils.js index 33fd303f9..154a95f68 100644 --- a/modules/widget_utils.js +++ b/modules/widget_utils.js @@ -53,8 +53,14 @@ exports.cleanup = function() { back onscreen with a downwards swipe. Use .show to undo. First parameter controls automatic hiding time, 0 equals not hiding at all. Default value is 2000ms until hiding. -Bangle.js 2 only at the moment. */ +Bangle.js 2 only at the moment. On Bangle.js 1 widgets will be hidden permanently. + +Note: On Bangle.js 1 is is possible to draw widgets in an offscreen area of the LCD +and use Bangle.setLCDOffset. However we can't detect a downward swipe so how to +actually make this work needs some thought. +*/ exports.swipeOn = function(autohide) { + if (process.env.HWVERSION!==2) return exports.hide(); exports.cleanup(); if (!global.WIDGETS) return; exports.autohide=autohide===undefined?2000:autohide; From 5b6eed5f9ce03837d854bcec28916b145c2e64c4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 19 Dec 2022 10:17:08 +0000 Subject: [PATCH 75/82] 0.04: Now shows message icons again (#2416) --- apps/widmessages/ChangeLog | 3 ++- apps/widmessages/metadata.json | 2 +- apps/widmessages/widget.js | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog index 709fb19c3..348d49528 100644 --- a/apps/widmessages/ChangeLog +++ b/apps/widmessages/ChangeLog @@ -1,4 +1,5 @@ 0.01: Moved messages widget into standalone widget app 0.02: Fix 'srcs' being defined in global scope Remove library stub -0.03: Fix messages not showing if UI auto-open is disabled \ No newline at end of file +0.03: Fix messages not showing if UI auto-open is disabled +0.04: Now shows message icons again (#2416) diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json index 47a1151da..0e399f71f 100644 --- a/apps/widmessages/metadata.json +++ b/apps/widmessages/metadata.json @@ -1,7 +1,7 @@ { "id": "widmessages", "name": "Message Widget", - "version": "0.03", + "version": "0.04", "description": "Widget showing new messages", "icon": "app.png", "type": "widget", diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js index bbf980fd3..44f525ec8 100644 --- a/apps/widmessages/widget.js +++ b/apps/widmessages/widget.js @@ -22,10 +22,9 @@ let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {}); if (recall!==true || settings.flash) { const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages); - srcs = Object.keys(this.srcs); g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); for(let i = 0; i Date: Mon, 19 Dec 2022 10:26:37 +0000 Subject: [PATCH 76/82] 0.08: Don't completely remove the lock widget when screen unlocked (use 1px) to ensure appRect/drawWidgets still thinks there are widgets (fix #2409, #2413) --- apps/widlock/ChangeLog | 1 + apps/widlock/metadata.json | 2 +- apps/widlock/widget.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/widlock/ChangeLog b/apps/widlock/ChangeLog index 665750150..abceba48e 100644 --- a/apps/widlock/ChangeLog +++ b/apps/widlock/ChangeLog @@ -5,3 +5,4 @@ 0.05: Set sortorder to -10 so that others can take -1 etc 0.06: Set sortorder to -10 in widget code 0.07: Remove check for .isLocked (extremely old firmwares), speed up widget loading +0.08: Don't completely remove the lock widget when screen unlocked (use 1px) to ensure appRect/drawWidgets still thinks there are widgets diff --git a/apps/widlock/metadata.json b/apps/widlock/metadata.json index db532851a..509a5b7a5 100644 --- a/apps/widlock/metadata.json +++ b/apps/widlock/metadata.json @@ -1,7 +1,7 @@ { "id": "widlock", "name": "Lock Widget", - "version": "0.07", + "version": "0.08", "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked", "icon": "widget.png", "type": "widget", diff --git a/apps/widlock/widget.js b/apps/widlock/widget.js index abcedde20..b5787e09d 100644 --- a/apps/widlock/widget.js +++ b/apps/widlock/widget.js @@ -1,8 +1,8 @@ Bangle.on("lock", function() { - WIDGETS["lock"].width = Bangle.isLocked()?16:0; + WIDGETS["lock"].width = Bangle.isLocked()?16:1; Bangle.drawWidgets(); }); -WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:0,draw:function(w) { +WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:1,draw:function(w) { if (Bangle.isLocked()) g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4); }}; From d8543f4d41cbc991aa72b27a3430e19a0f240da1 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 19 Dec 2022 12:52:33 +0100 Subject: [PATCH 77/82] choozi - Fix library not working standalone --- apps/choozi/app.js | 6 +++--- modules/graphics_utils.js | 32 ++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 577ef606a..517628470 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -58,7 +58,7 @@ function drawPerimeter() { for (var i = 0; i < N; i++) { g.setColor(colours[i%colours.length]); var minAngle = (i/N)*radians; - GU.drawArc(g, perimMin,perimMax,minAngle,minAngle+arclen); + GU.drawArc(g, perimMin,perimMax,minAngle,minAngle+arclen, centreX, centreY, stepAngle); } } @@ -115,10 +115,10 @@ function choose() { animateChoice((minAngle+maxAngle)/2); g.setColor(colours[chosen%colours.length]); for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){ - GU.drawArc(g, i, perimMax, minAngle, maxAngle); + GU.drawArc(g, i, perimMax, minAngle, maxAngle, centreX, centreY, stepAngle); if (process.env.HWVERSION == 2) g.flip(); } - GU.drawArc(g, 0, perimMax, minAngle, maxAngle); + GU.drawArc(g, 0, perimMax, minAngle, maxAngle, centreX, centreY, stepAngle); for (var r = 1; r < segmentMax; r += circleStep){ g.fillCircle(centreX,centreY,r); if (process.env.HWVERSION == 2) g.flip(); diff --git a/modules/graphics_utils.js b/modules/graphics_utils.js index 8e4b893ba..925502afb 100644 --- a/modules/graphics_utils.js +++ b/modules/graphics_utils.js @@ -1,6 +1,6 @@ -// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle -exports.drawArc = function(graphics, minR, maxR, minAngle, maxAngle) { - var step = stepAngle; +// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle, all angles are radians +exports.drawArc = function(graphics, minR, maxR, minAngle, maxAngle, X, Y, stepAngle) { + var step = stepAngle || 0.2; var angle = minAngle; var inside = []; var outside = []; @@ -8,20 +8,28 @@ exports.drawArc = function(graphics, minR, maxR, minAngle, maxAngle) { while (angle < maxAngle) { c = Math.cos(angle); s = Math.sin(angle); - inside.push(centreX+c*minR); // x - inside.push(centreY+s*minR); // y + inside.push(X+c*minR); // x + inside.push(Y+s*minR); // y // outside coordinates are built up in reverse order - outside.unshift(centreY+s*maxR); // y - outside.unshift(centreX+c*maxR); // x + outside.unshift(Y+s*maxR); // y + outside.unshift(X+c*maxR); // x angle += step; } c = Math.cos(maxAngle); s = Math.sin(maxAngle); - inside.push(centreX+c*minR); - inside.push(centreY+s*minR); - outside.unshift(centreY+s*maxR); - outside.unshift(centreX+c*maxR); - + inside.push(X+c*minR); + inside.push(Y+s*minR); + outside.unshift(Y+s*maxR); + outside.unshift(X+c*maxR); + var vertices = inside.concat(outside); graphics.fillPoly(vertices, true); +} + +exports.degreesToRadians = function(degrees){ + return Math.PI/180 * degrees; +} + +exports.radiansToDegrees = function(radians){ + return 180/Math.PI * degrees; } \ No newline at end of file From bc20ce3f5bd1e0fd02699e1d6b6458f5e5bad9c5 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 19 Dec 2022 12:53:01 +0100 Subject: [PATCH 78/82] choozi - Correct README file --- apps/choozi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/choozi/README.md b/apps/choozi/README.md index 8d0aa97f4..ccaa97a27 100644 --- a/apps/choozi/README.md +++ b/apps/choozi/README.md @@ -11,7 +11,7 @@ the players seated in a circle, set the number of segments equal to the number of players, ensure that each person knows which colour represents them, and then choose a segment. After a short animation, the chosen segment will fill the screen. -You can use Choozi to randomly select an element from any set with 2 to 14 members, +You can use Choozi to randomly select an element from any set with 2 to 15 members, as long as you can define a bijection between members of the set and coloured segments on the Bangle.js display. From 36557e4f5ea5f721f450de7c71e56900ba72335c Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Mon, 19 Dec 2022 13:01:28 +0100 Subject: [PATCH 79/82] choozi - Rename drawArc to fillArc --- apps/choozi/app.js | 6 +++--- modules/graphics_utils.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/choozi/app.js b/apps/choozi/app.js index 517628470..b9f53bc89 100644 --- a/apps/choozi/app.js +++ b/apps/choozi/app.js @@ -58,7 +58,7 @@ function drawPerimeter() { for (var i = 0; i < N; i++) { g.setColor(colours[i%colours.length]); var minAngle = (i/N)*radians; - GU.drawArc(g, perimMin,perimMax,minAngle,minAngle+arclen, centreX, centreY, stepAngle); + GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle); } } @@ -115,10 +115,10 @@ function choose() { animateChoice((minAngle+maxAngle)/2); g.setColor(colours[chosen%colours.length]); for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){ - GU.drawArc(g, i, perimMax, minAngle, maxAngle, centreX, centreY, stepAngle); + GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle); if (process.env.HWVERSION == 2) g.flip(); } - GU.drawArc(g, 0, perimMax, minAngle, maxAngle, centreX, centreY, stepAngle); + GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle); for (var r = 1; r < segmentMax; r += circleStep){ g.fillCircle(centreX,centreY,r); if (process.env.HWVERSION == 2) g.flip(); diff --git a/modules/graphics_utils.js b/modules/graphics_utils.js index 925502afb..5c08188bc 100644 --- a/modules/graphics_utils.js +++ b/modules/graphics_utils.js @@ -1,5 +1,5 @@ -// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle, all angles are radians -exports.drawArc = function(graphics, minR, maxR, minAngle, maxAngle, X, Y, stepAngle) { +// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle centered at X,Y. All angles are radians. +exports.fillArc = function(graphics, X, Y, minR, maxR, minAngle, maxAngle, stepAngle) { var step = stepAngle || 0.2; var angle = minAngle; var inside = []; From 67e91e6873576afc00203ddecfd4690bb2cd1d39 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 19 Dec 2022 12:09:30 +0000 Subject: [PATCH 80/82] 0.08: Stability improvements - ensure we continue even if a flat string can't be allocated Stop ClockInfo text drawing outside the allocated area Fix https://github.com/espruino/BangleApps/issues/2394 --- apps/slopeclockpp/ChangeLog | 2 ++ apps/slopeclockpp/app.js | 26 +++++++++++++++----------- apps/slopeclockpp/metadata.json | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/slopeclockpp/ChangeLog b/apps/slopeclockpp/ChangeLog index eef1840ea..58299b236 100644 --- a/apps/slopeclockpp/ChangeLog +++ b/apps/slopeclockpp/ChangeLog @@ -7,3 +7,5 @@ 0.05: Images in clkinfo are optional now 0.06: Added support for locale based time 0.07: README file update as UI interaction was not easy to understand +0.08: Stability improvements - ensure we continue even if a flat string can't be allocated + Stop ClockInfo text drawing outside the allocated area diff --git a/apps/slopeclockpp/app.js b/apps/slopeclockpp/app.js index bf719344e..4535d7a79 100644 --- a/apps/slopeclockpp/app.js +++ b/apps/slopeclockpp/app.js @@ -1,7 +1,7 @@ Graphics.prototype.setFontPaytoneOne = function(scale) { // Actual height 71 (81 - 11) this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('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'))), + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAAAAAD/wAAAAAAAAAAAAAA//gAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/+AAAAAAAAAAAAAD//8AAAAAAAAAAAAAf//wAAAAAAAAAAAAB///gAAAAAAAAAAAAH//+AAAAAAAAAAAAAf//4AAAAAAAAAAAAD///gAAAAAAAAAAAAP//+AAAAAAAAAAAAA///4AAAAAAAAAAAAD///gAAAAAAAAAAAAH//+AAAAAAAAAAAAAf//4AAAAAAAAAAAAB///gAAAAAAAAAAAAD//8AAAAAAAAAAAAAP//wAAAAAAAAAAAAAf/+AAAAAAAAAAAAAA//wAAAAAAAAAAAAAB/+AAAAAAAAAAAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAH8AAAAAAAAAAAAAAD/wAAAAAAAAAAAAAA//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAP//wAAAAAAAAAAAAD///AAAAAAAAAAAAB///8AAAAAAAAAAAAf///wAAAAAAAAAAAP////AAAAAAAAAAAD////8AAAAAAAAAAB/////wAAAAAAAAAA//////AAAAAAAAAAP/////8AAAAAAAAAH//////wAAAAAAAAB///////AAAAAAAAA///////8AAAAAAAAf///////wAAAAAAAH////////AAAAAAAD////////wAAAAAAA////////4AAAAAAAf///////+AAAAAAAP////////AAAAAAAD////////gAAAAAAB////////4AAAAAAAf///////8AAAAAAAB////////AAAAAAAAH///////gAAAAAAAAf//////wAAAAAAAAB//////8AAAAAAAAAH/////+AAAAAAAAAAf/////gAAAAAAAAAB/////wAAAAAAAAAAH////4AAAAAAAAAAAf///+AAAAAAAAAAAB////AAAAAAAAAAAAH///wAAAAAAAAAAAAf//4AAAAAAAAAAAAB//8AAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/gAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAH8AAAAAAAAAAAAAAAfAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAB///4AAAAAAAAAAAA////8AAAAAAAAAAAP////+AAAAAAAAAAD/////+AAAAAAAAAA//////8AAAAAAAAAP//////8AAAAAAAAB///////4AAAAAAAAP///////4AAAAAAAB////////wAAAAAAAP////////gAAAAAAB/////////AAAAAAAP////////+AAAAAAB/////////4AAAAAAP/////////wAAAAAA//////////gAAAAAH/////////+AAAAAA//////////8AAAAAD//////////wAAAAAP//////////gAAAAB//////////+AAAAAH////gB////8AAAAA////gAAf///wAAAAD///wAAAP///AAAAAP//8AAAAP//8AAAAA///gAAAAf//4AAAAD//8AAAAA///gAAAAf//gAAAAB//+AAAAB//+AAAAAH//4AAAAH//4AAAAAP//gAAAAf//AAAAAA//+AAAAB//8AAAAAD//4AAAAH//wAAAAAP//gAAAAf//AAAAAA//+AAAAB//+AAAAAD//4AAAAH//4AAAAAf//gAAAAf//wAAAAD//+AAAAA///gAAAAf//4AAAAD///AAAAD///gAAAAP///AAAA///8AAAAA////AAAP///wAAAAB////gAH////AAAAAH//////////4AAAAAf//////////gAAAAA//////////+AAAAAD//////////wAAAAAH//////////AAAAAAf/////////4AAAAAA//////////AAAAAAB/////////8AAAAAAD/////////gAAAAAAP////////8AAAAAAAf////////gAAAAAAA////////8AAAAAAAB////////gAAAAAAAB///////8AAAAAAAAD///////gAAAAAAAAH//////4AAAAAAAAAH/////+AAAAAAAAAAH/////gAAAAAAAAAAH////4AAAAAAAAAAAD///8AAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/4AAAAB//wAAAAAB//gAAAAH//AAAAAAH/+AAAAAf/8AAAAAAf/4AAAAB//wAAAAAB//AAAAAH//AAAAAAH/8AAAAAf/8AAAAAAf/wAAAAB//wAAAAAD//AAAAAH//AAAAAAP/////////8AAAAAA//////////wAAAAAH//////////AAAAAAf/////////8AAAAAB//////////wAAAAAP//////////AAAAAA//////////8AAAAAH//////////wAAAAAf//////////AAAAAD//////////8AAAAAP//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAD/AAAAAAB//wAAAAAP/gAAAAAP//AAAAAA//wAAAAB//8AAAAAH//4AAAAP//wAAAAAf//AAAAB///AAAAAD//8AAAAP//8AAAAAP//gAAAB///wAAAAA//+AAAAP///AAAAAH//wAAAB///8AAAAAf//AAAAP///wAAAAB//8AAAB////AAAAAH//wAAAP///8AAAAA//+AAAB////wAAAAD//4AAAP////AAAAAP//gAAB////8AAAAA//+AAAP////wAAAAD//4AAB/////AAAAAP//gAAP////8AAAAA//+AAD/////wAAAAD//4AAf/////AAAAAP//wAH/////8AAAAA///gA///9//wAAAAD///Af///3//AAAAAP///////+f/8AAAAA////////x//wAAAAD////////H//AAAAAP///////4f/8AAAAAf///////B//wAAAAB///////4H//AAAAAH///////gf/8AAAAAf//////8B//wAAAAA///////gH//AAAAAD//////8Af/8AAAAAH//////gB//wAAAAAf/////8AH//AAAAAA//////gAf/8AAAAAB/////8AB//wAAAAAD/////gAH//AAAAAAH////8AAf/8AAAAAAP////AAB//wAAAAAAf///wAAH//AAAAAAAf//+AAAf/8AAAAAAAf//AAAB//wAAAAAAAP/AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAB+AAAAAAAAPAAAAAAP/gAAAAAAf8AAAAAA//wAAAAAf/wAAAAAH//gAAAAP//gAAAAAf/+AAAAAf/+AAAAAB//wAAAAB//4AAAAAP//AAAAAH//gAAAAA//8AAAAAP//AAAAAD//gB//AA//8AAAAAf/+AH/8AD//wAAAAB//4Af/wAP//AAAAAH//gB//AAf/+AAAAAf/8AH/8AB//4AAAAD//wAf/wAH//gAAAAP//AD//AAf/+AAAAA//8AP/8AB//4AAAAD//wA//wAH//gAAAAP//AD//AAf/+AAAAA//8AP/+AB//4AAAAD//wA//4AH//gAAAAP//AH//gAf/+AAAAA//8Af/+AB//4AAAAD//wD//8AH//gAAAAP//gP//wA//+AAAAA///B///gH//4AAAAD///f///g///gAAAAP//////////+AAAAA///////////4AAAAD///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAD//////////4AAAAAP//////////gAAAAA////+/////8AAAAAB////5/////wAAAAAD////H////+AAAAAAP///4P////4AAAAAAf///A/////AAAAAAA///4B////4AAAAAAA//+AD////AAAAAAAB//wAH///4AAAAAAAA/4AAP///AAAAAAAAAAAAAP//4AAAAAAAAAAAAAP/+AAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/8AAAAAAAAAAAAAH//wAAAAAAAAAAAAA///AAAAAAAAAAAAAP//8AAAAAAAAAAAAB///wAAAAAAAAAAAAP///AAAAAAAAAAAAD///8AAAAAAAAAAAAf///wAAAAAAAAAAAD////AAAAAAAAAAAA////8AAAAAAAAAAAH////wAAAAAAAAAAB/////AAAAAAAAAAAP////8AAAAAAAAAAB/////wAAAAAAAAAAf/////AAAAAAAAAAD///v/8AAAAAAAAAA///4//wAAAAAAAAAH///D//AAAAAAAAAA///wP/8AAAAAAAAAP//+A//wAAAAAAAAB///gD//AAAAAAAAAf//8AP/8AAAAAAAAD///AA//wAAAAAAAAf//4AD//AAAAAAAAH//+AAP/8AAAAAAAA///wAA//wAAAAAAAH//8AAD//AAAAAAAAf//////////wAAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAAAAAAAB///wAB/+AAAAAAf/////gA//8AAAAAP/////+AB//wAAAAA//////8AH//AAAAAD//////wAf/+AAAAAP//////AA//4AAAAA//////+AD//gAAAAD//////4AP//AAAAAP//////wA//8AAAAA///////AB//wAAAAD//////8AH//AAAAAP//////gAf/+AAAAA//////+AB//4AAAAD//////wAH//gAAAAP//////AAf/+AAAAA//8Af/4AB//4AAAAD//gD//gAH//gAAAAP/+AP/+AAf/+AAAAA//4A//wAB//4AAAAD//gH//AAH//gAAAAP/+Af/8AAf/+AAAAA//4B//4AD//4AAAAD//gH//gAP//gAAAAP/+Af//AB//+AAAAA//4B//+AP//4AAAAD//gH//+D///gAAAAP/+Af//////8AAAAA//4B///////wAAAAD//gH///////AAAAAP/+Af//////8AAAAA//4B///////gAAAAD//gD//////+AAAAAP/+AP//////wAAAAA//4A///////AAAAAD//gB//////4AAAAAP/+AH//////gAAAAA//4AP/////8AAAAAD//gAf/////gAAAAAP/+AA/////8AAAAAA//gAD/////gAAAAABgAAAD////8AAAAAAAAAAAH////gAAAAAAAAAAAP///8AAAAAAAAAAAAP///AAAAAAAAAAAAAP//wAAAAAAAAAAAAAD/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAB///4AAAAAAAAAAAA////+AAAAAAAAAAAf////+AAAAAAAAAAH/////+AAAAAAAAAB//////+AAAAAAAAAP//////8AAAAAAAAD///////4AAAAAAAAf///////wAAAAAAAD////////gAAAAAAAf////////AAAAAAAD////////+AAAAAAAf////////8AAAAAAD/////////4AAAAAAf/////////gAAAAAB//////////AAAAAAP/////////8AAAAAB//////////4AAAAAH//////////gAAAAA//////////+AAAAAD//////////8AAAAAP///4f+D///wAAAAB///8D/wB///AAAAAH///AP/AB//+AAAAAf//wB/8AD//4AAAAD//+AP/wAH//gAAAAP//wA/+AAP/+AAAAA//+AH/4AA//4AAAAD//4Af/gAD//gAAAAP//gB/+AAP/+AAAAA//8AP/4AA//4AAAAD//wA//wAD//gAAAAP//AD//AAP/+AAAAA//8AP/8AA//4AAAAD//wA//4AH//gAAAAP//AD//gA//+AAAAA//8AP//AH//4AAAAD//wA///B///AAAAAP//AD//////8AAAAAf/8AP//////wAAAAB//4A///////AAAAAH//gD//////4AAAAAf/+AP//////gAAAAA//8Af/////8AAAAAD//wB//////wAAAAAP//AH/////+AAAAAAf/+AP/////wAAAAAB//4Af/////AAAAAAD//wB/////4AAAAAAP/+AD/////AAAAAAAf/AAH////4AAAAAAB/gAAP////AAAAAAADwAAAf///4AAAAAAAAAAAA////AAAAAAAAAAAAA///wAAAAAAAAAAAAA//8AAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAA//4AAAAAAAAAAAAAD//gAAAAAAAAAAAAAP/+AAAAAAAcAAAAAA//4AAAAAAH4AAAAAD//gAAAAAD/gAAAAAP/+AAAAAA/+AAAAAA//4AAAAAP/8AAAAAD//gAAAAH//wAAAAAP/+AAAAB///gAAAAA//4AAAA///+AAAAAD//gAAAP///4AAAAAP/+AAAH////wAAAAA//4AAB/////AAAAAD//gAAf////+AAAAAP/+AAP/////4AAAAA//4AD//////wAAAAD//gB///////AAAAAP/+Af//////8AAAAA//4P///////4AAAAD//j////////gAAAAP/+////////+AAAAA///////////gAAAAD//////////wAAAAAP/////////8AAAAAA//////////AAAAAAD/////////gAAAAAAP////////4AAAAAAA////////8AAAAAAAD////////AAAAAAAAP///////gAAAAAAAA///////4AAAAAAAAD//////8AAAAAAAAAP//////AAAAAAAAAA//////gAAAAAAAAAD/////4AAAAAAAAAAP////8AAAAAAAAAAA/////AAAAAAAAAAAD////gAAAAAAAAAAAP///4AAAAAAAAAAAA///+AAAAAAAAAAAAD///AAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAA//4AAAAAAAAAAAAAH//4AAAAAAAAAAAAB///wAAAAAAAAAAAAP///gAAAAAAAB/8AB////AAAAAAAAf/+AH///+AAAAAAAH//8A////8AAAAAAA///8H////wAAAAAAH///4f////gAAAAAA////z/////AAAAAAH////v////8AAAAAA//////////wAAAAAD//////////gAAAAAf/////////+AAAAAB//////////8AAAAAP//////////wAAAAA///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////g///gAAAAH/8P///4A//+AAAAA//gP//+AB//4AAAAD/8Af//wAD//gAAAAP/wB///AAP/+AAAAA/+AD//+AAf/4AAAAD/4AP//4AB//gAAAAP/gAf//wAH/+AAAAA/+AB///AAf/4AAAAD/4AD//8AB//gAAAAP/gAP//4AH/+AAAAA//AA///gAf/4AAAAD/8AB///AD//gAAAAP/4AP//+AP/+AAAAA//wD///4A//4AAAAD//5////wH//gAAAAP///////x//+AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAP//////////wAAAAA//////////+AAAAAD//////////4AAAAAH//////////gAAAAAP///3/////8AAAAAA////P/////wAAAAAB///4/////+AAAAAAD///B/////4AAAAAAH//4D/////AAAAAAAP//AH////4AAAAAAAP/wAP////AAAAAAAAHwAA////8AAAAAAAAAAAA////AAAAAAAAAAAAB///4AAAAAAAAAAAAD///AAAAAAAAAAAAAB//gAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAAAAP//wAAAAAAAAAAAAD///wAAAAAAAAAAAAf///gAAAeAAAAAAAH////gAAP8AAAAAAA/////AAH/wAAAAAAH////+AB//gAAAAAA/////8Af/+AAAAAAH/////wA//8AAAAAAf/////gD//wAAAAAD/////+AH//gAAAAAf/////8Af/+AAAAAB//////wB//4AAAAAP//////AD//wAAAAA//////+AP//AAAAAD//////4A//8AAAAAf//////gB//wAAAAB//////+AH//gAAAAH//8H//4Af/+AAAAA///AH//gB//4AAAAD//4AP/+AH//gAAAAP//AAf/4Af/+AAAAA//4AB//gB//4AAAAD//gAD/+AH//gAAAAP/+AAP/4Af/+AAAAA//4AA//gB//4AAAAD//gAD/8AP//gAAAAP/+AAP/wA//+AAAAA//4AA//AD//4AAAAD//gAD/4Af//gAAAAP//AAP/gD//+AAAAA//+AA/8Af//wAAAAD//8AH/wD///AAAAAH//8Af+A///8AAAAAf//+B/wf///gAAAAB//////////+AAAAAH//////////4AAAAAP//////////AAAAAA//////////8AAAAAB//////////gAAAAAH/////////8AAAAAAP/////////wAAAAAA/////////+AAAAAAB/////////wAAAAAAD////////+AAAAAAAH////////wAAAAAAAP///////+AAAAAAAA////////wAAAAAAAA///////+AAAAAAAAB///////gAAAAAAAAD//////8AAAAAAAAAD//////AAAAAAAAAAD/////wAAAAAAAAAAD////4AAAAAAAAAAAB///8AAAAAAAAAAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAeAAAAAAAAAAf+AAAP/AAAAAAAAAH/8AAB//AAAAAAAAA//4AAP/+AAAAAAAAH//wAB//8AAAAAAAAf//gAP//wAAAAAAAD//+AA///gAAAAAAAP//8AH//+AAAAAAAA///wAf//4AAAAAAAH///AB///gAAAAAAAf//8AH///AAAAAAAB///wAf//8AAAAAAAH///AB///wAAAAAAAf//8AH///AAAAAAAA///wAf//4AAAAAAAD///AB///gAAAAAAAP//4AD//+AAAAAAAAf//gAP//4AAAAAAAB//+AAf//AAAAAAAAD//wAB//4AAAAAAAAH/+AAD//AAAAAAAAAP/wAAD/4AAAAAAAAAP8AAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), 46, atob("HTBFLTQ0PzU/Lz8+HQ=="), 100+(scale<<8)+(1<<16) @@ -47,6 +47,15 @@ let bgColor = bgColors[(Math.random()*bgColors.length)|0]||"#000"; // Draw the hour, and the minute into an offscreen buffer let draw = function() { + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + animate(false, function() { + draw(); + }); + }, 60000 - (Date.now() % 60000)); + // Now draw this one R = Bangle.appRect; x = R.w / 2; y = R.y + R.h / 2 - 12; // 12 = room for date @@ -70,15 +79,6 @@ let draw = function() { g2.setColor(0).fillPoly([0,0, g2.getWidth(),0, 0,slope*2]); // start the animation *in* animate(true); - - // queue next draw - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - animate(false, function() { - draw(); - }); - }, 60000 - (Date.now() % 60000)); }; let isAnimIn = true; @@ -123,7 +123,9 @@ let animate = function(isIn, callback) { // clock info menus (scroll up/down for info) let clockInfoDraw = (itm, info, options) => { let texty = options.y+41; - g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty); + // set a cliprect to stop us drawing outside our box + g.reset().setClipRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1); + g.setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty); if (options.focus) g.setColor(options.hl); if (options.x < g.getWidth()/2) { // left align @@ -135,6 +137,8 @@ let clockInfoDraw = (itm, info, options) => { if (info.img) g.clearRect(x-23, options.y, x, options.y+23).drawImage(info.img, x-23, options.y); g.setFontAlign(1,1).drawString(info.text, x,texty); } + // return ClipRect + g.setClipRect(0,0,g.getWidth()-1, g.getHeight()-1); }; let clockInfoItems = require("clock_info").load(); let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#f00"/*red*/ }); diff --git a/apps/slopeclockpp/metadata.json b/apps/slopeclockpp/metadata.json index 3243d389a..fbab02fca 100644 --- a/apps/slopeclockpp/metadata.json +++ b/apps/slopeclockpp/metadata.json @@ -1,6 +1,6 @@ { "id": "slopeclockpp", "name": "Slope Clock ++", - "version":"0.07", + "version":"0.08", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], From 70de58ce24c8aeb05e9e9662be79c23121e87f54 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 19 Dec 2022 12:14:52 +0000 Subject: [PATCH 81/82] Allocate the font bitmap in RAM at startup - saves ~200ms on each redraw --- apps/slopeclockpp/app.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/slopeclockpp/app.js b/apps/slopeclockpp/app.js index 4535d7a79..dca4a84e4 100644 --- a/apps/slopeclockpp/app.js +++ b/apps/slopeclockpp/app.js @@ -1,14 +1,3 @@ -Graphics.prototype.setFontPaytoneOne = function(scale) { - // Actual height 71 (81 - 11) - this.setFontCustom( - atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAAAAAD/wAAAAAAAAAAAAAA//gAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/+AAAAAAAAAAAAAD//8AAAAAAAAAAAAAf//wAAAAAAAAAAAAB///gAAAAAAAAAAAAH//+AAAAAAAAAAAAAf//4AAAAAAAAAAAAD///gAAAAAAAAAAAAP//+AAAAAAAAAAAAA///4AAAAAAAAAAAAD///gAAAAAAAAAAAAH//+AAAAAAAAAAAAAf//4AAAAAAAAAAAAB///gAAAAAAAAAAAAD//8AAAAAAAAAAAAAP//wAAAAAAAAAAAAAf/+AAAAAAAAAAAAAA//wAAAAAAAAAAAAAB/+AAAAAAAAAAAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAH8AAAAAAAAAAAAAAD/wAAAAAAAAAAAAAA//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAP//wAAAAAAAAAAAAD///AAAAAAAAAAAAB///8AAAAAAAAAAAAf///wAAAAAAAAAAAP////AAAAAAAAAAAD////8AAAAAAAAAAB/////wAAAAAAAAAA//////AAAAAAAAAAP/////8AAAAAAAAAH//////wAAAAAAAAB///////AAAAAAAAA///////8AAAAAAAAf///////wAAAAAAAH////////AAAAAAAD////////wAAAAAAA////////4AAAAAAAf///////+AAAAAAAP////////AAAAAAAD////////gAAAAAAB////////4AAAAAAAf///////8AAAAAAAB////////AAAAAAAAH///////gAAAAAAAAf//////wAAAAAAAAB//////8AAAAAAAAAH/////+AAAAAAAAAAf/////gAAAAAAAAAB/////wAAAAAAAAAAH////4AAAAAAAAAAAf///+AAAAAAAAAAAB////AAAAAAAAAAAAH///wAAAAAAAAAAAAf//4AAAAAAAAAAAAB//8AAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/gAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAH8AAAAAAAAAAAAAAAfAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAB///4AAAAAAAAAAAA////8AAAAAAAAAAAP////+AAAAAAAAAAD/////+AAAAAAAAAA//////8AAAAAAAAAP//////8AAAAAAAAB///////4AAAAAAAAP///////4AAAAAAAB////////wAAAAAAAP////////gAAAAAAB/////////AAAAAAAP////////+AAAAAAB/////////4AAAAAAP/////////wAAAAAA//////////gAAAAAH/////////+AAAAAA//////////8AAAAAD//////////wAAAAAP//////////gAAAAB//////////+AAAAAH////gB////8AAAAA////gAAf///wAAAAD///wAAAP///AAAAAP//8AAAAP//8AAAAA///gAAAAf//4AAAAD//8AAAAA///gAAAAf//gAAAAB//+AAAAB//+AAAAAH//4AAAAH//4AAAAAP//gAAAAf//AAAAAA//+AAAAB//8AAAAAD//4AAAAH//wAAAAAP//gAAAAf//AAAAAA//+AAAAB//+AAAAAD//4AAAAH//4AAAAAf//gAAAAf//wAAAAD//+AAAAA///gAAAAf//4AAAAD///AAAAD///gAAAAP///AAAA///8AAAAA////AAAP///wAAAAB////gAH////AAAAAH//////////4AAAAAf//////////gAAAAA//////////+AAAAAD//////////wAAAAAH//////////AAAAAAf/////////4AAAAAA//////////AAAAAAB/////////8AAAAAAD/////////gAAAAAAP////////8AAAAAAAf////////gAAAAAAA////////8AAAAAAAB////////gAAAAAAAB///////8AAAAAAAAD///////gAAAAAAAAH//////4AAAAAAAAAH/////+AAAAAAAAAAH/////gAAAAAAAAAAH////4AAAAAAAAAAAD///8AAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/4AAAAB//wAAAAAB//gAAAAH//AAAAAAH/+AAAAAf/8AAAAAAf/4AAAAB//wAAAAAB//AAAAAH//AAAAAAH/8AAAAAf/8AAAAAAf/wAAAAB//wAAAAAD//AAAAAH//AAAAAAP/////////8AAAAAA//////////wAAAAAH//////////AAAAAAf/////////8AAAAAB//////////wAAAAAP//////////AAAAAA//////////8AAAAAH//////////wAAAAAf//////////AAAAAD//////////8AAAAAP//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAB//wAAAAAAAAAAAAAH//AAAAAAAAAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAD/AAAAAAB//wAAAAAP/gAAAAAP//AAAAAA//wAAAAB//8AAAAAH//4AAAAP//wAAAAAf//AAAAB///AAAAAD//8AAAAP//8AAAAAP//gAAAB///wAAAAA//+AAAAP///AAAAAH//wAAAB///8AAAAAf//AAAAP///wAAAAB//8AAAB////AAAAAH//wAAAP///8AAAAA//+AAAB////wAAAAD//4AAAP////AAAAAP//gAAB////8AAAAA//+AAAP////wAAAAD//4AAB/////AAAAAP//gAAP////8AAAAA//+AAD/////wAAAAD//4AAf/////AAAAAP//wAH/////8AAAAA///gA///9//wAAAAD///Af///3//AAAAAP///////+f/8AAAAA////////x//wAAAAD////////H//AAAAAP///////4f/8AAAAAf///////B//wAAAAB///////4H//AAAAAH///////gf/8AAAAAf//////8B//wAAAAA///////gH//AAAAAD//////8Af/8AAAAAH//////gB//wAAAAAf/////8AH//AAAAAA//////gAf/8AAAAAB/////8AB//wAAAAAD/////gAH//AAAAAAH////8AAf/8AAAAAAP////AAB//wAAAAAAf///wAAH//AAAAAAAf//+AAAf/8AAAAAAAf//AAAB//wAAAAAAAP/AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAB+AAAAAAAAPAAAAAAP/gAAAAAAf8AAAAAA//wAAAAAf/wAAAAAH//gAAAAP//gAAAAAf/+AAAAAf/+AAAAAB//wAAAAB//4AAAAAP//AAAAAH//gAAAAA//8AAAAAP//AAAAAD//gB//AA//8AAAAAf/+AH/8AD//wAAAAB//4Af/wAP//AAAAAH//gB//AAf/+AAAAAf/8AH/8AB//4AAAAD//wAf/wAH//gAAAAP//AD//AAf/+AAAAA//8AP/8AB//4AAAAD//wA//wAH//gAAAAP//AD//AAf/+AAAAA//8AP/+AB//4AAAAD//wA//4AH//gAAAAP//AH//gAf/+AAAAA//8Af/+AB//4AAAAD//wD//8AH//gAAAAP//gP//wA//+AAAAA///B///gH//4AAAAD///f///g///gAAAAP//////////+AAAAA///////////4AAAAD///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////////AAAAAD//////////4AAAAAP//////////gAAAAA////+/////8AAAAAB////5/////wAAAAAD////H////+AAAAAAP///4P////4AAAAAAf///A/////AAAAAAA///4B////4AAAAAAA//+AD////AAAAAAAB//wAH///4AAAAAAAA/4AAP///AAAAAAAAAAAAAP//4AAAAAAAAAAAAAP/+AAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/8AAAAAAAAAAAAAH//wAAAAAAAAAAAAA///AAAAAAAAAAAAAP//8AAAAAAAAAAAAB///wAAAAAAAAAAAAP///AAAAAAAAAAAAD///8AAAAAAAAAAAAf///wAAAAAAAAAAAD////AAAAAAAAAAAA////8AAAAAAAAAAAH////wAAAAAAAAAAB/////AAAAAAAAAAAP////8AAAAAAAAAAB/////wAAAAAAAAAAf/////AAAAAAAAAAD///v/8AAAAAAAAAA///4//wAAAAAAAAAH///D//AAAAAAAAAA///wP/8AAAAAAAAAP//+A//wAAAAAAAAB///gD//AAAAAAAAAf//8AP/8AAAAAAAAD///AA//wAAAAAAAAf//4AD//AAAAAAAAH//+AAP/8AAAAAAAA///wAA//wAAAAAAAH//8AAD//AAAAAAAAf//////////wAAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAB///////////gAAAAH//////////+AAAAAf//////////4AAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAD//AAAAAAAAAAAAAAP/8AAAAAAAAAAAAAA//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAAAAAAAB///wAB/+AAAAAAf/////gA//8AAAAAP/////+AB//wAAAAA//////8AH//AAAAAD//////wAf/+AAAAAP//////AA//4AAAAA//////+AD//gAAAAD//////4AP//AAAAAP//////wA//8AAAAA///////AB//wAAAAD//////8AH//AAAAAP//////gAf/+AAAAA//////+AB//4AAAAD//////wAH//gAAAAP//////AAf/+AAAAA//8Af/4AB//4AAAAD//gD//gAH//gAAAAP/+AP/+AAf/+AAAAA//4A//wAB//4AAAAD//gH//AAH//gAAAAP/+Af/8AAf/+AAAAA//4B//4AD//4AAAAD//gH//gAP//gAAAAP/+Af//AB//+AAAAA//4B//+AP//4AAAAD//gH//+D///gAAAAP/+Af//////8AAAAA//4B///////wAAAAD//gH///////AAAAAP/+Af//////8AAAAA//4B///////gAAAAD//gD//////+AAAAAP/+AP//////wAAAAA//4A///////AAAAAD//gB//////4AAAAAP/+AH//////gAAAAA//4AP/////8AAAAAD//gAf/////gAAAAAP/+AA/////8AAAAAA//gAD/////gAAAAABgAAAD////8AAAAAAAAAAAH////gAAAAAAAAAAAP///8AAAAAAAAAAAAP///AAAAAAAAAAAAAP//wAAAAAAAAAAAAAD/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAB///4AAAAAAAAAAAA////+AAAAAAAAAAAf////+AAAAAAAAAAH/////+AAAAAAAAAB//////+AAAAAAAAAP//////8AAAAAAAAD///////4AAAAAAAAf///////wAAAAAAAD////////gAAAAAAAf////////AAAAAAAD////////+AAAAAAAf////////8AAAAAAD/////////4AAAAAAf/////////gAAAAAB//////////AAAAAAP/////////8AAAAAB//////////4AAAAAH//////////gAAAAA//////////+AAAAAD//////////8AAAAAP///4f+D///wAAAAB///8D/wB///AAAAAH///AP/AB//+AAAAAf//wB/8AD//4AAAAD//+AP/wAH//gAAAAP//wA/+AAP/+AAAAA//+AH/4AA//4AAAAD//4Af/gAD//gAAAAP//gB/+AAP/+AAAAA//8AP/4AA//4AAAAD//wA//wAD//gAAAAP//AD//AAP/+AAAAA//8AP/8AA//4AAAAD//wA//4AH//gAAAAP//AD//gA//+AAAAA//8AP//AH//4AAAAD//wA///B///AAAAAP//AD//////8AAAAAf/8AP//////wAAAAB//4A///////AAAAAH//gD//////4AAAAAf/+AP//////gAAAAA//8Af/////8AAAAAD//wB//////wAAAAAP//AH/////+AAAAAAf/+AP/////wAAAAAB//4Af/////AAAAAAD//wB/////4AAAAAAP/+AD/////AAAAAAAf/AAH////4AAAAAAB/gAAP////AAAAAAADwAAAf///4AAAAAAAAAAAA////AAAAAAAAAAAAA///wAAAAAAAAAAAAA//8AAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAA//4AAAAAAAAAAAAAD//gAAAAAAAAAAAAAP/+AAAAAAAcAAAAAA//4AAAAAAH4AAAAAD//gAAAAAD/gAAAAAP/+AAAAAA/+AAAAAA//4AAAAAP/8AAAAAD//gAAAAH//wAAAAAP/+AAAAB///gAAAAA//4AAAA///+AAAAAD//gAAAP///4AAAAAP/+AAAH////wAAAAA//4AAB/////AAAAAD//gAAf////+AAAAAP/+AAP/////4AAAAA//4AD//////wAAAAD//gB///////AAAAAP/+Af//////8AAAAA//4P///////4AAAAD//j////////gAAAAP/+////////+AAAAA///////////gAAAAD//////////wAAAAAP/////////8AAAAAA//////////AAAAAAD/////////gAAAAAAP////////4AAAAAAA////////8AAAAAAAD////////AAAAAAAAP///////gAAAAAAAA///////4AAAAAAAAD//////8AAAAAAAAAP//////AAAAAAAAAA//////gAAAAAAAAAD/////4AAAAAAAAAAP////8AAAAAAAAAAA/////AAAAAAAAAAAD////gAAAAAAAAAAAP///4AAAAAAAAAAAA///+AAAAAAAAAAAAD///AAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAA//4AAAAAAAAAAAAAH//4AAAAAAAAAAAAB///wAAAAAAAAAAAAP///gAAAAAAAB/8AB////AAAAAAAAf/+AH///+AAAAAAAH//8A////8AAAAAAA///8H////wAAAAAAH///4f////gAAAAAA////z/////AAAAAAH////v////8AAAAAA//////////wAAAAAD//////////gAAAAAf/////////+AAAAAB//////////8AAAAAP//////////wAAAAA///////////AAAAAH//////////8AAAAAf//////////wAAAAB///////g///gAAAAH/8P///4A//+AAAAA//gP//+AB//4AAAAD/8Af//wAD//gAAAAP/wB///AAP/+AAAAA/+AD//+AAf/4AAAAD/4AP//4AB//gAAAAP/gAf//wAH/+AAAAA/+AB///AAf/4AAAAD/4AD//8AB//gAAAAP/gAP//4AH/+AAAAA//AA///gAf/4AAAAD/8AB///AD//gAAAAP/4AP//+AP/+AAAAA//wD///4A//4AAAAD//5////wH//gAAAAP///////x//+AAAAAf//////////wAAAAB///////////AAAAAH//////////8AAAAAP//////////wAAAAA//////////+AAAAAD//////////4AAAAAH//////////gAAAAAP///3/////8AAAAAA////P/////wAAAAAB///4/////+AAAAAAD///B/////4AAAAAAH//4D/////AAAAAAAP//AH////4AAAAAAAP/wAP////AAAAAAAAHwAA////8AAAAAAAAAAAA////AAAAAAAAAAAAB///4AAAAAAAAAAAAD///AAAAAAAAAAAAAB//gAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAAAAP//wAAAAAAAAAAAAD///wAAAAAAAAAAAAf///gAAAeAAAAAAAH////gAAP8AAAAAAA/////AAH/wAAAAAAH////+AB//gAAAAAA/////8Af/+AAAAAAH/////wA//8AAAAAAf/////gD//wAAAAAD/////+AH//gAAAAAf/////8Af/+AAAAAB//////wB//4AAAAAP//////AD//wAAAAA//////+AP//AAAAAD//////4A//8AAAAAf//////gB//wAAAAB//////+AH//gAAAAH//8H//4Af/+AAAAA///AH//gB//4AAAAD//4AP/+AH//gAAAAP//AAf/4Af/+AAAAA//4AB//gB//4AAAAD//gAD/+AH//gAAAAP/+AAP/4Af/+AAAAA//4AA//gB//4AAAAD//gAD/8AP//gAAAAP/+AAP/wA//+AAAAA//4AA//AD//4AAAAD//gAD/4Af//gAAAAP//AAP/gD//+AAAAA//+AA/8Af//wAAAAD//8AH/wD///AAAAAH//8Af+A///8AAAAAf//+B/wf///gAAAAB//////////+AAAAAH//////////4AAAAAP//////////AAAAAA//////////8AAAAAB//////////gAAAAAH/////////8AAAAAAP/////////wAAAAAA/////////+AAAAAAB/////////wAAAAAAD////////+AAAAAAAH////////wAAAAAAAP///////+AAAAAAAA////////wAAAAAAAA///////+AAAAAAAAB///////gAAAAAAAAD//////8AAAAAAAAAD//////AAAAAAAAAAD/////wAAAAAAAAAAD////4AAAAAAAAAAAB///8AAAAAAAAAAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAeAAAAAAAAAAf+AAAP/AAAAAAAAAH/8AAB//AAAAAAAAA//4AAP/+AAAAAAAAH//wAB//8AAAAAAAAf//gAP//wAAAAAAAD//+AA///gAAAAAAAP//8AH//+AAAAAAAA///wAf//4AAAAAAAH///AB///gAAAAAAAf//8AH///AAAAAAAB///wAf//8AAAAAAAH///AB///wAAAAAAAf//8AH///AAAAAAAA///wAf//4AAAAAAAD///AB///gAAAAAAAP//4AD//+AAAAAAAAf//gAP//4AAAAAAAB//+AAf//AAAAAAAAD//wAB//4AAAAAAAAH/+AAD//AAAAAAAAAP/wAAD/4AAAAAAAAAP8AAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), - 46, - atob("HTBFLTQ0PzU/Lz8+HQ=="), - 100+(scale<<8)+(1<<16) - ); - return this; -}; - { // must be inside our own scope here so that when we are unloaded everything disappears // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global @@ -16,6 +5,17 @@ let settings = Object.assign( require("Storage").readJSON("slopeclockpp.default.json", true) || {}, require("Storage").readJSON("slopeclockpp.json", true) || {} ); +const fontBitmap = E.toString(require('heatshrink').decompress(atob('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'))); + +Graphics.prototype.setFontPaytoneOne = function(scale) { + // Actual height 71 (81 - 11) + this.setFontCustom(fontBitmap, + 46, + atob("HTBFLTQ0PzU/Lz8+HQ=="), + 100+(scale<<8)+(1<<16) + ); + return this; +}; let drawTimeout; From c47bc078acf593e3e7554c557c1fa01fd68fc14a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 19 Dec 2022 12:23:31 +0000 Subject: [PATCH 82/82] stability --- apps/slopeclock/ChangeLog | 1 + apps/slopeclock/app.js | 9 ++++++--- apps/slopeclock/metadata.json | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/slopeclock/ChangeLog b/apps/slopeclock/ChangeLog index 2eb04a4f0..da82f6355 100644 --- a/apps/slopeclock/ChangeLog +++ b/apps/slopeclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Reset font to save some memory during remove 0.03: Added support for locale based time +0.04: Stability improvements diff --git a/apps/slopeclock/app.js b/apps/slopeclock/app.js index cc3dce630..2164e7ede 100644 --- a/apps/slopeclock/app.js +++ b/apps/slopeclock/app.js @@ -1,7 +1,12 @@ +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global + +const fontBitmap = E.toString(require('heatshrink').decompress(atob('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'))); + Graphics.prototype.setFontPaytoneOne = function(scale) { // Actual height 81 (91 - 11) this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('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'))), + fontBitmap, 46, atob("ITZOMzs7SDxHNUdGIQ=="), 113+(scale<<8)+(1<<16) @@ -9,8 +14,6 @@ Graphics.prototype.setFontPaytoneOne = function(scale) { return this; }; -{ // must be inside our own scope here so that when we are unloaded everything disappears - // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global let drawTimeout; let g2 = Graphics.createArrayBuffer(g.getWidth(),90,1,{msb:true}); diff --git a/apps/slopeclock/metadata.json b/apps/slopeclock/metadata.json index 6ee78350f..d9d4d85ca 100644 --- a/apps/slopeclock/metadata.json +++ b/apps/slopeclock/metadata.json @@ -1,12 +1,12 @@ { "id": "slopeclock", "name": "Slope Clock", - "version":"0.03", + "version":"0.04", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], "storage": [ {"name":"slopeclock.app.js","url":"app.js"}, {"name":"slopeclock.img","url":"app-icon.js","evaluate":true}