From 5e525f081fbd3ae740503945b9823603eb89600f Mon Sep 17 00:00:00 2001 From: Bruce Blore Date: Wed, 2 Nov 2022 12:06:17 -0700 Subject: [PATCH 001/228] Added bgtimer, gallery, infoclk, pomoplus, random, rpnsci, stlap, stlapview bgtimer: A timer that runs in the background and can be set with a keypad gallery: An app to present a list of images in a menu and allow the user to pick from them infoclk: A clock face that displays more information and some app shortcuts when unlocked pomoplus: A configurable pomodoro timer that runs in the background random: Random number generator rpnsci: Scientific calculator using reverse polish notation stlap: A stopwatch with lap timer that runs in the background stlapview: An optional app that displays data collected by stlap --- apps/bgtimer/app.js | 27 ++ apps/bgtimer/boot.js | 11 + apps/bgtimer/common.js | 42 +++ apps/bgtimer/icon.js | 1 + apps/bgtimer/icon.png | Bin 0 -> 414 bytes apps/bgtimer/img/pause.png | Bin 0 -> 2891 bytes apps/bgtimer/img/play.png | Bin 0 -> 2891 bytes apps/bgtimer/img/reset.png | Bin 0 -> 2891 bytes apps/bgtimer/keypad.js | 136 +++++++++ apps/bgtimer/metadata.json | 44 +++ apps/bgtimer/ring.js | 28 ++ apps/bgtimer/timerview.js | 107 +++++++ apps/gallery/README.md | 18 ++ apps/gallery/app.js | 52 ++++ apps/gallery/icon.js | 1 + apps/gallery/icon.png | Bin 0 -> 249 bytes apps/gallery/metadata.json | 26 ++ apps/infoclk/README.md | 33 ++ apps/infoclk/app.js | 405 +++++++++++++++++++++++++ apps/infoclk/font.js | 23 ++ apps/infoclk/font/am.png | Bin 0 -> 360 bytes apps/infoclk/font/colon.png | Bin 0 -> 216 bytes apps/infoclk/font/digit0.png | Bin 0 -> 290 bytes apps/infoclk/font/digit1.png | Bin 0 -> 183 bytes apps/infoclk/font/digit2.png | Bin 0 -> 305 bytes apps/infoclk/font/digit3.png | Bin 0 -> 270 bytes apps/infoclk/font/digit4.png | Bin 0 -> 247 bytes apps/infoclk/font/digit5.png | Bin 0 -> 302 bytes apps/infoclk/font/digit6.png | Bin 0 -> 309 bytes apps/infoclk/font/digit7.png | Bin 0 -> 227 bytes apps/infoclk/font/digit8.png | Bin 0 -> 309 bytes apps/infoclk/font/digit9.png | Bin 0 -> 319 bytes apps/infoclk/font/pm.png | Bin 0 -> 327 bytes apps/infoclk/icon.js | 2 + apps/infoclk/icon.png | Bin 0 -> 1989 bytes apps/infoclk/metadata.json | 38 +++ apps/infoclk/settings.js | 571 +++++++++++++++++++++++++++++++++++ apps/pomoplus/app.js | 157 ++++++++++ apps/pomoplus/boot.js | 19 ++ apps/pomoplus/common.js | 118 ++++++++ apps/pomoplus/icon.js | 1 + apps/pomoplus/icon.png | Bin 0 -> 2122 bytes apps/pomoplus/img/pause.png | Bin 0 -> 2891 bytes apps/pomoplus/img/play.png | Bin 0 -> 2891 bytes apps/pomoplus/img/reset.png | Bin 0 -> 2891 bytes apps/pomoplus/img/skip.png | Bin 0 -> 2891 bytes apps/pomoplus/metadata.json | 37 +++ apps/pomoplus/settings.js | 94 ++++++ apps/random/app.js | 205 +++++++++++++ apps/random/icon.js | 1 + apps/random/icon.png | Bin 0 -> 378 bytes apps/random/metadata.json | 24 ++ apps/rpnsci/README.md | 38 +++ apps/rpnsci/app.js | 403 ++++++++++++++++++++++++ apps/rpnsci/icon.js | 1 + apps/rpnsci/icon.png | Bin 0 -> 765 bytes apps/rpnsci/icon.xcf | Bin 0 -> 4021 bytes apps/rpnsci/metadata.json | 24 ++ apps/stlap/app.js | 304 +++++++++++++++++++ apps/stlap/icon.js | 1 + apps/stlap/icon.png | Bin 0 -> 1566 bytes apps/stlap/img/pause.png | Bin 0 -> 2891 bytes apps/stlap/img/play.png | Bin 0 -> 2891 bytes apps/stlap/img/reset.png | Bin 0 -> 2891 bytes apps/stlap/metadata.json | 24 ++ apps/stlapview/app.js | 110 +++++++ apps/stlapview/icon.js | 1 + apps/stlapview/icon.png | Bin 0 -> 1566 bytes apps/stlapview/metadata.json | 27 ++ 69 files changed, 3154 insertions(+) create mode 100644 apps/bgtimer/app.js create mode 100644 apps/bgtimer/boot.js create mode 100644 apps/bgtimer/common.js create mode 100644 apps/bgtimer/icon.js create mode 100644 apps/bgtimer/icon.png create mode 100644 apps/bgtimer/img/pause.png create mode 100644 apps/bgtimer/img/play.png create mode 100644 apps/bgtimer/img/reset.png create mode 100644 apps/bgtimer/keypad.js create mode 100644 apps/bgtimer/metadata.json create mode 100644 apps/bgtimer/ring.js create mode 100644 apps/bgtimer/timerview.js create mode 100644 apps/gallery/README.md create mode 100644 apps/gallery/app.js create mode 100644 apps/gallery/icon.js create mode 100644 apps/gallery/icon.png create mode 100644 apps/gallery/metadata.json create mode 100644 apps/infoclk/README.md create mode 100644 apps/infoclk/app.js create mode 100644 apps/infoclk/font.js create mode 100644 apps/infoclk/font/am.png create mode 100644 apps/infoclk/font/colon.png create mode 100644 apps/infoclk/font/digit0.png create mode 100644 apps/infoclk/font/digit1.png create mode 100644 apps/infoclk/font/digit2.png create mode 100644 apps/infoclk/font/digit3.png create mode 100644 apps/infoclk/font/digit4.png create mode 100644 apps/infoclk/font/digit5.png create mode 100644 apps/infoclk/font/digit6.png create mode 100644 apps/infoclk/font/digit7.png create mode 100644 apps/infoclk/font/digit8.png create mode 100644 apps/infoclk/font/digit9.png create mode 100644 apps/infoclk/font/pm.png create mode 100644 apps/infoclk/icon.js create mode 100644 apps/infoclk/icon.png create mode 100644 apps/infoclk/metadata.json create mode 100644 apps/infoclk/settings.js create mode 100644 apps/pomoplus/app.js create mode 100644 apps/pomoplus/boot.js create mode 100644 apps/pomoplus/common.js create mode 100644 apps/pomoplus/icon.js create mode 100644 apps/pomoplus/icon.png create mode 100644 apps/pomoplus/img/pause.png create mode 100644 apps/pomoplus/img/play.png create mode 100644 apps/pomoplus/img/reset.png create mode 100644 apps/pomoplus/img/skip.png create mode 100644 apps/pomoplus/metadata.json create mode 100644 apps/pomoplus/settings.js create mode 100644 apps/random/app.js create mode 100644 apps/random/icon.js create mode 100644 apps/random/icon.png create mode 100644 apps/random/metadata.json create mode 100644 apps/rpnsci/README.md create mode 100644 apps/rpnsci/app.js create mode 100644 apps/rpnsci/icon.js create mode 100644 apps/rpnsci/icon.png create mode 100644 apps/rpnsci/icon.xcf create mode 100644 apps/rpnsci/metadata.json create mode 100644 apps/stlap/app.js create mode 100644 apps/stlap/icon.js create mode 100644 apps/stlap/icon.png create mode 100644 apps/stlap/img/pause.png create mode 100644 apps/stlap/img/play.png create mode 100644 apps/stlap/img/reset.png create mode 100644 apps/stlap/metadata.json create mode 100644 apps/stlapview/app.js create mode 100644 apps/stlapview/icon.js create mode 100644 apps/stlapview/icon.png create mode 100644 apps/stlapview/metadata.json diff --git a/apps/bgtimer/app.js b/apps/bgtimer/app.js new file mode 100644 index 000000000..66f22a7a2 --- /dev/null +++ b/apps/bgtimer/app.js @@ -0,0 +1,27 @@ +Bangle.BGTIMER_ACTIVE = true; +const common = require("bgtimer-com.js"); +const storage = require("Storage"); + +const keypad = require("bgtimer-keys.js"); +const timerView = require("bgtimer-tview.js"); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +//Save our state when the app is closed +E.on('kill', () => { + storage.writeJSON(common.STATE_PATH, common.state); +}); + +//Handle touch here. I would implement these separately in each view, but I can't figure out how to clear the event listeners. +Bangle.on('touch', (button, xy) => { + if (common.state.wasRunning) timerView.touch(button, xy); + else keypad.touch(button, xy); +}); + +Bangle.on('swipe', dir => { + if (!common.state.wasRunning) keypad.swipe(dir); +}); + +if (common.state.wasRunning) timerView.show(common); +else keypad.show(common); diff --git a/apps/bgtimer/boot.js b/apps/bgtimer/boot.js new file mode 100644 index 000000000..67840b3ce --- /dev/null +++ b/apps/bgtimer/boot.js @@ -0,0 +1,11 @@ +const BGTIMER_common = require("bgtimer-com.js"); + +//Only start the timeout if the timer is running +if (BGTIMER_common.state.running) { + setTimeout(() => { + //Check now to avoid race condition + if (Bangle.BGTIMER_ACTIVE === undefined) { + load('bgtimer-ring.js'); + } + }, BGTIMER_common.getTimeLeft()); +} \ No newline at end of file diff --git a/apps/bgtimer/common.js b/apps/bgtimer/common.js new file mode 100644 index 000000000..67a6660d3 --- /dev/null +++ b/apps/bgtimer/common.js @@ -0,0 +1,42 @@ +const storage = require("Storage"); +const heatshrink = require("heatshrink"); + +exports.STATE_PATH = "bgtimer.state.json"; + +exports.BUTTON_ICONS = { + play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), + pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")), + reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI=")) +}; + +//Store the minimal amount of information to be able to reconstruct the state of the timer at any given time. +//This is necessary because it is necessary to write to flash to let the timer run in the background, so minimizing the writes is necessary. +exports.STATE_DEFAULT = { + wasRunning: false, //If the timer ever was running. Used to determine whether to display a reset button + running: false, //Whether the timer is currently running + startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. + pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. + elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. + setTime: 0, //How long the user wants the timer to run for + inputString: '0' //The string of numbers the user typed in. +}; +exports.state = storage.readJSON(exports.STATE_PATH); +if (!exports.state) { + exports.state = exports.STATE_DEFAULT; +} + +//Get the number of milliseconds until the timer expires +exports.getTimeLeft = function () { + if (!exports.state.wasRunning) { + //If the timer never ran, the time left is just the set time + return exports.setTime + } else if (exports.state.running) { + //If the timer is running, the time left is current time - start time + preexisting time + var runningTime = (new Date()).getTime() - exports.state.startTime + exports.state.elapsedTime; + } else { + //If the timer is not running, the same as above but use when the timer was paused instead of now. + var runningTime = exports.state.pausedTime - exports.state.startTime + exports.state.elapsedTime; + } + + return exports.state.setTime - runningTime; +} \ No newline at end of file diff --git a/apps/bgtimer/icon.js b/apps/bgtimer/icon.js new file mode 100644 index 000000000..a47eb21f8 --- /dev/null +++ b/apps/bgtimer/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcAkmSpICOggRPpEACJ9AgESCJxMBhu27dtARVgCIMBCJpxDmwRL7ARDgwRL4CWECJaoFjYRJ2ARFgYRJwDNGCJFsb46SIRgQAFSRAQHSRCMEAAqSGRgoAFRhaSKRgySKRg6SIRhCSIRhCSICBqSCRhSSGRhY2FkARPhMkCJ9JkiONgECCIOQCJsSCIOSCJuSCIVACBcECIdICJYOBCIVJRhYRFSRSMBCIiSKBwgCCSRCMCCIqSIRgYCFRhYCFSQyMEAQqSGBw6SIRgySKRgtO4iSJBAmT23bOIqSCRgvtCINsSQ4aEndtCINt2KSGIggOBCIW2JQlARgZECCIhKEpBEGCIpKEA==")) \ No newline at end of file diff --git a/apps/bgtimer/icon.png b/apps/bgtimer/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7dcf44b88254d3f31f6c94d8d3f4933fc2f1f3ec GIT binary patch literal 414 zcmV;P0b%}$P)S|NQ$gCcl(vO0I@(u75S@S(5l8z2uoB`P_zbWw zrm4RZMDIcW0M2=s5|PIQSAQ9GhHvoa~#6ticgf|`rupkieP=E%3iiZOv2xNQ+ zpoT!lhXFW5M0_aViHM312i)5=&)tvz$Ko#bnHY2Xe6J}65d|BRBpJU;*HZzryneBR}MeU;y^`QaBd! zMnDd_rQtXTd(f>Pd22WZ@_N-k7mo_}L-v@ literal 0 HcmV?d00001 diff --git a/apps/bgtimer/img/pause.png b/apps/bgtimer/img/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..ad31dadcff3ecffba6c7e015a4b2ecdcccc15b0f GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yb}Jpb3EHthA;}Wgh!W@g+}zZ>5(ej@)Wnk1 z6ovB4k_-iRPv3y>Mm}+%S<0R+jv*erj1rvBzcKy?0-#2mfDs6AN&+Pa>1V>HnHqAV z77z@@(I6TPB7z}6P{C*_8Vw?XAwW>UXet^FB7z}6Pyx+TQT{Ft9Zp`Z43JAaUHx3v IIVCg!009H(%m4rY literal 0 HcmV?d00001 diff --git a/apps/bgtimer/img/play.png b/apps/bgtimer/img/play.png new file mode 100644 index 0000000000000000000000000000000000000000..6c20c24c54d72382a69d5aa3685e3ad8fa6174ba GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yc1y0~8~!E(g(OQ{BTAg}b8}PkN*J7rQWHy3 zQxwWGOEMJPJ$(bh8~Mb6W+{8RIEHxeGD>hh{|3|p0snyj#-=9C2r~@X0!AQ!iBn3$ z4MMg6W+*i*!fOFFEF#JRN-Sc6X(ooo7L0`Sk>mqp!-$O^vMqpFM#v(HEg)nO)hxhe z5!Ed~w}_-v1k+3m4NGw}vjFA@nppr#T`Vx0s71pJhM7k-3(yUwngzHFrq}{P2E!~M zE0T#d9ApVeaX^Z}FdqmdKI;Vst0LE7sJpcdz literal 0 HcmV?d00001 diff --git a/apps/bgtimer/img/reset.png b/apps/bgtimer/img/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..7a317d09795c09aabab4836c8b9ec4a6c5fe4db8 GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-y4rWEypL5fJLXst}5hc#~xw)x%B@E6*sfi`2 zDGKG8B^e6tp1uL$jeO!jvy?qu978;K86`NMe`EX)1VD{A0V5FLlmto;($9oXGe{1f z0r=!#<`T33mnFm)4$@1M1?ZL#YdAG9F5Ihr6 z+Y*@Ba0@^#qn0Hw!$D@ESU?R+kPQc!NApyK;scrn5w#2l1q%qkf(q;dY8wu*0%FN% zSp*9WoHWgoDM=Q9QU(a%bS@SNqAfr+7@3X5OcWuaEWl { + common.state.inputString = '0'; + common.state.setTime = 0; + updateDisplay(); + feedback(true); + } +}; + +let StartButton = { + label: 'Go', + onclick: () => { + common.state.startTime = (new Date()).getTime(); + common.state.elapsedTime = 0; + common.state.wasRunning = true; + common.state.running = true; + feedback(true); + require('bgtimer-tview.js').show(common); + } +}; + +const BUTTONS = [ + [new NumberButton(7), new NumberButton(8), new NumberButton(9), ClearButton], + [new NumberButton(4), new NumberButton(5), new NumberButton(6), new NumberButton(0)], + [new NumberButton(1), new NumberButton(2), new NumberButton(3), StartButton] +]; + +function feedback(acceptable) { + if (acceptable) Bangle.buzz(50, 0.5); + else Bangle.buzz(200, 1); +} + +function drawButtons() { + g.reset().clearRect(0, 44, 175, 175).setFont("Vector", 15).setFontAlign(0, 0); + //Draw lines + for (let x = 44; x <= 176; x += 44) { + g.drawLine(x, 44, x, 175); + } + for (let y = 44; y <= 176; y += 44) { + g.drawLine(0, y, 175, y); + } + for (let row = 0; row < 3; row++) { + for (let col = 0; col < 4; col++) { + g.drawString(BUTTONS[row][col].label, 22 + 44 * col, 66 + 44 * row); + } + } +} + +function getFontSize(length) { + let size = Math.floor(176 / length); //Characters of width needed per pixel + size *= (20 / 12); //Convert to height + // Clamp to between 6 and 20 + if (size < 6) return 6; + else if (size > 20) return 20; + else return Math.floor(size); +} + +function updateDisplay() { + let displayString = inputStringToDisplayString(common.state.inputString); + g.clearRect(0, 24, 175, 43).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1).setFont("Vector", getFontSize(displayString.length)).drawString(displayString, 176, 24); +} + +exports.show = function (callerCommon) { + common = callerCommon; + g.reset(); + drawButtons(); + updateDisplay(); +}; + +exports.touch = function (button, xy) { + let row = Math.floor((xy.y - 44) / 44); + let col = Math.floor(xy.x / 44); + if (row < 0) return; + if (row > 2) row = 2; + if (col < 0) col = 0; + if (col > 3) col = 3; + + BUTTONS[row][col].onclick(); +}; + +exports.swipe = function (dir) { + if (dir == -1) { + if (common.state.inputString.length == 1) common.state.inputString = '0'; + else common.state.inputString = common.state.inputString.substring(0, common.state.inputString.length - 1); + + common.state.setTime = inputStringToTime(common.state.inputString); + + feedback(true); + updateDisplay(); + } else if (dir == 0) { + EnterButton.onclick(); + } +}; \ No newline at end of file diff --git a/apps/bgtimer/metadata.json b/apps/bgtimer/metadata.json new file mode 100644 index 000000000..3f63d0ec5 --- /dev/null +++ b/apps/bgtimer/metadata.json @@ -0,0 +1,44 @@ +{ + "id": "bgtimer", + "name": "Timer", + "version": "0.02", + "description": "A timer with a keypad that runs in the background", + "icon": "icon.png", + "type": "app", + "tags": "tools", + "supports": [ + "BANGLEJS2" + ], + "allow_emulator": true, + "storage": [ + { + "name": "bgtimer.app.js", + "url": "app.js" + }, + { + "name": "bgtimer.img", + "url": "icon.js", + "evaluate": true + }, + { + "name": "bgtimer.boot.js", + "url": "boot.js" + }, + { + "name": "bgtimer-com.js", + "url": "common.js" + }, + { + "name": "bgtimer-ring.js", + "url": "ring.js" + }, + { + "name": "bgtimer-keys.js", + "url": "keypad.js" + }, + { + "name": "bgtimer-tview.js", + "url": "timerview.js" + } + ] +} \ No newline at end of file diff --git a/apps/bgtimer/ring.js b/apps/bgtimer/ring.js new file mode 100644 index 000000000..9df5cb4bd --- /dev/null +++ b/apps/bgtimer/ring.js @@ -0,0 +1,28 @@ +const common = require('bgtimer-com.js'); + +Bangle.loadWidgets() +Bangle.drawWidgets() + +Bangle.setLocked(false); +Bangle.setLCDPower(true); + +let brightness = 0; + +setInterval(() => { + Bangle.buzz(200); + Bangle.setLCDBrightness(1 - brightness); + brightness = 1 - brightness; +}, 400); +Bangle.buzz(200); + +function stopTimer() { + common.state.wasRunning = false; + common.state.running = false; + require("Storage").writeJSON(common.STATE_PATH, common.state); +} + +E.showAlert("Timer expired!").then(() => { + stopTimer(); + load(); +}); +E.on('kill', stopTimer); \ No newline at end of file diff --git a/apps/bgtimer/timerview.js b/apps/bgtimer/timerview.js new file mode 100644 index 000000000..4c9f7acd3 --- /dev/null +++ b/apps/bgtimer/timerview.js @@ -0,0 +1,107 @@ +let common; + +function drawButtons() { + //Draw the backdrop + const BAR_TOP = g.getHeight() - 24; + g.setColor(0, 0, 1).setFontAlign(0, -1) + .clearRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .fillRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .setColor(1, 1, 1) + .drawLine(g.getWidth() / 2, BAR_TOP, g.getWidth() / 2, g.getHeight()) + + //Draw the buttons + .drawImage(common.BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP); + if (common.state.running) { + g.drawImage(common.BUTTON_ICONS.pause, g.getWidth() * 3 / 4, BAR_TOP); + } else { + g.drawImage(common.BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP); + } +} + +function drawTimer() { + let timeLeft = common.getTimeLeft(); + g.reset() + .setFontAlign(0, 0) + .setFont("Vector", 36) + .clearRect(0, 24, 176, 152) + + //Draw the timer + .drawString((() => { + let hours = timeLeft / 3600000; + let minutes = (timeLeft % 3600000) / 60000; + let seconds = (timeLeft % 60000) / 1000; + + function pad(number) { + return ('00' + parseInt(number)).slice(-2); + } + + if (hours >= 1) return `${parseInt(hours)}:${pad(minutes)}:${pad(seconds)}`; + else return `${parseInt(minutes)}:${pad(seconds)}`; + })(), g.getWidth() / 2, g.getHeight() / 2) + + if (timeLeft <= 0) load('bgtimer-ring.js'); +} + +let timerInterval; + +function setupTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + } + setTimeout(() => { + timerInterval = setInterval(drawTimer, 1000); + drawTimer(); + }, common.timeLeft % 1000); +} + +exports.show = function (callerCommon) { + common = callerCommon; + drawButtons(); + drawTimer(); + if (common.state.running) { + setupTimerInterval(); + } +} + +function clearTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + timerInterval = undefined; + } +} + +exports.touch = (button, xy) => { + if (xy.y < 152) return; + + if (button == 1) { + //Reset the timer + let setTime = common.state.setTime; + let inputString = common.state.inputString; + common.state = common.STATE_DEFAULT; + common.state.setTime = setTime; + common.state.inputString = inputString; + clearTimerInterval(); + require('bgtimer-keys.js').show(common); + } else { + if (common.state.running) { + //Record the exact moment that we paused + let now = (new Date()).getTime(); + common.state.pausedTime = now; + + //Stop the timer + common.state.running = false; + clearTimerInterval(); + drawTimer(); + drawButtons(); + } else { + //Start the timer and record when we started + let now = (new Date()).getTime(); + common.state.elapsedTime += common.state.pausedTime - common.state.startTime; + common.state.startTime = now; + common.state.running = true; + drawTimer(); + setupTimerInterval(); + drawButtons(); + } + } +}; \ No newline at end of file diff --git a/apps/gallery/README.md b/apps/gallery/README.md new file mode 100644 index 000000000..b70fa07c2 --- /dev/null +++ b/apps/gallery/README.md @@ -0,0 +1,18 @@ +# Gallery + +A simple gallery app + +## Usage + +Upon opening the gallery app, you will be presented with a list of images that you can display. Tap the image to show it. Brightness will be set to full, and the screen timeout will be disabled. When you are done viewing the image, you can tap the screen to go back to the list of images. Press BTN1 to flip the image upside down. + +## Adding images + +1. The gallery app does not perform any scaling, and does not support panning. Therefore, you should use your favorite image editor to produce an image of the appropriate size for your watch. (240x240 for Bangle 1 or 176x176 for Bangle 2.) How you achieve this is up to you. If on a Bangle 2, I recommend adjusting the colors here to comply with the color restrictions. + +2. Upload your image to the [Espruino image converter](https://www.espruino.com/Image+Converter). I recommend enabling compression and choosing one of the following color settings: + * 16 bit RGB565 for Bangle 1 + * 3 bit RGB for Bangle 2 + * 1 bit black/white for monochrome images that you want to respond to your system theme. (White will be rendered as your foreground color and black will be rendered as your background color.) + +3. Set the output format to an image string, copy it into the [IDE](https://www.espruino.com/ide/), and set the destination to a file in storage. The file name should begin with "gal-" (without the quotes) and end with ".img" (without the quotes) to appear in the gallery. Note that the gal- prefix and .img extension will be removed in the UI. Upload the file. \ No newline at end of file diff --git a/apps/gallery/app.js b/apps/gallery/app.js new file mode 100644 index 000000000..ca9392f13 --- /dev/null +++ b/apps/gallery/app.js @@ -0,0 +1,52 @@ +const storage = require('Storage'); + +let imageFiles = storage.list(/^gal-.*\.img/).sort(); + +let imageMenu = { '': { 'title': 'Gallery' } }; + +for (let fileName of imageFiles) { + let displayName = fileName.substr(4, fileName.length - 8); // Trim off the 'gal-' and '.img' for a friendly display name + imageMenu[displayName] = eval(`() => { drawImage("${fileName}"); }`); // Unfortunately, eval is the only reasonable way to do this +} + +let cachedOptions = Bangle.getOptions(); // We will change the backlight and timeouts later, and need to restore them when displaying the menu +let backlightSetting = storage.readJSON('setting.json').brightness; // LCD brightness is not included in there for some reason + +let angle = 0; // Store the angle of rotation +let image; // Cache the image here because we access it in multiple places + +function drawMenu() { + Bangle.removeListener('touch', drawMenu); // We no longer want touching to reload the menu + Bangle.setOptions(cachedOptions); // The drawImage function set no timeout, undo that + Bangle.setLCDBrightness(backlightSetting); // Restore backlight + image = undefined; // Delete the image from memory + + E.showMenu(imageMenu); +} + +function drawImage(fileName) { + E.showMenu(); // Remove the menu to prevent it from breaking things + setTimeout(() => { Bangle.on('touch', drawMenu); }, 300); // Touch the screen to go back to the image menu (300ms timeout to allow user to lift finger) + Bangle.setOptions({ // Disable display power saving while showing the image + lockTimeout: 0, + lcdPowerTimeout: 0, + backlightTimeout: 0 + }); + Bangle.setLCDBrightness(1); // Full brightness + + image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this + g.clear().reset().drawImage(image, 88, 88, { rotate: angle }); +} + +setWatch(info => { + if (image) { + if (angle == 0) angle = Math.PI; + else angle = 0; + Bangle.buzz(); + + g.clear().reset().drawImage(image, 88, 88, { rotate: angle }) + } +}, BTN1, { repeat: true }); + +// We don't load the widgets because there is no reasonable way to unload them +drawMenu(); \ No newline at end of file diff --git a/apps/gallery/icon.js b/apps/gallery/icon.js new file mode 100644 index 000000000..11fee53eb --- /dev/null +++ b/apps/gallery/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIOLgf/AAX8Av4FBJgkMAos/CIfMAv4Fe4AF/Apq5EAAw")) \ No newline at end of file diff --git a/apps/gallery/icon.png b/apps/gallery/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..71835e93da29808335d7f39c2ca849373b083d94 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUt1D-C9AsNnZXE|~mP~c&C{9iuz z8E5bbG@o`agQ-${dF&4QA(@#tDiuj)}sXJrCW}qOH(Be|C&4}kl zK$oM0z`>iR3=*6**%BRC(zUIR9hiOMb7|FdaVe<{ykC3EQnx{~p>pFDf+|Lvz(7tMOYcx7L0GSDv!p00i_>zopr0ApiXkN^Mx literal 0 HcmV?d00001 diff --git a/apps/gallery/metadata.json b/apps/gallery/metadata.json new file mode 100644 index 000000000..89f7606aa --- /dev/null +++ b/apps/gallery/metadata.json @@ -0,0 +1,26 @@ +{ + "id": "gallery", + "name": "Gallery", + "version": "0.02", + "description": "A gallery that lets you view images uploaded with the IDE (see README)", + "readme": "README.md", + "icon": "icon.png", + "type": "app", + "tags": "tools", + "supports": [ + "BANGLEJS2", + "BANGLEJS" + ], + "allow_emulator": true, + "storage": [ + { + "name": "gallery.app.js", + "url": "app.js" + }, + { + "name": "gallery.img", + "url": "icon.js", + "evaluate": true + } + ] +} \ No newline at end of file diff --git a/apps/infoclk/README.md b/apps/infoclk/README.md new file mode 100644 index 000000000..1dd563bec --- /dev/null +++ b/apps/infoclk/README.md @@ -0,0 +1,33 @@ +# Informational clock + +A configurable clock with extra info and shortcuts when unlocked, but large time when locked + +## Information + +The clock has two different screen arrangements, depending on whether the watch is locked or unlocked. The most commonly viewed piece of information is the time, so when the watch is locked it optimizes for the time being visible at a glance without the backlight. The hours and minutes take up nearly the entire top half of the display, with the date and seconds taking up nearly the entire bottom half. The day progress bar is between them if enabled, unless configured to be on the bottom row. The bottom row can be configured to display a weather summary, step count, step count and heart rate, the daily progress bar, or nothing. + +When the watch is unlocked, it can be assumed that the backlight is on and the user is actively looking at the watch, so instead we can optimize for information density. The bottom half of the display becomes shortcuts, and the top half of the display becomes 4 rows of information (date and time, step count and heart rate, 2 line weather summary) + an optional daily progress bar. (The daily progress bar can be independently enabled when locked and unlocked.) + +Most things are self-explanatory, but the day progress bar might not be. The day progress bar is intended to show approximately how far through the day you are, in the form of a progress bar. You might want to configure it to show how far you are through your waking hours, or you might want to use it to show how far you are through your work or school day. + +## Shortcuts + +There are generally a few apps that the user uses far more frequently than the others. For example, they might use a timer, alarm clock, and calculator every day, while everything else (such as the settings app) gets used only occasionally. This clock has space for 8 apps in the bottom half of the screen only one tap away, avoiding the need to wait for the launcher to open and then scroll through it. Tapping the top of the watch opens the launcher, eliminating the need for the button (which still opens the launcher due to bangle.js conventions). There is also handling for left, right, and vertical swipes. A vertical swipe by default opens the messages app, mimicking mobile operating systems which use a swipe down to view the notification shade. + +## Configurability + +Displaying the seconds allows for more precise timing, but waking up the CPU to refresh the display more often consumes battery. The user can enable or disable them completely, but can also configure them to be enabled or disabled automatically based on some hueristics: + +* They can be hidden while the display is locked, if the user expects to unlock their watch when they need the seconds. +* They can be hidden when the battery is too low, to make the last portion of the battery last a little bit longer. +* They can be hidden during a period of time such as when the user is asleep and therefore unlikely to need very much precision. + +The date format can be changed. + +As described earlier, the contents of the bottom row when locked can be changed. + +The 8 tap-based shortcuts on the bottom and the 3 swipe-based shortcuts can be changed to nothing, the launcher, or any app on the watch. + +The start and end time of the day progress bar can be changed. It can be enabled or disabled separately when the watch is locked and unlocked. The color can be changed. The time when it resets from full to empty can be changed. + +When the battery is below a defined point, the watch's color can change to another chosen color to help the user notice that the battery is low. \ No newline at end of file diff --git a/apps/infoclk/app.js b/apps/infoclk/app.js new file mode 100644 index 000000000..6bc626018 --- /dev/null +++ b/apps/infoclk/app.js @@ -0,0 +1,405 @@ +const SETTINGS_FILE = "infoclk.json"; +const FONT = require('infoclk-font.js'); + +const storage = require("Storage"); +const locale = require("locale"); +const weather = require('weather'); + +let config = Object.assign({ + seconds: { + // Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display. + // The seconds will be shown unless one of these conditions is enabled here, and currently true. + hideLocked: false, // Hide the seconds when the display is locked. + hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage. + hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds + hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes + hideEnd: 700, // The time when the seconds are shown again + hideAlways: false, // Always hide (never show) the seconds + }, + + date: { + // Settings related to the display of the date + mmdd: true, // If true, display the month first. If false, display the date first. + separator: '-', // The character that goes between the month and date + monthName: false, // If false, display the month as a number. If true, display the name. + monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name. + dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name. + }, + + bottomLocked: { + display: 'weather' // What to display in the bottom row when locked: + // 'weather': The current temperature and weather description + // 'steps': Step count + // 'health': Step count and bpm + // 'progress': Day progress bar + // false: Nothing + }, + + shortcuts: [ + //8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked + // false = no shortcut + // '#LAUNCHER' = open the launcher + // any other string = name of app to open + 'stlap', 'bgtimer', 'pomoplus', 'alarm', + 'rpnsci', 'calendar', 'torch', 'weather' + ], + + swipe: { + // 3 shortcuts to launch upon swiping: + // false = no shortcut + // '#LAUNCHER' = open the launcher + // any other string = name of app to open + up: 'messages', // Swipe up or swipe down, due to limitation of event handler + left: '#LAUNCHER', + right: '#LAUNCHER', + }, + + dayProgress: { + // A progress bar representing how far through the day you are + enabledLocked: true, // Whether this bar is enabled when the watch is locked + enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked + color: [0, 0, 1], // The color of the bar + start: 700, // The time of day that the bar starts filling + end: 2200, // The time of day that the bar becomes full + reset: 300 // The time of day when the progress bar resets from full to empty + }, + + lowBattColor: { + // The text can change color to indicate that the battery is low + level: 20, // The percentage where this happens + color: [1, 0, 0] // The color that the text changes to + } +}, storage.readJSON(SETTINGS_FILE)); + +// Return whether the given time (as a date object) is between start and end (as a number where the first 2 digits are hours on a 24 hour clock and the last 2 are minutes), with end time wrapping to next day if necessary +function timeInRange(start, time, end) { + + // Convert the given date object to a time number + let timeNumber = time.getHours() * 100 + time.getMinutes(); + + // Normalize to prevent the numbers from wrapping around at midnight + if (end <= start) { + end += 2400; + if (timeNumber < start) timeNumber += 2400; + } + + return start <= timeNumber && timeNumber <= end; +} + +// Return whether settings should be displayed based on the user's configuration +function shouldDisplaySeconds(now) { + return !( + (config.seconds.hideAlways) || + (config.seconds.hideLocked && Bangle.isLocked()) || + (E.getBattery() <= config.seconds.hideBattery) || + (config.seconds.hideTime && timeInRange(config.seconds.hideStart, now, config.seconds.hideEnd)) + ); +} + +// Determine the font size needed to fit a string of the given length widthin maxWidth number of pixels, clamped between minSize and maxSize +function getFontSize(length, maxWidth, minSize, maxSize) { + let size = Math.floor(maxWidth / length); //Number of pixels of width available to character + size *= (20 / 12); //Convert to height, assuming 20 pixels of height for every 12 of width + + // Clamp to within range + if (size < minSize) return minSize; + else if (size > maxSize) return maxSize; + else return Math.floor(size); +} + +// Get the current day of the week according to user settings +function getDayString(now) { + if (config.date.dayFullName) return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][now.getDay()]; + else return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][now.getDay()]; +} + +// Pad a number with zeros to be the given number of digits +function pad(number, digits) { + let result = '' + number; + while (result.length < digits) result = '0' + result; + return result; +} + +// Get the current date formatted according to the user settings +function getDateString(now) { + let month; + if (!config.date.monthName) month = pad(now.getMonth() + 1, 2); + else if (config.date.monthFullName) month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][now.getMonth()]; + else month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.getMonth()]; + + if (config.date.mmdd) return `${month}${config.date.separator}${pad(now.getDate(), 2)}`; + else return `${pad(now.getDate(), 2)}${config.date.separator}${month}`; +} + +// Get a floating point number from 0 to 1 representing how far between the user-defined start and end points we are +function getDayProgress(now) { + let start = config.dayProgress.start; + let current = now.getHours() * 100 + now.getMinutes(); + let end = config.dayProgress.end; + let reset = config.dayProgress.reset; + + // Normalize + if (end <= start) end += 2400; + if (current < start) current += 2400; + if (reset < start) reset += 2400; + + // Convert an hhmm number into a floating-point hours + function toDecimalHours(time) { + let hours = Math.floor(time / 100); + let minutes = time % 100; + + return hours + (minutes / 60); + } + + start = toDecimalHours(start); + current = toDecimalHours(current); + end = toDecimalHours(end); + reset = toDecimalHours(reset); + + let progress = (current - start) / (end - start); + + if (progress < 0 || progress > 1) { + if (current < reset) return 1; + else return 0; + } else { + return progress; + } +} + +// Get a Gadgetbridge weather string +function getWeatherString() { + let current = weather.get(); + if (current) return locale.temp(current.temp - 273.15) + ', ' + current.txt; + else return 'Weather unknown!'; +} + +// Get a second weather row showing humidity, wind speed, and wind direction +function getWeatherRow2() { + let current = weather.get(); + if (current) return `${current.hum}%, ${locale.speed(current.wind)} ${current.wrose}`; + else return 'Check Gadgetbridge'; +} + +// Get a step string +function getStepsString() { + return '' + Bangle.getHealthStatus('day').steps + ' steps'; +} + +// Get a health string including daily steps and recent bpm +function getHealthString() { + return `${Bangle.getHealthStatus('day').steps} steps ${Bangle.getHealthStatus('last').bpm} bpm`; +} + +// Set the next timeout to draw the screen +let drawTimeout; +function setNextDrawTimeout() { + if (drawTimeout) { + clearTimeout(drawTimeout); + drawTimeout = undefined; + } + + let time; + let now = new Date(); + if (shouldDisplaySeconds(now)) time = 1000 - (now.getTime() % 1000); + else time = 60000 - (now.getTime() % 60000); + + drawTimeout = setTimeout(draw, time); +} + + +const DIGIT_WIDTH = 40; // How much width is allocated for each digit, 37 pixels + 3 pixels of space (which will go off of the screen on the right edge) +const COLON_WIDTH = 19; // How much width is allocated for the colon, 16 pixels + 3 pixels of space +const HHMM_TOP = 27; // 24 pixels for widgets + 3 pixels of space +const DIGIT_HEIGHT = 64; // How tall the digits are + +const SECONDS_TOP = HHMM_TOP + DIGIT_HEIGHT + 3; // The top edge of the seconds, top of hours and minutes + digit height + space +const SECONDS_LEFT = 2 * DIGIT_WIDTH + COLON_WIDTH; // The left edge of the seconds: displayed after 2 digits and the colon +const DATE_LETTER_HEIGHT = DIGIT_HEIGHT / 2; // Each letter of the day of week and date will be half the height of the time digits + +const DATE_CENTER_X = SECONDS_LEFT / 2; // Day of week and date will be centered between left edge of screen and where seconds start +const DOW_CENTER_Y = SECONDS_TOP + (DATE_LETTER_HEIGHT / 2); // Day of week will be the top row +const DATE_CENTER_Y = DOW_CENTER_Y + DATE_LETTER_HEIGHT; // Date will be the bottom row +const DOW_DATE_CENTER_Y = SECONDS_TOP + (DIGIT_HEIGHT / 2); // When displaying both on one row, center it +const BOTTOM_CENTER_Y = ((SECONDS_TOP + DIGIT_HEIGHT + 3) + g.getHeight()) / 2; + +// Draw the clock +function draw() { + //Prepare to draw + g.reset() + .setFontAlign(0, 0); + + if (E.getBattery() <= config.lowBattColor.level) { + let color = config.lowBattColor.color; + g.setColor(color[0], color[1], color[2]); + } + now = new Date(); + + if (Bangle.isLocked()) { //When the watch is locked + g.clearRect(0, 24, g.getWidth(), g.getHeight()); + + //Draw the hours and minutes + let x = 0; + + for (let digit of locale.time(now, 1)) { //apparently this is how you get an hh:mm time string adjusting for the user's 12/24 hour preference + if (digit != ' ') g.drawImage(FONT[digit], x, HHMM_TOP); + if (digit == ':') x += COLON_WIDTH; + else x += DIGIT_WIDTH; + } + if (storage.readJSON('setting.json')['12hour']) g.drawImage(FONT[(now.getHours() < 12) ? 'am' : 'pm'], 0, HHMM_TOP); + + //Draw the seconds if necessary + if (shouldDisplaySeconds(now)) { + let tens = Math.floor(now.getSeconds() / 10); + let ones = now.getSeconds() % 10; + g.drawImage(FONT[tens], SECONDS_LEFT, SECONDS_TOP) + .drawImage(FONT[ones], SECONDS_LEFT + DIGIT_WIDTH, SECONDS_TOP); + + // Draw the day of week and date assuming the seconds are displayed + + g.setFont('Vector', getFontSize(getDayString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT)) + .drawString(getDayString(now), DATE_CENTER_X, DOW_CENTER_Y) + .setFont('Vector', getFontSize(getDateString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT)) + .drawString(getDateString(now), DATE_CENTER_X, DATE_CENTER_Y); + + } else { + //Draw the day of week and date without the seconds + + let string = getDayString(now) + ' ' + getDateString(now); + g.setFont('Vector', getFontSize(string.length, g.getWidth(), 6, DATE_LETTER_HEIGHT)) + .drawString(string, g.getWidth() / 2, DOW_DATE_CENTER_Y); + } + + // Draw the bottom area + if (config.bottomLocked.display == 'progress') { + let color = config.dayProgress.color; + g.setColor(color[0], color[1], color[2]) + .fillRect(0, SECONDS_TOP + DIGIT_HEIGHT + 3, g.getWidth() * getDayProgress(now), g.getHeight()); + } else { + let bottomString; + + if (config.bottomLocked.display == 'weather') bottomString = getWeatherString(); + else if (config.bottomLocked.display == 'steps') bottomString = getStepsString(); + else if (config.bottomLocked.display == 'health') bottomString = getHealthString(); + else bottomString = ' '; + + g.setFont('Vector', getFontSize(bottomString.length, 176, 6, g.getHeight() - (SECONDS_TOP + DIGIT_HEIGHT + 3))) + .drawString(bottomString, g.getWidth() / 2, BOTTOM_CENTER_Y); + } + + // Draw the day progress bar between the rows if necessary + if (config.dayProgress.enabledLocked && config.bottomLocked.display != 'progress') { + let color = config.dayProgress.color; + g.setColor(color[0], color[1], color[2]) + .fillRect(0, HHMM_TOP + DIGIT_HEIGHT, g.getWidth() * getDayProgress(now), SECONDS_TOP); + } + } else { + + //If the watch is unlocked + g.clearRect(0, 24, g.getWidth(), g.getHeight() / 2); + rows = [ + `${getDayString(now)} ${getDateString(now)} ${locale.time(now, 1)}`, + getHealthString(), + getWeatherString(), + getWeatherRow2() + ]; + if (shouldDisplaySeconds(now)) rows[0] += ':' + pad(now.getSeconds(), 2); + if (storage.readJSON('setting.json')['12hour']) rows[0] += ((now.getHours() < 12) ? ' AM' : ' PM'); + + let maxHeight = ((g.getHeight() / 2) - HHMM_TOP) / (config.dayProgress.enabledUnlocked ? (rows.length + 1) : rows.length); + + let y = HHMM_TOP + maxHeight / 2; + for (let row of rows) { + let size = getFontSize(row.length, g.getWidth(), 6, maxHeight); + g.setFont('Vector', size) + .drawString(row, g.getWidth() / 2, y); + y += maxHeight; + } + + if (config.dayProgress.enabledUnlocked) { + let color = config.dayProgress.color; + g.setColor(color[0], color[1], color[2]) + .fillRect(0, y - maxHeight / 2, 176 * getDayProgress(now), y + maxHeight / 2); + } + } + + setNextDrawTimeout(); +} + +// Draw the icons. This is done separately from the main draw routine to avoid having to scale and draw a bunch of images repeatedly. +function drawIcons() { + g.reset().clearRect(0, 24, g.getWidth(), g.getHeight()); + for (let i = 0; i < 8; i++) { + let x = [0, 44, 88, 132, 0, 44, 88, 132][i]; + let y = [88, 88, 88, 88, 132, 132, 132, 132][i]; + let appId = config.shortcuts[i]; + let appInfo = storage.readJSON(appId + '.info', 1); + if (!appInfo) continue; + icon = storage.read(appInfo.icon); + g.drawImage(icon, x, y, { + scale: 0.916666666667 + }); + } +} + +weather.on("update", draw); +Bangle.on("step", draw); +Bangle.on('lock', locked => { + //If the watch is unlocked, draw the icons + if (!locked) drawIcons(); + draw(); +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Launch an app given the current ID. Handles special cases: +// false: Do nothing +// '#LAUNCHER': Open the launcher +// nonexistent app: Do nothing +function launch(appId) { + if (appId == false) return; + else if (appId == '#LAUNCHER') { + Bangle.buzz(); + Bangle.showLauncher(); + } else { + let appInfo = storage.readJSON(appId + '.info', 1); + if (appInfo) { + Bangle.buzz(); + load(appInfo.src); + } + } +} + +//Set up touch to launch the selected app +Bangle.on('touch', function (button, xy) { + let x = Math.floor(xy.x / 44); + if (x < 0) x = 0; + else if (x > 3) x = 3; + + let y = Math.floor(xy.y / 44); + if (y < 0) y = -1; + else if (y > 3) y = 1; + else y -= 2; + + if (y < 0) { + Bangle.buzz(); + Bangle.showLauncher(); + } else { + let i = 4 * y + x; + launch(config.shortcuts[i]); + } +}); + +//Set up swipe handler +Bangle.on('swipe', function (direction) { + if (direction == -1) launch(config.swipe.left); + else if (direction == 0) launch(config.swipe.up); + else launch(config.swipe.right); +}); + +if (!Bangle.isLocked()) drawIcons(); + +draw(); \ No newline at end of file diff --git a/apps/infoclk/font.js b/apps/infoclk/font.js new file mode 100644 index 000000000..6063958e7 --- /dev/null +++ b/apps/infoclk/font.js @@ -0,0 +1,23 @@ +const heatshrink = require("heatshrink") + +function decompress(string) { + return heatshrink.decompress(atob(string)) +} + +exports = { + '0': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYk/En4kmAA4qBAAP7BAePAYX4BofBAYX8F4Q+BEwRHBIQI5BA"), + '1': decompress("ktAwIGDj/4AgX/4ADBg/+BAU/+ADBgP/wAEBh/8BoV/8ADBgf/En4k/En4k/EgQ="), + '2': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECElQdBPA2HAYX8OYfHBAYRD8Z3Dj6TG/kPPYZm4EiwAHO4f7BAfPfI/xBoaTEPAfgQwY"), + '3': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECElJWIAEpu/EhpgS34DC/IID54DC/l/AgXDAYX4j57DA"), + '4': decompress("ktAwMA//AgEf//+BYP///wgEHAgOAgE///8gEBBAPggEPAgIWBv///EAgYIBEn4kXABf9AgfnAgY4BAAP4BAfDAYX+EwfwIQRRCJIJRBJIRRBJIQICj5RBJIRRBJIJRCNwJRBNwQk/Ei4A=="), + '5': decompress("ktAwIEB/4AB/EfAgXDAYX+n4EC+YDC/+fAYX9BAfvAgYAJ+AwBgP/wAEBh/8H4V/8ADBgf/BAUf/AEC//AAYMH/wICn4kpPYUPAgXgv4EC4JfDg4DC/iFD8ANDwaTDCQfwEoZ2/EhrXNAAm/AYX5BAfPQoaTD4ahDj57DA=="), + '6': decompress("ktAwIEB/4AB/EfAgXDAYX+n4EC+YDC/+fAYX9BAfvAgYAJ+AwBgP/wAEBh/8H4V/8ADBgf/BAUf/AEC//AAYMH/wICn4kpPYUPAgXgv6AG/6JD/gID84ED358NJIIsCKIQ0BKIRJCFgJJCSYcHAgJuBXYJuBKIQkpAA58D/YIDx6PDBofBQoYvCHwImCI4KUCwA="), + '7': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECEn4k/En4kVA"), + '8': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYkpAFpu/EhwAHFQIAB/YIDx4DC/AND4IDC/ieD4AmCI4JCBHIIA=="), + '9': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYkpABf9AgfnAgaFD/AID4Z8DEwfwIQRRCJIJRBJIRRBJIQICj5RBJIRRBJIJRCNwJRBNwQkoPhoAE34DC/L0H/iwBQAv4WAJ7CA=="), + + ':': decompress("iFAwITQg/gj/4n/8v/+AIP/ABQPDCoIZBDoJTfH94A=="), + + 'am': decompress("jFAwIEBngCEvwCH/4CFwEBAQkD//AgfnAQcH4fgAQsPwPwAQf/+Ef//4AQn8n0AvgCCHQN+vkAnwCC/EAj4CF+EAh4CCNIoLFC4v8gE/AQv+gF/AQpwB/4CDwICG+/D94CD8/v+fn54CC+P/x4CF+H/IgICFvwCEngCD"), + 'pm': decompress("jFAwMAn///l///+/4AE+EAh4CaEYoABFgX8BwMAAUwAFIIv4gEfAQX8OYICF/0Av4CF/8AKQICCwICG+/D94CD8/v+fn54CC+P/x4CF+H/IgICFvwCEngCDA") +} \ No newline at end of file diff --git a/apps/infoclk/font/am.png b/apps/infoclk/font/am.png new file mode 100644 index 0000000000000000000000000000000000000000..a76ad25fc8bd5b987689eda614a1bfb519259243 GIT binary patch literal 360 zcmV-u0hj)XP)fJj5NVp zUZW%r0XQG8upV*+rNxE7=eg4Y&JKH3P?Fr-8o0L`pn7UhLqTzOB=2izQx`x>a%HlU zg7pAu5Kw*T=164G;{X*=)AQD7CA^D8RW}#6qSvegIWYr!!j6nAqBH+AfD#pO8etZ# z0dQ2%VNF@o1;8M3)N~Zg2XGr88WyZirZZ_g4!GumjhdE%bXe_0{5=4qBI?%g8lXP< zvXFM#djXD3^dhM&S_8mT$iE^Si@E?J2pXL2N>=wT3-AMt$$=hV8R&5U0000FtYi*xWC%KNFBJ~#n2-^57*E11a! zmI=JzNm4=(y?sQ{BLG1`4tg@9iEJf(z+L5yVrgYB$yQ8KabY>TT^_c6v#p!Ui87*$ za=7Zo2^)&l7qK6Rlw&_A+L5py>?c52&gOx#tn6--(n1D*@uSQ9TmMhz7ny<}HUB<5 SKmY&$07*qoM6N<$f&c&qqgFit literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit0.png b/apps/infoclk/font/digit0.png new file mode 100644 index 0000000000000000000000000000000000000000..5470154ee601c8829b3f254c35a8ed21ef90245f GIT binary patch literal 290 zcmV+-0p0$IP)c zQ7Z%$fv1+MsFi{$VXj4apmfqIq}++}Bq-5}q<$@GxC)DFx&**N3Q-c$>_2gqx?$2- zOrM(Rkb;yUCCNS&7Nj6`B|w!RWdv1%x*tIbQsVC%MV@LENI^=iTOb9gh5$t#MV^ur zY%?sLicfNklvH>MPp$NnVeGk3i_zysGW7O%2$|0NVw2Vn!^h5q2G<{907*qoM6N<$f)`wN#sB~S literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit1.png b/apps/infoclk/font/digit1.png new file mode 100644 index 0000000000000000000000000000000000000000..26a35fd1be9685e70fba18845d231e5087436a99 GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^szB_(!3HGfF5mYGNY#0|IEG|6zrD1PmqCGt#cCllQ1FmvHB)s96`^Y4Bm2~FN2i??W?-cZ2{06micQ(n!twLzqq1zurlY7_^s%tLA)M4(T zmSpNOrYShdYZ|Fm!bDRo@hjJ~`zS0)Ns1DnIx{6H zN`UHcrjU@1Iztz`9hMX+ujM0@&I1KAMQE8RLg{vTMnCwdL;1BCwHY1AnVF)nBqb?I zfa=VYq$mNZGgH$^A*6KF89Mjqlwu;OxId$X!ZiIvpp(#m`T$(9bRFaYu zsi;GP+K-tsQ)cQyz|jTu4u?G2J0vA3_~+z%?lf~`?*7~yG1Xt&_m${?nxaiwQj$uPr`1PcNlH?b x0M(f(Nl^k+XQm`Y2~eGxk`yICb!O^SQadKJTLI?SZzBKz002ovPDHLkV1lOcXY>F7 literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit5.png b/apps/infoclk/font/digit5.png new file mode 100644 index 0000000000000000000000000000000000000000..5647ad00a4203ac500bbc91a35a969826f5674e4 GIT binary patch literal 302 zcmV+}0nz@6P)Nkl`;@!S-AC%m-A63B`=}+G zddgJfz9c*RLPWL%vQRYB1=|hPozx2{{nSKFMDmw4wXyx=k%!8V60h+S=TmiIZnH-I z;=T??pdxKO%c2J~6zy)CvZ-0iRoC5pRF+NI6g5C|t|^5)ttI0m}&-?-8Ch5k~bXh4s1|BAkDn&#{d8T07*qoM6N<$g7N~2 AhyVZp literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit6.png b/apps/infoclk/font/digit6.png new file mode 100644 index 0000000000000000000000000000000000000000..56c44688139cda32620387169e5deb5952a6ef6f GIT binary patch literal 309 zcmV-50m}Y~P)*_2HMO3~FvW!aQXwFZ1jKovogW<^|N?^`h+8l&cklr2T5 za__63f~~?M50xQz+ByWO{8n<}n04tltPNlC?s~^la6=(KE3i=@8qlkn00000NkvXX Hu0mjfeT0Q( literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit7.png b/apps/infoclk/font/digit7.png new file mode 100644 index 0000000000000000000000000000000000000000..1fb6a64237347be6e572ef6b3bfe9e56dfbe5d9f GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^szB_(!3HGfF5mYGNUid8aSX|DetX4{>wtm)%fY+< z?X4UY=egYSj#*fI)}ifGhT9S0|Mok7rBqigx^g`;>Rrjo%$-_GqjoL~TX{tzO|#;} z=2;>CTSdLU9-Y5u`LgJA0AC4(2Ql0u6QU>ezLM@*1^<-!raS<}F_u*7E z4Av8xLN9Dbp_L(p-z-slfTjYEX&P-ea2~S(ryj^Ca=Sn#%DQ3@@%8EX00000NkvXX Hu0mjf3Gst1 literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/digit9.png b/apps/infoclk/font/digit9.png new file mode 100644 index 0000000000000000000000000000000000000000..990a3a43bbb16093a9aa37e999b57a60e04fa01a GIT binary patch literal 319 zcmV-F0l@x=P)9rRKuR68M;BUa9{j4h>G zm(m0(_fu6@M&IN$rwFoiJYH)A3Qc%02>n4r(Mj5-YzlTBUie*8t|`~lf&m)pwa=!g z0UC#E3I)suX+_e~LlDg_)357zhHk;Au>pp-JWvaUs{{RbVQ4q_& R&KCdx002ovPDHLkV1hy|g)IO8 literal 0 HcmV?d00001 diff --git a/apps/infoclk/font/pm.png b/apps/infoclk/font/pm.png new file mode 100644 index 0000000000000000000000000000000000000000..a3db97eb8c51fc817b78d347b41b5ce7d4851d3e GIT binary patch literal 327 zcmV-N0l5B&P)kh*p2!-|iA9_ohbwdjruwL4Q{aBi4{mQ`{(42n17kTdSn72jtzm!t%#g7ln8(`Gp zJ7fvqkgYWjg(6%80SJlU<_d|fO9WT&OjzPYo)1KGhuZbG;MDcts@WA$jd0Tdpp8?G zM-HG~AyG&K6@U}EIwwb)0_a^2*7rs9Xa$p4O*oyFq^_bhH)cRf6zo|~>x;Zm+z;T9 zNjwUp*3l}{eE}Ak)SCh%(G-9Nf%hDi2%ZP9`V%M5>0PNWl3u3f*&RT0Atl!wfppEL zUBR(qTI;CEWFAAkDct9uq)^Xt)#T>`q-4|WT3fnHqW=JBQE(N{EzuMJ2a)P^Qv}U` ZH{YFl6>+g)wEzGB07*qoM6N<$f&h;Vja~o% literal 0 HcmV?d00001 diff --git a/apps/infoclk/icon.js b/apps/infoclk/icon.js new file mode 100644 index 000000000..2db8a7a9a --- /dev/null +++ b/apps/infoclk/icon.js @@ -0,0 +1,2 @@ +//TODO replace app icon with my own +require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA==")) diff --git a/apps/infoclk/icon.png b/apps/infoclk/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a38093c5f3b6f88dbc9b75017422dbe237d4407f GIT binary patch literal 1989 zcmV;$2RitPP)jLx_$n|!#a_+a7!4?Zj*KoBI+mq8Z8#{*+o z7DHwNZgKLU(NGK;MJHm2(2Z3wlN9^G$Dl3PLZR*L$JxWF_g-#q5pXO%?4OgH|2gM( z&iUPY&i~`w2to*6Cz0Salm7|*Pv});Rk#kH&o?nKVKf?JV`CKxgR&1SYr2-&`UyG$k%PDI4SU@#m$e3%a_DJdBl84(40z238D&kClk zs;Y82oqX8o)29U(i^UQ;n+PGB&6b*)8u|?i3JT`t=0fr3&YhD;M7F-UxtYZ<46||L z#$^(^di83!ed+D(C4@{&Ojs-yi^VcGH|KCTl9H16n23l7R(HGIbWWX4$8TSNkjR^L zcXzY8Ua#-%?PaT@qoV@@0~H&dOV(ykrCQkT3X65OnG@Zi=UX7pvt31 zj}{pcL)2{nR~LU?!AoakWJE?r@;{iv;h@Tm8#ky@Q&Y1{D-)+L zk?;ux0)h4G*YjJ(#>Q^nzRiCh09LE@!i5U}3JVKgYOOCJ6uR+Sw{CU2-NC`D<_&Lc zZ7nJ)ve|63T3A>ZA0H23aBz^e?A^OJbi$<(>ged0o}Lzbr0D2qQLxA3VcF#5df&c%%k%pC z_wR3NYGVEE(W6Hhh8Y?fqAfC+OyD3EODH`({lS9=J9q8`AeBm6T3YzMGcz-7ZEYHj zMx)V~&1Q$gG4w*>jhws#}m#f8yXryC+C~U z`1tsuqP^K{Rw|XD3shHEi{?XpeSM+eoSYo1)w(dD#ik#GkY~@Hm6eqVUYB7Q5=Ndr zeOgynw`b2D8Znxin?>0WLatxGUQtm|QBiU7#}wE{v` zE79qQ-%ki34hQ+;590L_Ldfha>Fp)d!~y~G@F9PBpO089!Qyrkhl5){BH^9@$<8K( zknwSGHUObsMZ!iMoIj7rNmN(E>qSis+-{VX!tckAKO!Om1qJYW@$I*me+7H^5OR62 z3=ZN&Qctf5g8}2?{1^qN6CZvUtlqoFPd{WbR8}H06H+N04!rjsKKcl1 zHT-^*mm?zsDJcj9aOMowuALuH=N;9*{^ImQhp>7z6pDG^{(i2SkbpPdKwMlnD;pTV zci*9=hFgSsgt0MHSEIZfVy=i!h!F4{kf=Wi^-z~Cfu=;}wcszLg7?YD27=Y7B$L-n$w>wz2ZCjda2n2$gD3L&|#yjuu z2}MPrp#d%zrl%1bi`-l|95{0Zk&&pa4elc$u-j2t3A-JBKOHBPB04%)Buo62QdtSL z8t=akwHjq*n3-7tKg75Bwl+ctsi@#u^m@|L64uIo{WZ*0`actu3LkvHRe$=4-LlzP zG&h4@(OoVyHR0!>Q z7SEqkLVx}VyB!`6&AgGFjjSwOzRV5%{dYK>@c97X&K-RDB~&Whx&>fx5P5mX%|&i5 zES9AaijKycZ{po|gH^j7J|8vvUAr(f#a_qZa-p>qnVHb*(cO)YKgK7YfW2`_r7##U zGXuN*KZ*8JQ)q0&kt0Y?hs%Wn2Ovn)Km8PaeXKu8NC11$U9)D%%lmw+13^*}qN6FH z-+sfTOGr$_fdjDHVKCt4O^lA>^Up#50d{oY$`u$4$j*jDf}1ztbmFV80BqTUEnC3* z|HA1lEy&D-(a5<(Lh$z69KdWwOblM!|B#xBHEXEZd%eW%CT=(JdO4k+urdq>AVk>9 z6Q`5N<>FSIpHBh-Vlss_`<9lKNk2L}#ngwjPIdq7ye|T3ar}q0{vjtPwx88zODg-bLj7E4oP%5!@EeCk*lKg)F X^{k9p7{Urq00000NkvXXu0mjf6Vk;2 literal 0 HcmV?d00001 diff --git a/apps/infoclk/metadata.json b/apps/infoclk/metadata.json new file mode 100644 index 000000000..bb6dea3a4 --- /dev/null +++ b/apps/infoclk/metadata.json @@ -0,0 +1,38 @@ +{ + "id": "infoclk", + "name": "Informational clock", + "version": "0.08", + "description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked", + "readme": "README.md", + "icon": "icon.png", + "type": "clock", + "tags": "clock", + "supports": [ + "BANGLEJS2" + ], + "allow_emulator": true, + "storage": [ + { + "name": "infoclk.app.js", + "url": "app.js" + }, + { + "name": "infoclk.settings.js", + "url": "settings.js" + }, + { + "name": "infoclk-font.js", + "url": "font.js" + }, + { + "name": "infoclk.img", + "url": "icon.js", + "evaluate": true + } + ], + "data": [ + { + "name": "infoclk.json" + } + ] +} \ No newline at end of file diff --git a/apps/infoclk/settings.js b/apps/infoclk/settings.js new file mode 100644 index 000000000..d12225f99 --- /dev/null +++ b/apps/infoclk/settings.js @@ -0,0 +1,571 @@ +(function (back) { + const SETTINGS_FILE = "infoclk.json"; + const storage = require('Storage'); + + let config = Object.assign({ + seconds: { + // Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display. + // The seconds will be shown unless one of these conditions is enabled here, and currently true. + hideLocked: false, // Hide the seconds when the display is locked. + hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage. + hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds + hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes + hideEnd: 700, // The time when the seconds are shown again + hideAlways: false, // Always hide (never show) the seconds + }, + + date: { + // Settings related to the display of the date + mmdd: true, // If true, display the month first. If false, display the date first. + separator: '-', // The character that goes between the month and date + monthName: false, // If false, display the month as a number. If true, display the name. + monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name. + dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name. + }, + + bottomLocked: { + display: 'weather' // What to display in the bottom row when locked: + // 'weather': The current temperature and weather description + // 'steps': Step count + // 'health': Step count and bpm + // 'progress': Day progress bar + // false: Nothing + }, + + shortcuts: [ + //8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked + // false = no shortcut + // '#LAUNCHER' = open the launcher + // any other string = name of app to open + 'stlap', 'bgtimer', 'pomoplus', 'alarm', + 'rpnsci', 'calendar', 'torch', 'weather' + ], + + swipe: { + // 3 shortcuts to launch upon swiping: + // false = no shortcut + // '#LAUNCHER' = open the launcher + // any other string = name of app to open + up: 'messages', // Swipe up or swipe down, due to limitation of event handler + left: '#LAUNCHER', + right: '#LAUNCHER', + }, + + dayProgress: { + // A progress bar representing how far through the day you are + enabledLocked: true, // Whether this bar is enabled when the watch is locked + enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked + color: [0, 0, 1], // The color of the bar + start: 700, // The time of day that the bar starts filling + end: 2200, // The time of day that the bar becomes full + reset: 300 // The time of day when the progress bar resets from full to empty + }, + + lowBattColor: { + // The text can change color to indicate that the battery is low + level: 20, // The percentage where this happens + color: [1, 0, 0] // The color that the text changes to + } + }, storage.readJSON(SETTINGS_FILE)); + + function saveSettings() { + storage.writeJSON(SETTINGS_FILE, config); + } + + function hourToString(hour) { + if (storage.readJSON('setting.json')['12hour']) { + if (hour == 0) return '12 AM'; + else if (hour < 12) return `${hour} AM`; + else if (hour == 12) return '12 PM'; + else return `${hour - 12} PM`; + } else return '' + hour; + } + + // The menu for configuring when the seconds are shown + function showSecondsMenu() { + E.showMenu({ + '': { + 'title': 'Seconds display', + 'back': showMainMenu + }, + 'Show seconds': { + value: !config.seconds.hideAlways, + onchange: value => { + config.seconds.hideAlways = !value; + saveSettings(); + } + }, + '...unless locked': { + value: config.seconds.hideLocked, + onchange: value => { + config.seconds.hideLocked = value; + saveSettings(); + } + }, + '...unless battery below': { + value: config.seconds.hideBattery, + min: 0, + max: 100, + format: value => `${value}%`, + onchange: value => { + config.seconds.hideBattery = value; + saveSettings(); + } + }, + '...unless between these 2 times...': () => { + E.showMenu({ + '': { + 'title': 'Hide seconds between', + 'back': showSecondsMenu + }, + 'Enabled': { + value: config.seconds.hideTime, + onchange: value => { + config.seconds.hideTime = value; + saveSettings(); + } + }, + 'Start hour': { + value: Math.floor(config.seconds.hideStart / 100), + format: hourToString, + min: 0, + max: 23, + wrap: true, + onchange: hour => { + minute = config.seconds.hideStart % 100; + config.seconds.hideStart = (100 * hour) + minute; + saveSettings(); + } + }, + 'Start minute': { + value: config.seconds.hideStart % 100, + min: 0, + max: 59, + wrap: true, + onchange: minute => { + hour = Math.floor(config.seconds.hideStart / 100); + config.seconds.hideStart = (100 * hour) + minute; + saveSettings(); + } + }, + 'End hour': { + value: Math.floor(config.seconds.hideEnd / 100), + format: hourToString, + min: 0, + max: 23, + wrap: true, + onchange: hour => { + minute = config.seconds.hideEnd % 100; + config.seconds.hideEnd = (100 * hour) + minute; + saveSettings(); + } + }, + 'End minute': { + value: config.seconds.hideEnd % 100, + min: 0, + max: 59, + wrap: true, + onchange: minute => { + hour = Math.floor(config.seconds.hideEnd / 100); + config.seconds.hideEnd = (100 * hour) + minute; + saveSettings(); + } + } + }); + } + }); + } + + // Available month/date separators + const SEPARATORS = [ + { name: 'Slash', char: '/' }, + { name: 'Dash', char: '-' }, + { name: 'Space', char: ' ' }, + { name: 'Comma', char: ',' }, + { name: 'None', char: '' } + ]; + + // Available bottom row display options + const BOTTOM_ROW_OPTIONS = [ + { name: 'Weather', val: 'weather' }, + { name: 'Step count', val: 'steps' }, + { name: 'Steps + BPM', val: 'health' }, + { name: 'Day progresss bar', val: 'progress' }, + { name: 'Nothing', val: false } + ]; + + // The menu for configuring which apps have shortcut icons + function showShortcutMenu() { + //Builds the shortcut options + let shortcutOptions = [ + { name: 'Nothing', val: false }, + { name: 'Launcher', val: '#LAUNCHER' }, + ]; + + let infoFiles = storage.list(/\.info$/).sort((a, b) => { + if (a.name < b.name) return -1; + else if (a.name > b.name) return 1; + else return 0; + }); + for (let infoFile of infoFiles) { + let appInfo = storage.readJSON(infoFile); + if (appInfo.src) shortcutOptions.push({ + name: appInfo.name, + val: appInfo.id + }); + } + + E.showMenu({ + '': { + 'title': 'Shortcuts', + 'back': showMainMenu + }, + 'Top first': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[0]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[0] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Top second': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[1]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[1] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Top third': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[2]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[2] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Top fourth': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[3]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[3] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Bottom first': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[4]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[4] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Bottom second': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[5]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[5] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Bottom third': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[6]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[6] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Bottom fourth': { + value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[7]), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.shortcuts[7] = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Swipe up': { + value: shortcutOptions.map(item => item.val).indexOf(config.swipe.up), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.swipe.up = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Swipe left': { + value: shortcutOptions.map(item => item.val).indexOf(config.swipe.left), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.swipe.left = shortcutOptions[value].val; + saveSettings(); + } + }, + 'Swipe right': { + value: shortcutOptions.map(item => item.val).indexOf(config.swipe.right), + format: value => shortcutOptions[value].name, + min: 0, + max: shortcutOptions.length - 1, + wrap: false, + onchange: value => { + config.swipe.right = shortcutOptions[value].val; + saveSettings(); + } + }, + }); + } + + const COLOR_OPTIONS = [ + { name: 'Black', val: [0, 0, 0] }, + { name: 'Blue', val: [0, 0, 1] }, + { name: 'Green', val: [0, 1, 0] }, + { name: 'Cyan', val: [0, 1, 1] }, + { name: 'Red', val: [1, 0, 0] }, + { name: 'Magenta', val: [1, 0, 1] }, + { name: 'Yellow', val: [1, 1, 0] }, + { name: 'White', val: [1, 1, 1] } + ]; + + // Workaround for being unable to use == on arrays: convert them into strings + function colorString(color) { + return `${color[0]} ${color[1]} ${color[2]}`; + } + + //Shows the top level menu + function showMainMenu() { + E.showMenu({ + '': { + 'title': 'Informational Clock', + 'back': back + }, + 'Seconds display': showSecondsMenu, + 'Day of week format': { + value: config.date.dayFullName, + format: value => value ? 'Full name' : 'Abbreviation', + onchange: value => { + config.date.dayFullName = value; + saveSettings(); + } + }, + 'Date format': () => { + E.showMenu({ + '': { + 'title': 'Date format', + 'back': showMainMenu, + }, + 'Order': { + value: config.date.mmdd, + format: value => value ? 'Month first' : 'Date first', + onchange: value => { + config.date.mmdd = value; + saveSettings(); + } + }, + 'Separator': { + value: SEPARATORS.map(item => item.char).indexOf(config.date.separator), + format: value => SEPARATORS[value].name, + min: 0, + max: SEPARATORS.length - 1, + wrap: true, + onchange: value => { + config.date.separator = SEPARATORS[value].char; + saveSettings(); + } + }, + 'Month format': { + // 0 = number only + // 1 = abbreviation + // 2 = full name + value: config.date.monthName ? (config.date.monthFullName ? 2 : 1) : 0, + format: value => ['Number', 'Abbreviation', 'Full name'][value], + min: 0, + max: 2, + wrap: true, + onchange: value => { + if (value == 0) config.date.monthName = false; + else { + config.date.monthName = true; + config.date.monthFullName = (value == 2); + } + saveSettings(); + } + } + }); + }, + 'Bottom row': { + value: BOTTOM_ROW_OPTIONS.map(item => item.val).indexOf(config.bottomLocked.display), + format: value => BOTTOM_ROW_OPTIONS[value].name, + min: 0, + max: BOTTOM_ROW_OPTIONS.length - 1, + wrap: true, + onchange: value => { + config.bottomLocked.display = BOTTOM_ROW_OPTIONS[value].val; + saveSettings(); + } + }, + 'Shortcuts': showShortcutMenu, + 'Day progress': () => { + E.showMenu({ + '': { + 'title': 'Day progress', + 'back': showMainMenu + }, + 'Enable while locked': { + value: config.dayProgress.enabledLocked, + onchange: value => { + config.dayProgress.enableLocked = value; + saveSettings(); + } + }, + 'Enable while unlocked': { + value: config.dayProgress.enabledUnlocked, + onchange: value => { + config.dayProgress.enabledUnlocked = value; + saveSettings(); + } + }, + 'Color': { + value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.dayProgress.color)), + format: value => COLOR_OPTIONS[value].name, + min: 0, + max: COLOR_OPTIONS.length - 1, + wrap: false, + onchange: value => { + config.dayProgress.color = COLOR_OPTIONS[value].val; + saveSettings(); + } + }, + 'Start hour': { + value: Math.floor(config.dayProgress.start / 100), + format: hourToString, + min: 0, + max: 23, + wrap: true, + onchange: hour => { + minute = config.dayProgress.start % 100; + config.dayProgress.start = (100 * hour) + minute; + saveSettings(); + } + }, + 'Start minute': { + value: config.dayProgress.start % 100, + min: 0, + max: 59, + wrap: true, + onchange: minute => { + hour = Math.floor(config.dayProgress.start / 100); + config.dayProgress.start = (100 * hour) + minute; + saveSettings(); + } + }, + 'End hour': { + value: Math.floor(config.dayProgress.end / 100), + format: hourToString, + min: 0, + max: 23, + wrap: true, + onchange: hour => { + minute = config.dayProgress.end % 100; + config.dayProgress.end = (100 * hour) + minute; + saveSettings(); + } + }, + 'End minute': { + value: config.dayProgress.end % 100, + min: 0, + max: 59, + wrap: true, + onchange: minute => { + hour = Math.floor(config.dayProgress.end / 100); + config.dayProgress.end = (100 * hour) + minute; + saveSettings(); + } + }, + 'Reset hour': { + value: Math.floor(config.dayProgress.reset / 100), + format: hourToString, + min: 0, + max: 23, + wrap: true, + onchange: hour => { + minute = config.dayProgress.reset % 100; + config.dayProgress.reset = (100 * hour) + minute; + saveSettings(); + } + }, + 'Reset minute': { + value: config.dayProgress.reset % 100, + min: 0, + max: 59, + wrap: true, + onchange: minute => { + hour = Math.floor(config.dayProgress.reset / 100); + config.dayProgress.reset = (100 * hour) + minute; + saveSettings(); + } + } + }); + }, + 'Low battery color': () => { + E.showMenu({ + '': { + 'title': 'Low battery color', + back: showMainMenu + }, + 'Low battery threshold': { + value: config.lowBattColor.level, + min: 0, + max: 100, + format: value => `${value}%`, + onchange: value => { + config.lowBattColor.level = value; + saveSettings(); + } + }, + 'Color': { + value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.lowBattColor.color)), + format: value => COLOR_OPTIONS[value].name, + min: 0, + max: COLOR_OPTIONS.length - 1, + wrap: false, + onchange: value => { + config.lowBattColor.color = COLOR_OPTIONS[value].val; + saveSettings(); + } + } + }); + }, + }); + } + + showMainMenu(); +}); \ No newline at end of file diff --git a/apps/pomoplus/app.js b/apps/pomoplus/app.js new file mode 100644 index 000000000..73af5c935 --- /dev/null +++ b/apps/pomoplus/app.js @@ -0,0 +1,157 @@ +Bangle.POMOPLUS_ACTIVE = true; //Prevent the boot code from running. To avoid having to reload on every interaction, we'll control the vibrations from here when the user is in the app. + +const storage = require("Storage"); +const common = require("pomoplus-com.js"); + +//Expire the state if necessary +if ( + common.settings.pausedTimerExpireTime != 0 && + !common.state.running && + (new Date()).getTime() - common.state.pausedTime > common.settings.pausedTimerExpireTime +) { + common.state = common.STATE_DEFAULT; +} + +function drawButtons() { + //Draw the backdrop + const BAR_TOP = g.getHeight() - 24; + g.setColor(0, 0, 1).setFontAlign(0, -1) + .clearRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .fillRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .setColor(1, 1, 1); + + if (!common.state.wasRunning) { //If the timer was never started, only show a play button + g.drawImage(common.BUTTON_ICONS.play, g.getWidth() / 2, BAR_TOP); + } else { + g.drawLine(g.getWidth() / 2, BAR_TOP, g.getWidth() / 2, g.getHeight()); + if (common.state.running) { + g.drawImage(common.BUTTON_ICONS.pause, g.getWidth() / 4, BAR_TOP) + .drawImage(common.BUTTON_ICONS.skip, g.getWidth() * 3 / 4, BAR_TOP); + } else { + g.drawImage(common.BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP) + .drawImage(common.BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP); + } + } +} + +function drawTimerAndMessage() { + g.reset() + .setFontAlign(0, 0) + .setFont("Vector", 36) + .clearRect(0, 24, 176, 152) + + //Draw the timer + .drawString((() => { + let timeLeft = common.getTimeLeft(); + let hours = timeLeft / 3600000; + let minutes = (timeLeft % 3600000) / 60000; + let seconds = (timeLeft % 60000) / 1000; + + function pad(number) { + return ('00' + parseInt(number)).slice(-2); + } + + if (hours >= 1) return `${parseInt(hours)}:${pad(minutes)}:${pad(seconds)}`; + else return `${parseInt(minutes)}:${pad(seconds)}`; + })(), g.getWidth() / 2, g.getHeight() / 2) + + //Draw the phase label + .setFont("Vector", 12) + .drawString(((currentPhase, numShortBreaks) => { + if (!common.state.wasRunning) return "Not started"; + else if (currentPhase == common.PHASE_WORKING) return `Work ${numShortBreaks + 1}/${common.settings.numShortBreaks + 1}` + else if (currentPhase == common.PHASE_SHORT_BREAK) return `Short break ${numShortBreaks + 1}/${common.settings.numShortBreaks}`; + else return "Long break!"; + })(common.state.phase, common.state.numShortBreaks), + g.getWidth() / 2, g.getHeight() / 2 + 18); + + //Update phase with vibation if needed + if (common.getTimeLeft() <= 0) { + common.nextPhase(true); + } +} + +drawButtons(); +Bangle.on("touch", (button, xy) => { + //If we support full touch and we're not touching the keys, ignore. + //If we don't support full touch, we can't tell so just assume we are. + if (xy !== undefined && xy.y <= g.getHeight() - 24) return; + + if (!common.state.wasRunning) { + //If we were never running, there is only one button: the start button + let now = (new Date()).getTime(); + common.state = { + wasRunning: true, + running: true, + startTime: now, + pausedTime: now, + elapsedTime: 0, + phase: common.PHASE_WORKING, + numShortBreaks: 0 + }; + setupTimerInterval(); + drawButtons(); + + } else if (common.state.running) { + //If we are running, there are two buttons: pause and skip + if (button == 1) { + //Record the exact moment that we paused + let now = (new Date()).getTime(); + common.state.pausedTime = now; + + //Stop the timer + common.state.running = false; + clearInterval(timerInterval); + timerInterval = undefined; + drawTimerAndMessage(); + drawButtons(); + + } else { + common.nextPhase(false); + } + + } else { + //If we are stopped, there are two buttons: Reset and continue + if (button == 1) { + //Reset the timer + common.state = common.STATE_DEFAULT; + drawTimerAndMessage(); + drawButtons(); + + } else { + //Start the timer and record old elapsed time and when we started + let now = (new Date()).getTime(); + common.state.elapsedTime += common.state.pausedTime - common.state.startTime; + common.state.startTime = now; + common.state.running = true; + drawTimerAndMessage(); + setupTimerInterval(); + drawButtons(); + } + } +}); + +let timerInterval; + +function setupTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + } + setTimeout(() => { + timerInterval = setInterval(drawTimerAndMessage, 1000); + drawTimerAndMessage(); + }, common.timeLeft % 1000); +} + +drawTimerAndMessage(); +if (common.state.running) { + setupTimerInterval(); +} + +//Save our state when the app is closed +E.on('kill', () => { + storage.writeJSON(common.STATE_PATH, common.state); +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/pomoplus/boot.js b/apps/pomoplus/boot.js new file mode 100644 index 000000000..edc233853 --- /dev/null +++ b/apps/pomoplus/boot.js @@ -0,0 +1,19 @@ +const POMOPLUS_storage = require("Storage"); +const POMOPLUS_common = require("pomoplus-com.js"); + +function setNextTimeout() { + setTimeout(() => { + //Make sure that the pomoplus app isn't in the foreground. The pomoplus app handles the vibrations when it is in the foreground in order to avoid having to reload every time the user changes state. That means that when the app is in the foreground, we shouldn't do anything here. + //We do this after the timer rather than before because the timer will start before the app executes. + if (Bangle.POMOPLUS_ACTIVE === undefined) { + POMOPLUS_common.nextPhase(true); + setNextTimeout(); + POMOPLUS_storage.writeJSON(POMOPLUS_common.STATE_PATH, POMOPLUS_common.state) + } + }, POMOPLUS_common.getTimeLeft()); +} + +//Only start the timeout if the timer is running +if (POMOPLUS_common.state.running) { + setNextTimeout(); +} \ No newline at end of file diff --git a/apps/pomoplus/common.js b/apps/pomoplus/common.js new file mode 100644 index 000000000..b1cd42de8 --- /dev/null +++ b/apps/pomoplus/common.js @@ -0,0 +1,118 @@ +const storage = require("Storage"); +const heatshrink = require("heatshrink"); + +exports.STATE_PATH = "pomoplus.state.json"; +exports.SETTINGS_PATH = "pomoplus.json"; + +exports.PHASE_WORKING = 0; +exports.PHASE_SHORT_BREAK = 1; +exports.PHASE_LONG_BREAK = 2; + +exports.BUTTON_ICONS = { + play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), + pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")), + reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI=")), + skip: heatshrink.decompress(atob("jEYwMAwEIgHAhkA8EOgHwh8A/EPwH8h/A/0P8H/h/w/+P/H/5/8//v/3/AAoICBwQUCDQIgCEwQsCGQQ4CHwRECA")) +}; + +exports.settings = storage.readJSON(exports.SETTINGS_PATH); +if (!exports.settings) { + exports.settings = { + workTime: 1500000, //Work for 25 minutes + shortBreak: 300000, //5 minute short break + longBreak: 900000, //15 minute long break + numShortBreaks: 3, //3 short breaks for every long break + pausedTimerExpireTime: 21600000, //If the timer was left paused for >6 hours, reset it on next launch + widget: false //If a widget is added in the future, whether the user wants it + }; +} + +//Store the minimal amount of information to be able to reconstruct the state of the timer at any given time. +//This is necessary because it is necessary to write to flash to let the timer run in the background, so minimizing the writes is necessary. +exports.STATE_DEFAULT = { + wasRunning: false, //If the timer ever was running. Used to determine whether to display a reset button + running: false, //Whether the timer is currently running + startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. + pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. + elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. + phase: exports.PHASE_WORKING, //What phase the timer is currently in + numShortBreaks: 0 //Number of short breaks that have occured so far +}; +exports.state = storage.readJSON(exports.STATE_PATH); +if (!exports.state) { + exports.state = exports.STATE_DEFAULT; +} + +//Get the number of milliseconds until the next phase change +exports.getTimeLeft = function () { + if (!exports.state.wasRunning) { + //If the timer never ran, the time left is just the amount of work time. + return exports.settings.workTime; + } else if (exports.state.running) { + //If the timer is running, the time left is current time - start time + preexisting time + var runningTime = (new Date()).getTime() - exports.state.startTime + exports.state.elapsedTime; + } else { + //If the timer is not running, the same as above but use when the timer was paused instead of now. + var runningTime = exports.state.pausedTime - exports.state.startTime + exports.state.elapsedTime; + } + + if (exports.state.phase == exports.PHASE_WORKING) { + return exports.settings.workTime - runningTime; + } else if (exports.state.phase == exports.PHASE_SHORT_BREAK) { + return exports.settings.shortBreak - runningTime; + } else { + return exports.settings.longBreak - runningTime; + } +} + +//Get the next phase to change to +exports.getNextPhase = function () { + if (exports.state.phase == exports.PHASE_WORKING) { + if (exports.state.numShortBreaks < exports.settings.numShortBreaks) { + return exports.PHASE_SHORT_BREAK; + } else { + return exports.PHASE_LONG_BREAK; + } + } else { + return exports.PHASE_WORKING; + } +} + +//Change to the next phase and update numShortBreaks, and optionally vibrate. DOES NOT WRITE STATE CHANGE TO STORAGE! +exports.nextPhase = function (vibrate) { + a = { + startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. + pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. + elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. + phase: exports.PHASE_WORKING, //What phase the timer is currently in + numShortBreaks: 0 //Number of short breaks that have occured so far + } + let now = (new Date()).getTime(); + exports.state.startTime = now; //The timer is being reset, so say it starts now. + exports.state.pausedTime = now; //This prevents a paused timer from having the start time moved to the future and therefore having been run for negative time. + exports.state.elapsedTime = 0; //Because we are resetting the timer, we no longer need to care about whether it was paused previously. + + let oldPhase = exports.state.phase; //Cache the old phase because we need to remember it when counting the number of short breaks + exports.state.phase = exports.getNextPhase(); + + if (oldPhase == exports.PHASE_SHORT_BREAK) { + //If we just left a short break, increase the number of short breaks + exports.state.numShortBreaks++; + } else if (oldPhase == exports.PHASE_LONG_BREAK) { + //If we just left a long break, set the number of short breaks to zero + exports.state.numShortBreaks = 0; + } + + if (vibrate) { + if (exports.state.phase == exports.PHASE_WORKING) { + Bangle.buzz(750, 1); + } else if (exports.state.phase == exports.PHASE_SHORT_BREAK) { + Bangle.buzz(); + setTimeout(Bangle.buzz, 400); + } else { + Bangle.buzz(); + setTimeout(Bangle.buzz, 400); + setTimeout(Bangle.buzz, 600); + } + } +} \ No newline at end of file diff --git a/apps/pomoplus/icon.js b/apps/pomoplus/icon.js new file mode 100644 index 000000000..020df6f5c --- /dev/null +++ b/apps/pomoplus/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgQNhvQFCoXX7ooQFy0N7vdCYQDDIJ4YDC6QwC7oDBDwYcLN4QRDAApELEYYWGBQKEFCAoWKBIRaIBIIFEEA5iEEooUEDgYhEFxgDB6BfFGAgeFCAYXCBhAEJGYYrGMxQJDC4aIHHIjhGBQQXNE5L5DCIq3GIgiuHWQ7SHhoXKABRCBC6plDC6xVKJBgXVGIIWVAH5pTQK4X/cRAXvJCnQC7JIUCwQwTFwYwTCwgwSC4owQIwowQCw4wPCxAYNFxIYMCxgYJCpoZHBpI=")) diff --git a/apps/pomoplus/icon.png b/apps/pomoplus/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4ea72f270d5633755b90ede38c67cdc7e121e4c5 GIT binary patch literal 2122 zcmV-Q2(|Z#P)vh7l(-y(Eff0j)pz+4-E-b}m z^l1UU0sJkAf_|W6kOHNyIGvr_*sva<9~!I?Jkb@W=RYLoDrwjOgGsO==$POP?*AJ? zSCc6Ck74~?@$_KUUg&FvZ4|l$oLEWlunY$TCD7ZzgDoC!VDxiJA(A+Jtq2t^n77$H1Ng ztpSQwJ=k^Nu))U!-!rTvwB5Ja32X+Mq1>xv*ma?1pdWZDjMKW-$&wBvQB$WupU?w9 zFK{%B)8p>9U^DQr!3MA%P`fHo9R~uS18maZgis$YDz)Squsw{^_eP_jRZs}y^!ls0GZ8n#dH z^Dv&C9gUiH2t91@Tj0&EIK5Gq1D=PW!B4^ZgbI06{(jkDXIGq_NTQ$!+zsqv6mw|`o zg7=_i!6pRb9(?3y>1l7U;M-m`?1t|@Db$~vvm$VpVeLZi14p{zbP%jZ@Pe>I8F=5L z7kCBit4S2J)N}xs0S*~N`OwS>(*S4}a2{A|a0q9B%voSo@OL3!an)hi+dy1y0nLI< zz<#iONffk!t^)r6a&)omZ#U?mVcYUx%GkVTuNyo#e_>b(y6hIB?FCI4gu>net*aO+ z_dEsm3TQXbZ5V>nhMtgfcpK1S6=Kc^{lT#PUGemA5(OQ=m%%=7XajJkUrPWb;B!J} z!Z@vpz+(%5pK&v-=Yg*SyFn%3KEY83KAX{Og;-+F$pkEL;035xu43;2ju8I%e1*p{Kx30%;{fu|?QEU?W&o)OuZ<%HtroAJ{S) zHBlUmnhLmzx*?nP+=6WZwj25rKv$ezPom&u;39A@ZlTQp4fl|t$ z7sA#9?{vk}Q^2c96ubt6-gXNx0XiARX(@?7^hdlI8A_G`D+o;W#C$cLIC=B_G=Oao4_uF@$@C&Qog5K&^!sYRXOl; z^i4u*in!v_n-5Vw3g$xlUEtm%3SI!-4HwvTX7vNu0{p^Y3>c7Ggv^~BgTsE`^g3`B zYz7nn_W@r5HoLk3^C=|2)`0FRdZRs6b2nda1^WColtjU4;NON_6ABDIfvX4Wb1&Td7KEh zU=eoQupi?#Y6I?>x)-NMeY#+Ema3{`ad=C+2iy&Mb~FlBh4J(?p&tRC4ddxCzX9Cr zdRVS^S?nM!CY?iI5<=5loaB~kDJ*y?-)mWsqeSz<9ANPyo19{{_7HH!>iw-`23 ztbn!!n$%8kR;TVIH2D7f#8~_FrXC%bEXr*Ks>3qRiHfdy+R}P8e``Hoj?P2@m)^>Q z=kK+|a&udc-iWh}=^8G84QB?KcQH?|%tC#7cG*Xu1OF<3&Sf5Xugdq#*SuaSBK7H& z;5bv{C411A^+qI?ijNyRSpOe z!E-{FZ?!BWGy!&$IVTv^`!+90)U*xsODad|Rbn|4_2?@gCEPar#knUU3x1_Ecnxke?*w|O7WgF{$T@O9*mppEU@fBZwzZU=8L*tOCG;6XX9Y)r-z`L&ywqE?`h_kT z{6+3`y0eiS-e8eBf;;^lSqi+f(V+N`4^JERsL)!2wrrqfQmACuh)@h13*&TfsSbHr z7Dbb&33K0vzX4cTFmT@BoX|VKP#C8(jbiywY1*Giw*sADcN(k{ zx(Dbs_q^zhq=rp_62U)#L32kf6N1Tls8UP*2Ty*k3rtPI!Tc}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yb}Jpb3EHthA;}Wgh!W@g+}zZ>5(ej@)Wnk1 z6ovB4k_-iRPv3y>Mm}+%S<0R+jv*erj1rvBzcKy?0-#2mfDs6AN&+Pa>1V>HnHqAV z77z@@(I6TPB7z}6P{C*_8Vw?XAwW>UXet^FB7z}6Pyx+TQT{Ft9Zp`Z43JAaUHx3v IIVCg!009H(%m4rY literal 0 HcmV?d00001 diff --git a/apps/pomoplus/img/play.png b/apps/pomoplus/img/play.png new file mode 100644 index 0000000000000000000000000000000000000000..6c20c24c54d72382a69d5aa3685e3ad8fa6174ba GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yc1y0~8~!E(g(OQ{BTAg}b8}PkN*J7rQWHy3 zQxwWGOEMJPJ$(bh8~Mb6W+{8RIEHxeGD>hh{|3|p0snyj#-=9C2r~@X0!AQ!iBn3$ z4MMg6W+*i*!fOFFEF#JRN-Sc6X(ooo7L0`Sk>mqp!-$O^vMqpFM#v(HEg)nO)hxhe z5!Ed~w}_-v1k+3m4NGw}vjFA@nppr#T`Vx0s71pJhM7k-3(yUwngzHFrq}{P2E!~M zE0T#d9ApVeaX^Z}FdqmdKI;Vst0LE7sJpcdz literal 0 HcmV?d00001 diff --git a/apps/pomoplus/img/reset.png b/apps/pomoplus/img/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..7a317d09795c09aabab4836c8b9ec4a6c5fe4db8 GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-y4rWEypL5fJLXst}5hc#~xw)x%B@E6*sfi`2 zDGKG8B^e6tp1uL$jeO!jvy?qu978;K86`NMe`EX)1VD{A0V5FLlmto;($9oXGe{1f z0r=!#<`T33mnFm)4$@1M1?ZL#YdAG9F5Ihr6 z+Y*@Ba0@^#qn0Hw!$D@ESU?R+kPQc!NApyK;scrn5w#2l1q%qkf(q;dY8wu*0%FN% zSp*9WoHWgoDM=Q9QU(a%bS@SNqAfr+7@3X5OcWuaEWlc}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-y4mSQL5h-thLXst}5hc#~xw)x%B@E6*sfi`2 zDGKG8B^e6tp1uL$jeO!jvy?qu978;K86`NMe`EL$1VD{M15(v9flMY<6DjJ!7SPfX zxCOMb1jPbcSb|{zwJpJF0W~ebX#uq?!D|6EEFowCrIrw70VS3&l9DSy7Lf{w_5rB| z!_<>x0V5E=G!jFD)Dvw1IfjEAL6imL8cvi2lo(FX0!j_XYXLP3$7unz4998#H4VqG mfZB$mSU?NIK^D-oMFBE@re=!7?Z%8XAhxHgpUXO@geCy$z({uh literal 0 HcmV?d00001 diff --git a/apps/pomoplus/metadata.json b/apps/pomoplus/metadata.json new file mode 100644 index 000000000..fcacae35d --- /dev/null +++ b/apps/pomoplus/metadata.json @@ -0,0 +1,37 @@ +{ + "id": "pomoplus", + "name": "Pomodoro Plus", + "version": "0.05", + "description": "A configurable pomodoro timer that runs in the background.", + "icon": "pomodoro.png", + "type": "app", + "tags": "pomodoro,cooking,tools", + "supports": [ + "BANGLEJS", + "BANGLEJS2" + ], + "allow_emulator": true, + "storage": [ + { + "name": "pomoplus.app.js", + "url": "app.js" + }, + { + "name": "pomoplus.img", + "url": "icon.js", + "evaluate": true + }, + { + "name": "pomoplus.boot.js", + "url": "boot.js" + }, + { + "name": "pomoplus-com.js", + "url": "common.js" + }, + { + "name": "pomoplus.settings.js", + "url": "settings.js" + } + ] +} \ No newline at end of file diff --git a/apps/pomoplus/settings.js b/apps/pomoplus/settings.js new file mode 100644 index 000000000..1ff52340a --- /dev/null +++ b/apps/pomoplus/settings.js @@ -0,0 +1,94 @@ +const SETTINGS_PATH = 'pomoplus.json'; +const storage = require("Storage"); + +(function (back) { + let settings = storage.readJSON(SETTINGS_PATH); + if (!settings) { + settings = { + workTime: 1500000, //Work for 25 minutes + shortBreak: 300000, //5 minute short break + longBreak: 900000, //15 minute long break + numShortBreaks: 3, //3 short breaks for every long break + pausedTimerExpireTime: 21600000, //If the timer was left paused for >6 hours, reset it on next launch + widget: false //If a widget is added in the future, whether the user wants it + }; + } + + function save() { + storage.writeJSON(SETTINGS_PATH, settings); + } + + const menu = { + '': { 'title': 'Pomodoro Plus' }, + '< Back': back, + 'Work time': { + value: settings.workTime, + step: 60000, //1 minute + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.workTime = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + 'Short break time': { + value: settings.shortBreak, + step: 60000, + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.shortBreak = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + '# Short breaks': { + value: settings.numShortBreaks, + step: 1, + min: 0, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.numShortBreaks = value; + save(); + } + }, + 'Long break time': { + value: settings.longBreak, + step: 60000, + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.longBreak = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + 'Timer expiration': { + value: settings.pausedTimerExpireTime, + step: 900000, //15 minutes + min: 0, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.pausedTimerExpireTime = value; + save(); + }, + format: function (value) { + if (value == 0) return "Off" + else return `${Math.floor(value / 3600000)}h ${(value % 3600000) / 60000}m` + } + }, + }; + E.showMenu(menu) +}) \ No newline at end of file diff --git a/apps/random/app.js b/apps/random/app.js new file mode 100644 index 000000000..c3001a6d1 --- /dev/null +++ b/apps/random/app.js @@ -0,0 +1,205 @@ +let n = 1; +let diceSides = 6; +let replacement = false; +let min = 1; +let max = 10; + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function showCoinMenu() { + E.showMenu({ + '': { + 'title': 'Coin flip', + 'back': showMainMenu + }, + '# of coins': { + value: n, + step: 1, + min: 1, + onchange: value => n = value + }, + 'Go': () => { + let resultMenu = { + '': { + 'title': 'Result', + 'back': showCoinMenu + } + }; + let heads = 0; + for (let i = 0; i < n; i++) { + let coin = Math.random() < 0.5; + if (coin) heads++; + resultMenu[`${i + 1}: ${coin ? 'Heads' : 'Tails'}`] = () => { }; + } + let tails = n - heads; + resultMenu[`${heads} heads, ${Math.round(100 * heads / n)}%`] = () => { }; + resultMenu[`${tails} tails, ${Math.round(100 * tails / n)}%`] = () => { }; + + E.showMenu(resultMenu); + } + }); +} + + +function showDiceMenu() { + E.showMenu({ + '': { + 'title': 'Dice roll', + 'back': showMainMenu + }, + '# of dice': { + value: n, + step: 1, + min: 1, + onchange: value => n = value + }, + '# of sides': { + value: diceSides, + step: 1, + min: 2, + onchange: value => diceSides = value + }, + 'Go': () => { + let resultMenu = { + '': { + 'title': 'Result', + 'back': showDiceMenu + } + }; + let sum = 0; + let min = diceSides + 1; + let max = 0; + for (let i = 0; i < n; i++) { + let roll = Math.floor(Math.random() * diceSides + 1); + sum += roll; + if (roll < min) min = roll; + if (roll > max) max = roll; + resultMenu[`${i + 1}: ${roll}`] = () => { }; + } + resultMenu[`Sum: ${sum}`] = () => { }; + resultMenu[`Min: ${min}`] = () => { }; + resultMenu[`Max: ${max}`] = () => { }; + resultMenu[`Average: ${sum / n}`] = () => { }; + + E.showMenu(resultMenu); + } + }); +} + + +function showCardMenu() { + E.showMenu({ + '': { + 'title': 'Card draw', + 'back': showMainMenu + }, + '# of cards': { + value: Math.min(52, n), + step: 1, + min: 1, + max: 52, + onchange: value => n = value + }, + 'Replacement': { + value: replacement, + onchange: value => { + replacement = value; + if (replacement && n > 52) n = 52; + } + }, + 'Go': () => { + n = Math.min(n, 52); + SUITS = ['Spades', 'Diamonds', 'Clubs', 'Hearts']; + RANKS = ['Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King']; + class Card { + constructor(suit, rank) { + this.suit = suit; + this.rank = rank; + } + + //Can't use == to check equality, so using Java-inspired .equals() + equals(other) { + return this.suit == other.suit && this.rank == other.rank; + } + } + + let resultMenu = { + '': { + 'title': 'Result', + 'back': showCardMenu + } + }; + let cards = []; + for (let i = 0; i < n; i++) { + let newCard; + while (true) { + newCard = new Card( + SUITS[Math.floor(Math.random() * SUITS.length)], + RANKS[Math.floor(Math.random() * RANKS.length)]); + + if (replacement) break; //If we are doing replacement, skip the check for duplicates and stop looping + + if (!cards.map(card => card.equals(newCard)).includes(true)) break; //If there are no duplicates found, stop looping + } + + cards.push(newCard); + resultMenu[`${newCard.rank} of ${newCard.suit}`] = () => { }; + } + + E.showMenu(resultMenu); + } + }); +} + +function showNumberMenu() { + E.showMenu({ + '': { + 'title': 'Number choice', + 'back': showMainMenu + }, + 'Minimum': { + value: min, + step: 1, + onchange: value => min = value + }, + 'Maximum': { + value: max, + step: 1, + onchange: value => max = value + }, + '# of choices': { + value: n, + min: 1, + step: 1, + onchange: value => n = value + }, + 'Go': () => { + let resultMenu = { + '': { + 'title': 'Result', + 'back': showNumberMenu + } + }; + for (let i = 0; i < n; i++) { + let value = Math.floor(min + Math.random() * (max - min + 1)); + resultMenu[`${i + 1}: ${value}`] = () => { }; + } + E.showMenu(resultMenu); + } + }); +} + +function showMainMenu() { + E.showMenu({ + '': { + 'title': 'Random' + }, + 'Coin': showCoinMenu, + 'Dice': showDiceMenu, + 'Card': showCardMenu, + 'Number': showNumberMenu + }); +} + +showMainMenu(); \ No newline at end of file diff --git a/apps/random/icon.js b/apps/random/icon.js new file mode 100644 index 000000000..de84a0893 --- /dev/null +++ b/apps/random/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIEBgf///AAoMHAoPgAoMPAoPwAoMfAoP4AoM/AoP8AoN/AoP+AoIEBAAMAgIbBD4OAAoPgFYIFC4A3BAoQCFEAQFBEwV/AoIyCn+ALYYFFCIIFDDoIECFIQFCGoQFCIIQFCJoQFCNoIuEHwQuCHwQuCQYQuCR4QuCTYQuGAoIcDg4oEg4oEg6mCAoQuDAoIuDAFQvFAsIA==")) \ No newline at end of file diff --git a/apps/random/icon.png b/apps/random/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e4af7d7a19d5228f8758605027b3c506d0fa4c77 GIT binary patch literal 378 zcmV-=0fqjFP)sE>U;ut)p4tfn;FzDPnNEiEK2=o<)73=@l|)GEJl_#* zl$1?^bb6QUus%x6BtRPI3t=b+N{+-)JTlMjM9B`M%rg)+09(Ptxw~#J4`7dKdJLRS zAoDNHWjnx_UlxEhza)TSemMZu{89i>^S5Nq>jcEa&CdiVF+U4HH80&w(qUt)O3hby zpxk^Vz!dYV01S=sQ1H!8upJoAKP=FJU;rYyBO(_N2q2a>CUk { + if (entryTerminated) { + if (liftOnNumberPress) liftStack(); + x = '0.'; + entryTerminated = false; + liftOnNumberPress = false; + feedback(true); + updateDisplay(); + } else if (!x.includes('.')) { + x += '.'; + feedback(true); + updateDisplay(); + } else { + feedback(false); + } + } +}; + +class ModeButton { + constructor(currentMode) { + if (currentMode == 'memstore' || currentMode == 'memrec') { + this.label = 'Exit'; + } else if (currentMode == 'operation') { + this.label = 'Num'; + } else { + this.label = 'Op'; + } + } + + onclick() { + if (mode == 'memstore' || mode == 'memrec') { + mode = 'operation'; + } else if (mode == 'operation') { + mode = 'number'; + } else { + mode = 'operation'; + } + feedback(true); + drawButtons(); + } +} + +class OperationButton { + constructor(label) { + this.label = label; + } + + onclick() { + if (this.label == '/' && parseFloat(x) == 0) { + feedback(false); + return; + } + let result = this.getResult(); + x = '' + result; + y = z; + z = t; + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + } + + getResult() { + let numX = parseFloat(x); + return { + '+': y + numX, + '-': y - numX, + '/': y / numX, + '*': y * numX, + '^': Math.pow(y, numX) + }[this.label]; + } +} + +class OneNumOpButton { + constructor(label) { + this.label = label; + } + + onclick() { + result = { + '+-': '' + -parseFloat(x), + 'Sin': '' + Math.sin(parseFloat(x)), + 'Cos': '' + Math.cos(parseFloat(x)), + 'Tan': '' + Math.tan(parseFloat(x)), + 'Asin': '' + Math.asin(parseFloat(x)), + 'Acos': '' + Math.acos(parseFloat(x)), + 'Atan': '' + Math.atan(parseFloat(x)), + 'Log': '' + (Math.log(parseFloat(x)) / Math.log(10)) + }[this.label]; + if (isNaN(result) || result == 'NaN') feedback(false); + else { + x = result; + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + } + } +} + +let ClearButton = { + label: 'Clr', + onclick: () => { + if (x != '0') { + x = '0'; + updateDisplay(); + } else if (y != 0 || z != 0 || t != 0) { + y = 0; + z = 0; + t = 0; + E.showMessage('Registers cleared!'); + setTimeout(() => { + drawButtons(); + updateDisplay(); + }, 250); + } else { + memory = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + storage.writeJSON(MEMORY_FILE, memory); + E.showMessage('Memory cleared!'); + setTimeout(() => { + drawButtons(); + updateDisplay(); + }, 250); + } + entryTerminated = false; + liftOnNumberPress = false; + feedback(true); + } +}; + +let SwapButton = { + label: 'Swp', + onclick: () => { + oldX = x; + x = '' + y; + y = parseFloat(oldX); + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + } +}; + +let RotateButton = { + label: 'Rot', + onclick: () => { + oldX = x; + x = '' + y; + y = z; + z = t; + t = parseFloat(oldX); + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + } +}; + +let EnterButton = { + label: 'Ent', + onclick: () => { + liftStack(); + entryTerminated = true; + liftOnNumberPress = false; + feedback(true); + updateDisplay(); + } +}; + +let ScientificButton = { + label: 'Sci', + onclick: () => { + mode = 'scientific'; + feedback(true); + drawButtons(); + } +}; + +class ConstantButton { + constructor(label, value) { + this.label = label; + this.value = value; + } + + onclick() { + if (entryTerminated && liftOnNumberPress) liftStack(); + x = '' + this.value; + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + } +} + +let MemStoreButton = { + label: 'Sto', + onclick: () => { + mode = 'memstore'; + feedback(true); + drawButtons(); + } +}; + +let MemRecallButton = { + label: 'Rec', + onclick: () => { + mode = 'memrec'; + feedback(true); + drawButtons(); + } +}; + +class MemStoreIn { + constructor(register) { + this.register = register; + this.label = '' + register; + } + + onclick() { + memory[this.register] = parseFloat(x); + storage.writeJSON(MEMORY_FILE, memory); + mode = 'scientific'; + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + drawButtons(); + } +} + +class MemRecFrom { + constructor(register) { + this.register = register; + this.label = '' + register; + } + + onclick() { + x = '' + memory[this.register]; + mode = 'scientific'; + entryTerminated = true; + liftOnNumberPress = true; + feedback(true); + updateDisplay(); + drawButtons(); + } +} + +const BUTTONS = { + 'number': [ + [new NumberButton(7), new NumberButton(8), new NumberButton(9), new ModeButton('number')], + [new NumberButton(4), new NumberButton(5), new NumberButton(6), new NumberButton(0)], + [new NumberButton(1), new NumberButton(2), new NumberButton(3), DecimalPointButton] + ], + 'operation': [ + [new OperationButton('+'), new OperationButton('-'), ClearButton, new ModeButton('operation')], + [new OperationButton('*'), new OperationButton('/'), SwapButton, EnterButton], + [new OperationButton('^'), new OneNumOpButton('+-'), RotateButton, ScientificButton] + ], + 'scientific': [ + [new OneNumOpButton('Sin'), new OneNumOpButton('Cos'), new OneNumOpButton('Tan'), new ModeButton('scientific')], + [new OneNumOpButton('Asin'), new OneNumOpButton('Acos'), new OneNumOpButton('Atan'), MemStoreButton], + [new OneNumOpButton('Log'), new ConstantButton('e', Math.E), new ConstantButton('pi', Math.PI), MemRecallButton] + ], + 'memstore': [ + [new MemStoreIn(7), new MemStoreIn(8), new MemStoreIn(9), new ModeButton('memstore')], + [new MemStoreIn(4), new MemStoreIn(5), new MemStoreIn(6), new MemStoreIn(0)], + [new MemStoreIn(1), new MemStoreIn(2), new MemStoreIn(3), new ModeButton('memstore')] + ], + 'memrec': [ + [new MemRecFrom(7), new MemRecFrom(8), new MemRecFrom(9), new ModeButton('memrec')], + [new MemRecFrom(4), new MemRecFrom(5), new MemRecFrom(6), new MemRecFrom(0)], + [new MemRecFrom(1), new MemRecFrom(2), new MemRecFrom(3), new ModeButton('memrec')] + ], +}; + +let x = '0'; +let y = 0; +let z = 0; +let t = 0; +let memJSON = storage.readJSON(MEMORY_FILE); +if (memJSON) { + let memory = memJSON; +} else { + let memory = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +} +let mode = 'number'; +let entryTerminated = false; +let liftOnNumberPress = false; + +function liftStack() { + t = z; + z = y; + y = parseFloat(x); +} + +function feedback(acceptable) { + if (acceptable) Bangle.buzz(50, 0.5); + else Bangle.buzz(200, 1); +} + +function drawButtons() { + g.reset().clearRect(0, 44, 175, 175).setFont("Vector", 15).setFontAlign(0, 0); + //Draw lines + for (let x = 44; x <= 176; x += 44) { + g.drawLine(x, 44, x, 175); + } + for (let y = 44; y <= 176; y += 44) { + g.drawLine(0, y, 175, y); + } + for (let row = 0; row < 3; row++) { + for (let col = 0; col < 4; col++) { + g.drawString(BUTTONS[mode][row][col].label, 22 + 44 * col, 66 + 44 * row); + } + } +} + +function getFontSize(length) { + let size = Math.floor(176 / length); //Characters of width needed per pixel + size *= (20 / 12); //Convert to height + // Clamp to between 6 and 20 + if (size < 6) return 6; + else if (size > 20) return 20; + else return Math.floor(size); +} + +function updateDisplay() { + g.clearRect(0, 24, 175, 43).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1).setFont("Vector", getFontSize(x.length)).drawString(x, 176, 24); +} + +Bangle.on("touch", (button, xy) => { + let row = Math.floor((xy.y - 44) / 44); + let col = Math.floor(xy.x / 44); + if (row < 0) { // Tap number to show registers + g.clearRect(0, 24, 175, 43).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1) + .setFont("Vector", getFontSize(x.length)).drawString('' + t, 176, 24); + + g.clearRect(0, 44, 175, 63).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1) + .setFont("Vector", getFontSize(x.length)).drawString('' + z, 176, 44); + + g.clearRect(0, 64, 175, 83).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1) + .setFont("Vector", getFontSize(x.length)).drawString('' + y, 176, 64); + + g.clearRect(0, 84, 175, 103).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1) + .setFont("Vector", getFontSize(x.length)).drawString(x, 176, 84); + + setTimeout(() => { + drawButtons(); + updateDisplay(); + }, 500); + } else { + if (row > 2) row = 2; + if (col < 0) col = 0; + if (col > 3) col = 3; + + BUTTONS[mode][row][col].onclick(); + } +}); + +Bangle.on("swipe", dir => { + if (dir == -1) { + if (entryTerminated) ClearButton.onclick(); + else if (x.length == 1) x = '0'; + else x = x.substring(0, x.length - 1); + + feedback(true); + updateDisplay(); + } else if (dir == 0) { + EnterButton.onclick(); + } +}); + +g.clear().reset(); + +drawButtons(); +updateDisplay(); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/rpnsci/icon.js b/apps/rpnsci/icon.js new file mode 100644 index 000000000..24ea29035 --- /dev/null +++ b/apps/rpnsci/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcCkmSpICEpEEBAwCICP4CCk/yCP4RVyf/AAXkCK0///5Nf4RffcYR/AQkAAERr/CKn+CK9//+f/41O/mT5IRO/+eLJ8/CIw+BAAP8CIkn+QRQMQY1MCKM8z5rP8mf/KzO8mTCJ1/CIP/8j7pCP4RMA==")) \ No newline at end of file diff --git a/apps/rpnsci/icon.png b/apps/rpnsci/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..474abf7e3358c8f47326adacfdf4cd4bd2918cba GIT binary patch literal 765 zcmVEX>4Tx04R}tkv&MmKpe$iTcsiuQ9Fn@1guUL#ELj-6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfbaGO3krMxx6k5c1aNLh~_a1le0DryARI_6OP&La) zCE`LRyD9`<5kNosFoKxGOnpuilkgm0_we!cF3PjK&;2=il$^-`pFljzbi*RvAfDc| zbk6(4VOEqB;&bA0gDyz?$aUG}H_ioz{X8>lq*L?6VPc`s#&R38qM;H`5l0kNqkMnH zWrgz=XSG~q&3p0}hH~1nGy0}1FmMZWuerT7_i_3Fq^Yaq4RCM> zj1?$*-Q(R|?Y;ebrrF;Qq{ni*j5N?100006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<^vKDD+ff07Ipvt02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{007lVL_t(&-tCy(4udcVMvL+NUzy#k$)aO}W>o49Hzr1_ zhw}GTT2pOlCeLf$C$_e@0RX@(0M*=dX9oZPV9a(aVLv7FsozR5fZ1j_%;OmG7RUZn z%-hUD002;?3SC($2_XOgxG{jF_IY-|)dw_rgBj)k03f2wqGwO{oklJ3e!Tz7DRpG6 zubTLs7FP9stWX*4ApV~(B4|W%4t1kzT29*{FIV$^NK(e*e~v``k4DzL{u)Zl09YZD vmDuT>c$p;jcw+3pcXB62(4YqZ03g*KO-@5vd@SZ300000NkvXXu0mjfLa#qu literal 0 HcmV?d00001 diff --git a/apps/rpnsci/icon.xcf b/apps/rpnsci/icon.xcf new file mode 100644 index 0000000000000000000000000000000000000000..9a41dbe325b45a3a516fe41d2dd3bcab725a4fc4 GIT binary patch literal 4021 zcmd^C&ubGw6rN4mSd%7g?GF_^j8U<*ZfT;e-UNG44}xItx=ALR(PX#mZfqh#Jc{5^ z{|BMqMW~<`=|A92{{(Le9tGF$?as7YX+1;=p}ytK``&xAv-9Jdon2eD)_A8{=Z_Xk zB?ehkO86CY0%{m2CZQuM2$4djphM6j&>4lP-Zb*^5I)`E*6Y3qcvL@uW4OCA^*JHN z^Vk`>AKMWc4r@w;`Rtj0xqqjzFYoId&VQ<3-_^ZJ(O1SiUf8(i7D{x(;s!9)f?eZiiD3+I}GG zU0wyp1y!xM9kb=u1h1nP#90cg%Np2TzNFlSkai=0zfih*Wl04i=52SK`bt!#j?f@bXvWYoK+<1g8M3@=|Is?52#-n(u@%eFTn0c!-AsW{9&Tu z(sEgU { + let hours = Math.floor(time / 3600000); + let minutes = Math.floor((time % 3600000) / 60000); + let seconds = Math.floor((time % 60000) / 1000); + let hundredths = Math.floor((time % 1000) / 10); + + if (hours >= 1) return `${hours}:${pad(minutes)}:${pad(seconds)}`; + else return `${minutes}:${pad(seconds)}:${pad(hundredths)}`; + })(), g.getWidth() / 2, g.getHeight() / 2); + + //Draw the lap labels if necessary + if (lapHistory.splits.length >= 1) { + let lastLap = lapHistory.splits.length; + let curLap = lastLap + 1; + + g.setFont("Vector", 12) + .drawString((() => { + let lapTime = time - lapHistory.splits[lastLap - 1]; + let hours = Math.floor(lapTime / 3600000); + let minutes = Math.floor((lapTime % 3600000) / 60000); + let seconds = Math.floor((lapTime % 60000) / 1000); + let hundredths = Math.floor((lapTime % 1000) / 10); + + if (hours == 0) return `Lap ${curLap}: ${pad(minutes)}:${pad(seconds)}:${pad(hundredths)}`; + else return `Lap ${curLap}: ${hours}:${pad(minutes)}:${pad(seconds)}:${pad(hundredths)}`; + })(), g.getWidth() / 2, g.getHeight() / 2 + 18) + .drawString((() => { + let lapTime; + if (lastLap == 1) lapTime = lapHistory.splits[lastLap - 1]; + else lapTime = lapHistory.splits[lastLap - 1] - lapHistory.splits[lastLap - 2]; + let hours = Math.floor(lapTime / 3600000); + let minutes = Math.floor((lapTime % 3600000) / 60000); + let seconds = Math.floor((lapTime % 60000) / 1000); + let hundredths = Math.floor((lapTime % 1000) / 10); + + if (hours == 0) return `Lap ${lastLap}: ${pad(minutes)}:${pad(seconds)}:${pad(hundredths)}`; + else return `Lap ${lastLap}: ${hours}:${pad(minutes)}:${pad(seconds)}:${pad(hundredths)}`; + })(), g.getWidth() / 2, g.getHeight() / 2 + 30); + } +} + +drawButtons(); + +function firstTimeStart(now, time) { + state = { + wasRunning: true, + sessionStart: Math.floor(now), + running: true, + startTime: now, + pausedTime: 0, + elapsedTime: 0, + }; + lapFile = 'stlap-' + state.sessionStart + '.json'; + setupTimerInterval(); + Bangle.buzz(200); + drawButtons(); +} + +function split(now, time) { + lapHistory.splits.push(time); + Bangle.buzz(); +} + +function pause(now, time) { + //Record the exact moment that we paused + state.pausedTime = now; + + //Stop the timer + state.running = false; + stopTimerInterval(); + Bangle.buzz(200); + drawTime(); + drawButtons(); +} + +function reset(now, time) { + //Record the time + lapHistory.splits.push(time); + lapHistory.final = true; + storage.writeJSON(lapFile, lapHistory); + + //Reset the timer + state = STATE_DEFAULT; + lapHistory = { + final: false, + splits: [] + }; + Bangle.buzz(500); + drawTime(); + drawButtons(); +} + +function start(now, time) { + //Start the timer and record when we started + state.elapsedTime += (state.pausedTime - state.startTime); + state.startTime = now; + state.running = true; + setupTimerInterval(); + Bangle.buzz(200); + drawTime(); + drawButtons(); +} + +Bangle.on("touch", (button, xy) => { + //In gesture mode, just turn on the light and then return + if (gestureMode) { + Bangle.setLCDPower(true); + return; + } + + //If we support full touch and we're not touching the keys, ignore. + //If we don't support full touch, we can't tell so just assume we are. + if (xy !== undefined && xy.y <= g.getHeight() - 48) return; + + let now = (new Date()).getTime(); + let time = getTime(); + + if (!state.wasRunning) { + if (storage.read('stlapview.app.js') !== undefined) { + //If we were never running and stlapview is installed, there are two buttons: open stlapview and start the timer + if (button == 1) load('stlapview.app.js'); + else firstTimeStart(now, time); + } + //If stlapview there is only one button: the start button + else firstTimeStart(now, time); + } else if (state.running) { + //If we are running, there are two buttons: lap and pause + if (button == 1) split(now, time); + else pause(now, time); + + } else { + //If we are stopped, there are two buttons: reset and continue + if (button == 1) reset(now, time); + else start(now, time); + } +}); + +Bangle.on('swipe', direction => { + let now = (new Date()).getTime(); + let time = getTime(); + + if (gestureMode) { + Bangle.setLCDPower(true); + if (!state.wasRunning) firstTimeStart(now, time); + else if (state.running) pause(now, time); + else start(now, time); + } else { + gestureMode = true; + Bangle.setOptions({ + lockTimeout: 0 + }); + drawTime(); + drawButtons(); + } +}); + +setWatch(() => { + let now = (new Date()).getTime(); + let time = getTime(); + + if (gestureMode) { + Bangle.setLCDPower(true); + if (state.running) split(now, time); + else reset(now, time); + } +}, BTN1, { repeat: true }); + +let timerInterval; + +function setupTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + } + timerInterval = setInterval(drawTime, 10); +} + +function stopTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + timerInterval = undefined; + } +} + +drawTime(); +if (state.running) { + setupTimerInterval(); +} + +//Save our state when the app is closed +E.on('kill', () => { + storage.writeJSON(STATE_PATH, state); + if (state.wasRunning) { + storage.writeJSON(lapFile, lapHistory); + } +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/stlap/icon.js b/apps/stlap/icon.js new file mode 100644 index 000000000..32281b7ab --- /dev/null +++ b/apps/stlap/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/apps/stlap/icon.png b/apps/stlap/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 diff --git a/apps/stlap/img/pause.png b/apps/stlap/img/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..ad31dadcff3ecffba6c7e015a4b2ecdcccc15b0f GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yb}Jpb3EHthA;}Wgh!W@g+}zZ>5(ej@)Wnk1 z6ovB4k_-iRPv3y>Mm}+%S<0R+jv*erj1rvBzcKy?0-#2mfDs6AN&+Pa>1V>HnHqAV z77z@@(I6TPB7z}6P{C*_8Vw?XAwW>UXet^FB7z}6Pyx+TQT{Ft9Zp`Z43JAaUHx3v IIVCg!009H(%m4rY literal 0 HcmV?d00001 diff --git a/apps/stlap/img/play.png b/apps/stlap/img/play.png new file mode 100644 index 0000000000000000000000000000000000000000..6c20c24c54d72382a69d5aa3685e3ad8fa6174ba GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-yc1y0~8~!E(g(OQ{BTAg}b8}PkN*J7rQWHy3 zQxwWGOEMJPJ$(bh8~Mb6W+{8RIEHxeGD>hh{|3|p0snyj#-=9C2r~@X0!AQ!iBn3$ z4MMg6W+*i*!fOFFEF#JRN-Sc6X(ooo7L0`Sk>mqp!-$O^vMqpFM#v(HEg)nO)hxhe z5!Ed~w}_-v1k+3m4NGw}vjFA@nppr#T`Vx0s71pJhM7k-3(yUwngzHFrq}{P2E!~M zE0T#d9ApVeaX^Z}FdqmdKI;Vst0LE7sJpcdz literal 0 HcmV?d00001 diff --git a/apps/stlap/img/reset.png b/apps/stlap/img/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..7a317d09795c09aabab4836c8b9ec4a6c5fe4db8 GIT binary patch literal 2891 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x}o+U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZrAngg+8q`tEBwU%ILU0~a$O;i)fy)2qxZK-jn}iQd+~#)f)B|X zUp;uVW_9yw1&+D~3oA>_T^^nPy}5LgY%Ja#-(B6mySyPl-}vk+31*YKX2G31W}IBt zwJ@CJV!yyKLuKRBGbS%lsX2b#mZ@fM_eY)o#r1u1rs;+X3PR_N4sYo+?^tf0+x*$C zuthX=>W?`USC()K*%!rr{Lx!{Ftst=?MzA5JPRisv0D+D$zdj@k2D{xSoLy>eu_<) zWq#qcQ=!-PsMjZ2EMKovJolM^P{a`-m5zV_SI*QM9T!(kPEvIJT`2#5!RzTKmQGmB z;?~C+vcR$I?}PdeyQ8)3Eg!13K6ZHC{kVpQd135=z~VXYlaFW4VK|unz$^c!c4@-1 z$zP(pyX6@7-ac@&)I0cF!)e)s2PY@^-kjQG(pn=|VZS*46jSYXhwrnlM;FU4=XaR3 z%j+Hkd;0;8#!c62@7=w6@Am%mS%3dB99mg)Jm;_40${YTC3(BM0BIoj>AbrhNO2Z; zL>4nJ=qZCRW5rVYGN2%PiKnkC`!i-y4rWEypL5fJLXst}5hc#~xw)x%B@E6*sfi`2 zDGKG8B^e6tp1uL$jeO!jvy?qu978;K86`NMe`EX)1VD{A0V5FLlmto;($9oXGe{1f z0r=!#<`T33mnFm)4$@1M1?ZL#YdAG9F5Ihr6 z+Y*@Ba0@^#qn0Hw!$D@ESU?R+kPQc!NApyK;scrn5w#2l1q%qkf(q;dY8wu*0%FN% zSp*9WoHWgoDM=Q9QU(a%bS@SNqAfr+7@3X5OcWuaEWl lapTimes[slowestIndex]) slowestIndex = i; + } + + let lapMenu = { + '': { + 'title': fileNameToDateString(fileName), + 'back': () => { E.showMenu(mainMenu); } + }, + }; + lapMenu[`Total time: ${msToHumanReadable(fileData[fileData.length - 1])}`] = () => { }; + lapMenu[`Fastest lap: ${fastestIndex + 1}: ${msToHumanReadable(lapTimes[fastestIndex])}`] = () => { }; + lapMenu[`Slowest lap: ${slowestIndex + 1}: ${msToHumanReadable(lapTimes[slowestIndex])}`] = () => { }; + lapMenu[`Average lap: ${msToHumanReadable(fileData[fileData.length - 1] / fileData.length)}`] = () => { }; + + for (let i = 0; i < lapTimes.length; i++) { + lapMenu[`Lap ${i + 1}: ${msToHumanReadable(lapTimes[i])}`] = () => { }; + } + + lapMenu.Delete = () => { + E.showMenu({ + '': { + 'title': 'Are you sure?', + 'back': () => { E.showMenu(lapMenu); } + }, + 'Yes': () => { + storage.erase(fileName); + showMainMenu(); + }, + 'No': () => { E.showMenu(lapMenu); } + }); + }; + + E.showMenu(lapMenu); +} + +function showMainMenu() { + let LAP_FILES = storage.list(/stlap-[0-9]*\.json/); + LAP_FILES.sort(); + LAP_FILES.reverse(); + + let mainMenu = { + '': { + 'title': 'Sessions' + } + }; + + //I know eval is evil, but I can't think of any other way to do this. + for (let lapFile of LAP_FILES) { + mainMenu[fileNameToDateString(lapFile)] = eval(`(function() { + view('${lapFile}'); + })`); + } + + if (LAP_FILES.length == 0) { + mainMenu['No data'] = _ => { load(); }; + } + + E.showMenu(mainMenu); +} + +showMainMenu(); \ No newline at end of file diff --git a/apps/stlapview/icon.js b/apps/stlapview/icon.js new file mode 100644 index 000000000..32281b7ab --- /dev/null +++ b/apps/stlapview/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/apps/stlapview/icon.png b/apps/stlapview/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 diff --git a/apps/stlapview/metadata.json b/apps/stlapview/metadata.json new file mode 100644 index 000000000..aa54a7b67 --- /dev/null +++ b/apps/stlapview/metadata.json @@ -0,0 +1,27 @@ +{ + "id": "stlapview", + "name": "Stopwatch laps", + "version": "0.02", + "description": "Optional lap viewer for my stopwatch app", + "icon": "icon.png", + "type": "app", + "tags": "tools,app", + "supports": [ + "BANGLEJS2" + ], + "allow_emulator": true, + "storage": [ + { + "name": "stlapview.app.js", + "url": "app.js" + }, + { + "name": "stlapview.img", + "url": "icon.js", + "evaluate": true + } + ], + "dependencies": { + "stlap": "app" + } +} \ No newline at end of file From 02a931f5fd675c4e0b7610e4262eafe455728d63 Mon Sep 17 00:00:00 2001 From: hugh Date: Sat, 5 Nov 2022 00:27:49 +0000 Subject: [PATCH 002/228] Added PrimeTimeLato app --- apps/primetimelato/README.md | 13 ++++ apps/primetimelato/app.js | 106 ++++++++++++++++++++++++++++++ apps/primetimelato/app.png | Bin 0 -> 710 bytes apps/primetimelato/icon.js | 1 + apps/primetimelato/metadata.json | 15 +++++ apps/primetimelato/screenshot.png | Bin 0 -> 2287 bytes 6 files changed, 135 insertions(+) create mode 100644 apps/primetimelato/README.md create mode 100644 apps/primetimelato/app.js create mode 100644 apps/primetimelato/app.png create mode 100644 apps/primetimelato/icon.js create mode 100644 apps/primetimelato/metadata.json create mode 100644 apps/primetimelato/screenshot.png diff --git a/apps/primetimelato/README.md b/apps/primetimelato/README.md new file mode 100644 index 000000000..6760fe41a --- /dev/null +++ b/apps/primetimelato/README.md @@ -0,0 +1,13 @@ +# Prime Time Lato (clock) + +A watchface that displays time and its prime factors in the Lato font. +For example when the time is 21:05, the prime factors are 5,421. +Displays 'Prime Time!' when the time is a prime number. + +![](screenshot.jpg) + +Written by: [Hugh Barney](https://github.com/hughbarney) + +Adapted from primetime by [Eve Bury](https://www.github.com/eveeeon) + +For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js new file mode 100644 index 000000000..2031fd53b --- /dev/null +++ b/apps/primetimelato/app.js @@ -0,0 +1,106 @@ +const h = g.getHeight(); +const w = g.getWidth(); + +Graphics.prototype.setFontLato = function(scale) { + // Actual height 41 (43 - 3) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('ACEfAokB/AGEn+AAocDBgsfBgkB+A6Yg4FEgYgF/4FEv/gHIhAEh/+DwgYEgP/4AeJn4eF/hDEDwxrE/4eFKAgeFgJDERQ5DEXJI0Eh//IIZlB/4pDAoP/HgQYBAAIaBPARDDv4JBj5WBh5ZCv4CBPATPCeQcPwDYECAIMGPId4gEeSIYMHDIYMCExZAGh6ICn5QEK4RnGOgqBGaoKOECYSiDn78FAFgxFR4bcCKISOBgF+RwYCBTgQMCOIQMCj5eCBgIfDBgQfCBgSbDWoSHC/61CAwYMUAAYzCAAZNCBkYAfgYmBgJ7CTYsPTYQMBgJnBgYMCn4MBv64CBgMPXYSBBD4cfBgQIBgf3RwK7CvybCGQXPBgIfBGQIMBeoU/DoIMCF4IMDgP8BgQmBn8AEwb+BBgQIBIQJAC4BYBIgTNBDYIMBg///xRDn//OoIMBcYISBBgUHNATpCgjCngIEDFIM+AoV8TYKYCMIJQBAQLCCgZcBYQIJBX4UHCwSJBYQaSCMYQDCYQabEBgngW4V4WoQBBOgIMN+AoCEwY+BGYZfBYwQIBv+AG4X+j/8K4TCB+bECM4P+YgcD//B/67Ch/4h//K4SSBv5IBAAdAWqMYAokDD4t+AokfBgi0DAAX/Bgkf7wSE/IME/6PBCQfBBgcD/AME/ypCCQXABgYSBBgg+BBgYSBBgatBBggSBBgYSBBgcB/4ACZ4QGDZ4IMLFARAEAAJaEBjQAUhAnFMgIABuD5CVwWAboUDLwZ0DNYKLBM4IMBh50C55rBCoWACAIMC+EPFIN4EQQMBg4MSHgYzEBgIzBIAUAvhND+EH8DZCBAN/bIfwMQP/OgIMBLgalCBgKlDg//V4kfCQIAqWgYzC/gFDIAJgBZoS3CAwV//4TDh/+j5UCCQOAn4SD8DPCCQSbCCQR/BNAV/3i1CB4PzAYLCBgP8AYIMCv+HBgcP+AMDCQMHEwb2BYQLPDgYMBIAKoBOYLPCwDNBZ4UQBQPhJ4J0EDYJbCZ4R7EAoZiDSoaUDADBNBFQj5EKwQMEGAoMEOgQFCnAMEQIYFBgaOCBgTRBBgc/AYIMCaIQMCgb2CBgX/JQIMCDAQMCh/8JoYYBJoiNDBgIYDBgIYDBgPzUwkfUwisBOokfDAYMCQIq/ERwwAcn4pCgfwg42D//B/6hBCAP+KwYQBMQKbBgF//9+g5EBh4YB4CfC/EHDwK1Dn7PD8A0BgF4gEeAIUHBgQBBBi4mEGYpAEAIMP4BNELQpnGOgM/ZYaBFGQMPYos/JAIAuj4xEKgJrBfoX//hEE/4TDCQJSCCoN/gZfBjCBCj+AgaOCAIiKBg4OCgKKBvgbCWYMDToK1CgE8JIQMC4ZCBBgU4HYTNCz4JBEwV7KoQzCUIYvBLYZNBn60CLQPfCQcDM4LHCEALHDZ4TaCCYaODHYK8fh6FDEwKSCF4Uf4COCBgJsBn4MDDIJPDVgYAZA='))), + 46, + atob("CxMeHh4eHh4eHh4eDQ=="), + 52+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setFontLatoSmall = function(scale) { + // Actual height 21 (20 - 0) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AB0B/+ch/88EAgQPHg/AgE+A4cPwEAvwTHoEAscQgc/wE//EP/0Au0wgEz/ED/+A//gg9jgEDiEAAIIACgcBwF+h0H+EwmPBwOf/4AB8Ng4cDg84hkfwFAvA/EgZfBneAwOEjkMkPAuccgPzwAbBCQJeBuZBBKYNxxkOhkgsFjgUD+B5BNox+Bgf4g/Y8F/wcDjkYjFw4HB40Gg8wjkfQgJLBj6bB84fBjCQDU5AAFj/wg//+A1B9xEGhkAg18gPx//+bgJmBAAckAQOgDQa+BgI2BjQ0HsBoBJogSBZgX/DQIrBG4IUFAAs7CQKVBFJ0GB4kEAQNwYQYACEIKaBRYX4RoQGCCoIADMwMf/kB8HwdQPA4EGGAMwmEBwPAjkHwfACgMAv4iBAAxICuEMSQNguEDgf//Ef/5ZEmCDDAAkBgHgnEOgfA8Eeg0B2EwjOBweMh04sE/wZlBfYomCjkPEIM8VwNgsI+BhkYjHg4HnFoPv8EOQYIAFQwMHJAN8gEOW4PjAgMYQwUPcIN/cI4ACkCNBgP8hkPsFgsY+BHoMYD4PHjkGj/gkAxBAAoHBh/wgPxV4J8B7EwnnBwPGhkMnHggLUBg/gXI6oDCgLiBsE+gb0BjD7B74bDni1CAAk8gP3+EP/ZTBfYMYfYI+Bw0Mh148E+HwPj+A6DAAIpBj6HB/0EhkwPwPPgcE+EYt4ZB8EDDwMP4EAiAeDgUCgE4nEB4INBAAcBKQMcjuA8BhBAAh2BgY5BjwUBJAMMDwPGJoMYf4ImFAAoUCswhBEgMZEgPMDgL7BmYpBzAUDABnBwEDhhTBFIiIBh4PBuDKCCwR6BgITBDAKSB88Dh84jAKB/geBHAQcBj/wgPhcIMDgOBhkYn0w4exw0wwkhxkhxHBhl/hFj7BKBo0Mg0wnnjgF+LIkEbQc/gEH8EBHgM/sEH4bGBjEB/HAgf2JIKvBCgICBJ4MOaYv//kP//gsHDga/BjEw4HBw0GjkwnHhwH9/kH54hBnggDkB1D//AjydBPIIyBFIMDgcAFIILB4EGg0AFIILBCgLLHj4kB//+CgUwTwOAhi8DFIccZANghkD4Pgh/+D4L7IFIpuBmHBwOGFIMwsFhwcDaAJTCwECSRasBJgIjBgDXBgxMBEYMBwUAhAdEn+Ag//gF8vkDwHgPoJoIXgnhw0Dh/gjF/Mo8D//4NASDBIgICBPQJLBIgJZBwEAF4IwBgB9BIYPwE44pHBYoiHg4EBvAUBwAZBExMAv//FIQbCKYU8AgJABnPggeHwE4jyKBnC8CgC8LEQZ2PNBA4BgIgCDYMHCwMfJIYNCnwMBB4N4gEPBYY+CNAJyHU4U/QoIxCFwQNBjwCBnACBHgpoGAAz4BB4PAj5OB8E4hwDBsEDPwMYSQXAgx+BmE4TwIUBPAOAh65Bj5wBAAxTBwI+BhgiBsHAgYiBjChB4OAg8cDwJVBvgfHB4KCBvl8HQmBwEMXoNgKY/ghwUBPAP+SoPhwF8ghOH//+U4UwsEBwbnBKYXwgcPRQPngF+jzMBuAbBOYnAn8GgP4nEc8HA4aSBjkwmHhwPDzkGNwMgNwYwBDoMAQgKoBcYIqBfYhoBChQAFv/4YIPwewIgBboIoFSIJnBgEHAgPwj6nBDIQVEAwMH8BBBAQQ6BdIUeCAc/BgR4BHwI6BCYJTCEIQCBj4CBh4CCE4QVBDQP+dYYLBnwcBBIMBAQoUBg/Agf8KwIZBHYIoBNIQSBfYUcIQP4nkB8YZBDwMPLoK5BJIXz8EPg+A8EeU4KmCXgYFBngCBDwL2BLAJGBMwIHCB4JKBgF4BwIWBiBGCgQpBuEwg+BwF8hkPsFg+cDj0YjPg4HcgwhBmBXBCgJnDAALmB/+f//8JYMkIoIMCHYJqCAQUfBYICCUYU4EgkIJINAgETFIgPEgAhBHoJtEgb3DQoacBSAQAGkBLDGYMAGYMCQ4gYBgiLCAQMYAQIzBDQQAFsbZBMYMxzEBxlAhlmgFikCIBwEPLowABn//4P/aIMwCgJUBjBpB4FgWYISBMIP/OYYAEg4MBv/Ag4UBmEYEAPAhjlBsEwgLyCAAcBIYMf+EB4IUFHwcIj//8C5BLA4cB4EBBgMdjkA5BTBocAmQ+ByBGBx0AjlgDISoCfwJ4BQ4P4jKwB5kAgwTDsEP3+A//mg03mEwzOBwnMU4Ngv8zgfh+EYPAKEEFIIuB4BnBEoMAPgTcEHgMAh/4NQ8gBwOf/kcPoKWEyEAhvP//uv7UBQgiSETgJ4BAgLJBnPAgeHcweAGALDGfYMP/4XBAAxmBSoP4FYQgBNAsPcIN/8CBCmBADCgV/dwP/BAIpIYgQpHLoIpCdwT7Mj08EIL7BDoMwfYOA4EOhwxBT4KUFHwXwZ4ITB4EMaQNgmEDDoMYH4LeBgZtBgZdFHwscCgI+HwOAUoPgv5eIPp8QCgcD4YUBFIOYKYPGgFjKYMfwEIvAUCgi8Dj5qBcwJoGGQQADn7JCH4J9BbRHAKYoAEuBLBVIMHAQMPEIMPBoIUBJYIMCvhoDAAQZFDgkeBQUBDwIGBEYUBXgIKCgYUBNAM/wA+CSQi8CnE4gH3B4PwFIINBIIMPSQPh8AUCAAMBLQR3Bn4KBn4SBv4fDDgJlBgI2BTw0AU4XBFIMfgEx7EB3nAh+GgF4XgOBF4JRCGAP/8f+/+YbAINBkArGbwIQB/5BBAArwBgLSBhP/v/H/6QBBAIACjhrDKwVgdAwHBRQThBgIbD'))), + 32, + atob("BAcIDAwRDwUGBggMBAcECAwMDAwMDAwMDAwFBQwMDAgRDg4OEAwMDxAGCQ4LExARDREOCwwPDhUODQ0GCAYMCAYLDAoMCwcLDAUFCwURDAwMDAgJCAwLEAsLCgYGBgwA"), + 21+(scale<<8)+(1<<16) + ); + return this; +}; + +// creates a list of prime factors of n and outputs them as a string, if n is prime outputs "Prime Time!" +function primeFactors(n) { + const factors = []; + let divisor = 2; + + while (n >= 2) { + if (n % divisor == 0) { + factors.push(divisor); + n = n / divisor; + } else { + divisor++; + } + } + if (factors.length === 1) { + return "Prime Time!"; + } + else + return factors.toString(); +} + + +// converts time HR:MIN to integer HRMIN e.g. 15:35 => 1535 +function timeToInt(t) { + var arr = t.split(':'); + var intTime = parseInt(arr[0])*100+parseInt(arr[1]); + + return intTime; +} + +function draw() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var primeStr = primeFactors(timeToInt(timeStr)); + + g.reset(); + g.setColor(0,0,0); + g.fillRect(Bangle.appRect); + + //g.setFont("6x8", w/30); + g.setFontLato(); + g.setFontAlign(0, 0); + g.setColor(100,100,100); + g.drawString(timeStr, w/2, h/2); + + //g.setFont("6x8", w/60); + g.setFontLatoSmall(); + g.drawString(primeStr, w/2, 3*h/4); + queueDraw(); +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +g.clear(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); diff --git a/apps/primetimelato/app.png b/apps/primetimelato/app.png new file mode 100644 index 0000000000000000000000000000000000000000..2a84c62a07e71744a3721a4a7f8e3caa3721d71c GIT binary patch literal 710 zcmV;%0y+JOP)Px%fJsC_R9Hvtm^+TbFc5|(bXUD~@YEpQpp&!1vedHBEsjPT^cG zM;ybcdbe!c0b3UI$(&QzDXb~rLc)cF3yHL2;5Q1RAQ}#bY@%AN((!nFiCZidxfVfv zq8@m6zu)su!T0{Zv>2;NWb5I8d`y=R4S}elv5oT zp@7cHvoaD^fhBfzqXILTObFX76JR{ETrNp=IeN$>g@uvANx-k1=n7-f;;h%}JSysL zCJB~1XHvi?g~U6+dIzBR{eF)eohZ9Tz)=(9)udA$MNK+2bW#IGKE=$J^n=tSX-$t| zbzi6hOL`P&gasz8WR#?^Fjg3b;uemmED^O32&9AUmcGzol?iP_O(r z;Iv2Ke=3Yr)#-G?>I~TgUpu8*)7!rp1#1eQPN#>@ut-yCZq?hLLR$ewZGe(d77XNN zwYb;YpTZi~5=joEw%u;GwApOJ599iWa%m^o%Pg&_OR|)WrYo3fC#-~osWThTIba(q sW){O~!&=J{S%8WU7sF|*{dEfe0H(cp6H7fzivR!s07*qoM6N<$f*j~OtpET3 literal 0 HcmV?d00001 diff --git a/apps/primetimelato/icon.js b/apps/primetimelato/icon.js new file mode 100644 index 000000000..06f93e2ef --- /dev/null +++ b/apps/primetimelato/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json new file mode 100644 index 000000000..ab30724ba --- /dev/null +++ b/apps/primetimelato/metadata.json @@ -0,0 +1,15 @@ +{ "id": "primetime", + "name": "Prime Time Lato Clock", + "version": "0.01", + "type": "clock", + "description": "A clock that tells you the primes of the time in the Lato font", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"primetimelato.app.js","url":"app.js"}, + {"name":"primetimelato.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/primetimelato/screenshot.png b/apps/primetimelato/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6e7cc0d173c5b79feb938a18255d874f9359cc GIT binary patch literal 2287 zcmeHJX*3)79{nc~QA_bG)1c8|rnc5ndzIpd&}xy!5~(ngaZIbqYsMB^MyaKkE*MiK zGc;6-L~E-hQcYV`RCr>kiL}O+sH8NhSn7G_ypQkG`}ofL@cZ3!&%K}S`JH=y`2lA! zYRY=b001>#A1~}4GXFJ7ihG;U{ugl%AY?4Y6EqDQECZla@9X7(OD2A(iLt^)+4 zvcPRC>h5+5D!;ck!c5jns;t}KwT3zOd`v9$!i5Mj0YQQq3e%uXEax;Qs6BLeSS$1n z-0gw31HuTNL9>c_PK=0qIy=oRt$?pj1RYWpSoruGbF_X+V&HF>&kc0yE(ixHb)Rsl z?Z|7ewDUhGvU|FoV2)gQscotvX$_AFS(&zC3zpln3N_)QVEd=z zzZ=w{hu8zF4>*P89%p-@wh-3bYIJtTTeVpTv$e)tJ-bk2=f=<=5RCb~Rbkry`o^xf zbvNjc8hbGXn=&bM&Iwo~CG2nG!J0msF?HBU>fwzkZE&-7@sv2IJNrpnv%CQFz0d!Ldg9V~wik zuh5VvyHEEO*eGfLnR?KREg97{lhc}0)k78Ea^~i_y6L54&mof zV3CR3SylIX_*2{xzEBU3Dp3fvnV(L!J5a{ti5gMW!t)-+r=LxT<^jrPLBmLS zKYFsRybjfEPxDCf*RD$q_3mrQMPU$u;nf^}8J{aU5)3-NTXXR%V)>4bsf&JdhiB@RxQd_>PtabxK%w4O zurlUk4nHta;4XNJ+0g;C#5vuOfevn}?O<^?%S+jSlWRv?~pGlHX6qd{2%9Zu|< z0(8^-&yP&LqJE_iiq!9>;biU(wr$#MK6&zziyz0#i=)SrH!of*-xnuTKNdzaPvIy; z>ensl_pz$2j6Uq&wxqf`X8UiJN0=2TT2fpY-oY8pR9BLh1KCKJA9_`@uk(xn1A{Dy zP0OR?=8rBiO)0LqQtU-)us&+Q1~qFUN2pzeCf=@j9@a z|8^9%qLZl2=bpgJU(Kp;w=AW_8L2ISq|;zq;UjlRYWH+Y{>4SLXxjN`IwiBYgls8utH{dTL+#Kr<%-j6cnIR54mF`+ACH6*RQ24Go z8NQhgsrpt~#0vyeQ8JqDs0^$_OM(IQ^jpkFpbGnU`JVvK+%LdEM@zc4I@>xR3<4}; z4WGlTRsS37OeA}YZcH1Vg~tNc->26b-FR_Egx(#|&@6nW#vibpG8C6o38GyFyM?7H z373QJ3aA>D_Qm@5wQv$`V0OqYQJ_yHP$|6^rDJl7J~j5iref&*V)aIBxBTGIG8K3v z_mO5^b?==43eYCt%~h#z7mUEQ4F0`GBt{9Sye}%=FWi?C(C6=iJ1CTzb0Exj*l+Ck zt=)1R>Pn*~)lgdme^Ef&p)lkY9GIl&dW|ikv<|v$e&Lfr3fwf60t7D*$fJ3~?~>eQ zX@6TB65CM5LqP`Uh}vv+afAFg--a64z~`|DYE*v;UUHY7?$NN)HenEhIC(X_^%hw$ z8xM=0Rok`Lgf}CO#IYW4uPuWW<_N{~ahYs^AQDTC1DEr%)hd1(X8H9!sN`7i>XZ@? iFK- Date: Sat, 5 Nov 2022 00:33:00 +0000 Subject: [PATCH 003/228] Added PrimeTimeLato app --- apps/primetimelato/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json index ab30724ba..88beed6ac 100644 --- a/apps/primetimelato/metadata.json +++ b/apps/primetimelato/metadata.json @@ -1,4 +1,4 @@ -{ "id": "primetime", +{ "id": "primetimelato", "name": "Prime Time Lato Clock", "version": "0.01", "type": "clock", From 91f0564c0d1e0fa55f397b37bb4d577b734f75f1 Mon Sep 17 00:00:00 2001 From: hugh Date: Sat, 5 Nov 2022 00:45:13 +0000 Subject: [PATCH 004/228] Added PrimeTimeLato app --- apps/primetimelato/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/primetimelato/ChangeLog diff --git a/apps/primetimelato/ChangeLog b/apps/primetimelato/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/primetimelato/ChangeLog @@ -0,0 +1 @@ +0.01: first release From a79b130e887b7867960d8cee5c60eba7c9d00ea7 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Fri, 4 Nov 2022 18:34:15 +0100 Subject: [PATCH 005/228] powermanager - New option to automatically calibrate the battery --- apps/powermanager/ChangeLog | 2 ++ apps/powermanager/README.md | 3 ++- apps/powermanager/boot.js | 9 +++++++ apps/powermanager/lib.js | 6 +++++ apps/powermanager/metadata.json | 3 ++- apps/powermanager/settings.js | 47 +++++++++++---------------------- 6 files changed, 36 insertions(+), 34 deletions(-) create mode 100644 apps/powermanager/lib.js diff --git a/apps/powermanager/ChangeLog b/apps/powermanager/ChangeLog index f0b60a45a..a83e8c676 100644 --- a/apps/powermanager/ChangeLog +++ b/apps/powermanager/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Allow forcing monotonic battery voltage/percentage 0.03: Use default Bangle formatter for booleans +0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app + Allow automatic calibration on every charge longer than 3 hours diff --git a/apps/powermanager/README.md b/apps/powermanager/README.md index 434ec814e..88b3c370a 100644 --- a/apps/powermanager/README.md +++ b/apps/powermanager/README.md @@ -3,8 +3,9 @@ Manages settings for charging. Features: * Warning threshold to be able to disconnect the charger at a given percentage -* Set the battery calibration offset. +* Set the battery calibration offset * Force monotonic battery percentage or voltage +* Automatic calibration on charging uninterrupted longer than 3 hours (reloads of the watch reset the timer). ## Internals diff --git a/apps/powermanager/boot.js b/apps/powermanager/boot.js index 077e24413..3ddaad8e0 100644 --- a/apps/powermanager/boot.js +++ b/apps/powermanager/boot.js @@ -48,4 +48,13 @@ return v; }; } + + if (settings.autoCalibration){ + let chargeStart; + Bangle.on("charging", (charging)=>{ + if (charging) chargeStart = Date.now(); + if (chargeStart && !charging && (Date.now() - chargeStart > 1000*60*60*3)) require("powermanager").setCalibration(); + if (!charging) chargeStart = undefined; + }); + } })(); diff --git a/apps/powermanager/lib.js b/apps/powermanager/lib.js new file mode 100644 index 000000000..f4a7e3378 --- /dev/null +++ b/apps/powermanager/lib.js @@ -0,0 +1,6 @@ +// set battery calibration value by either applying the given value or setting the currently read battery voltage +exports.setCalibration = function(calibration){ + let s = require('Storage').readJSON("setting.json", true) || {}; + s.batFullVoltage = calibration?calibration:((analogRead(D3) + analogRead(D3) + analogRead(D3) + analogRead(D3)) / 4); + require('Storage').writeJSON("setting.json", s); +} diff --git a/apps/powermanager/metadata.json b/apps/powermanager/metadata.json index dd1727657..0777feee3 100644 --- a/apps/powermanager/metadata.json +++ b/apps/powermanager/metadata.json @@ -2,7 +2,7 @@ "id": "powermanager", "name": "Power Manager", "shortName": "Power Manager", - "version": "0.03", + "version": "0.04", "description": "Allow configuration of warnings and thresholds for battery charging and display.", "icon": "app.png", "type": "bootloader", @@ -12,6 +12,7 @@ "storage": [ {"name":"powermanager.boot.js","url":"boot.js"}, {"name":"powermanager.settings.js","url":"settings.js"}, + {"name":"powermanager","url":"lib.js"}, {"name":"powermanager.default.json","url":"default.json"} ] } diff --git a/apps/powermanager/settings.js b/apps/powermanager/settings.js index 7cc683024..9eeb29e00 100644 --- a/apps/powermanager/settings.js +++ b/apps/powermanager/settings.js @@ -23,7 +23,6 @@ '': { 'title': 'Power Manager' }, - '< Back': back, 'Monotonic percentage': { value: !!settings.forceMonoPercentage, onchange: v => { @@ -44,29 +43,29 @@ } }; - function roundToDigits(number, stepsize) { return Math.round(number / stepsize) * stepsize; } - function getCurrentVoltageDirect() { - return (analogRead(D3) + analogRead(D3) + analogRead(D3) + analogRead(D3)) / 4; - } - var stepsize = 0.0002; - var full = 0.32; + var full = 0.3144; function getInitialCalibrationOffset() { return roundToDigits(systemsettings.batFullVoltage - full, stepsize) || 0; } - var submenu_calibrate = { '': { - title: "Calibrate" + title: "Calibrate", + back: function() { + E.showMenu(mainmenu); + }, }, - '< Back': function() { - E.showMenu(mainmenu); + 'Autodetect': { + value: !!settings.autoCalibration, + onchange: v => { + writeSettings("autoCalibration", v); + } }, 'Offset': { min: -0.05, @@ -75,25 +74,9 @@ value: getInitialCalibrationOffset(), format: v => roundToDigits(v, stepsize).toFixed((""+stepsize).length - 2), onchange: v => { - print(typeof v); - systemsettings.batFullVoltage = v + full; - require("Storage").writeJSON("setting.json", systemsettings); + require("powermanager").setCalibration(v + full); } }, - 'Auto': function() { - var newVoltage = getCurrentVoltageDirect(); - E.showAlert("Please charge fully before auto setting").then(() => { - E.showPrompt("Set current charge as full").then((r) => { - if (r) { - systemsettings.batFullVoltage = newVoltage; - require("Storage").writeJSON("setting.json", systemsettings); - //reset value shown in menu to the newly set one - submenu_calibrate.Offset.value = getInitialCalibrationOffset(); - E.showMenu(mainmenu); - } - }); - }); - }, 'Clear': function() { E.showPrompt("Clear charging offset?").then((r) => { if (r) { @@ -109,10 +92,10 @@ var submenu_chargewarn = { '': { - title: "Charge warning" - }, - '< Back': function() { - E.showMenu(mainmenu); + title: "Charge warning", + back: function() { + E.showMenu(mainmenu); + }, }, 'Enabled': { value: !!settings.warnEnabled, From bf135ba54ce448a9a9d0f2bfb10e6e4205c559d8 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 5 Nov 2022 10:06:04 +0100 Subject: [PATCH 006/228] powermanager - Remove print --- apps/powermanager/boot.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/powermanager/boot.js b/apps/powermanager/boot.js index 3ddaad8e0..2bc2aaa35 100644 --- a/apps/powermanager/boot.js +++ b/apps/powermanager/boot.js @@ -5,7 +5,6 @@ ); if (settings.warnEnabled){ - print("Charge warning enabled"); var chargingInterval; function handleCharging(charging){ From 6687986dab82d30b184a8f1f72d27fcbe0296d87 Mon Sep 17 00:00:00 2001 From: hugh Date: Sat, 5 Nov 2022 15:07:04 +0000 Subject: [PATCH 007/228] added option to buzz on prime, with settings --- apps/primetimelato/app.js | 31 ++++++++++++++++++++++++++--- apps/primetimelato/metadata.json | 7 +++++-- apps/primetimelato/settings.js | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 apps/primetimelato/settings.js diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js index 2031fd53b..e03e8fa51 100644 --- a/apps/primetimelato/app.js +++ b/apps/primetimelato/app.js @@ -1,5 +1,7 @@ const h = g.getHeight(); const w = g.getWidth(); +const SETTINGS_FILE = "pastel.json"; +let settings; Graphics.prototype.setFontLato = function(scale) { // Actual height 41 (43 - 3) @@ -22,6 +24,11 @@ Graphics.prototype.setFontLatoSmall = function(scale) { ); return this; }; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.buzz_on_prime = (settings.buzz_on_prime === undefined ? false : settings.buzz_on_prime); +} // creates a list of prime factors of n and outputs them as a string, if n is prime outputs "Prime Time!" function primeFactors(n) { @@ -55,24 +62,41 @@ function timeToInt(t) { function draw() { var date = new Date(); var timeStr = require("locale").time(date,1); - var primeStr = primeFactors(timeToInt(timeStr)); + var intTime = timeToInt(timeStr); + var primeStr = primeFactors(intTime); g.reset(); g.setColor(0,0,0); g.fillRect(Bangle.appRect); - //g.setFont("6x8", w/30); g.setFontLato(); g.setFontAlign(0, 0); g.setColor(100,100,100); g.drawString(timeStr, w/2, h/2); - //g.setFont("6x8", w/60); g.setFontLatoSmall(); g.drawString(primeStr, w/2, 3*h/4); + // Buzz if Prime Time and between 8am and 8pm + if (settings.buzz_on_prime && primeStr == "Prime Time!" && intTime >= 800 && intTime <= 2000) + buzzer(2); queueDraw(); } +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + // timeout used to update every minute var drawTimeout; @@ -96,6 +120,7 @@ Bangle.on('lcdPower',on=>{ }); +loadSettings(); g.clear(); // Show launcher when middle button pressed Bangle.setUI("clock"); diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json index 88beed6ac..eaecf94f5 100644 --- a/apps/primetimelato/metadata.json +++ b/apps/primetimelato/metadata.json @@ -10,6 +10,9 @@ "readme": "README.md", "storage": [ {"name":"primetimelato.app.js","url":"app.js"}, - {"name":"primetimelato.img","url":"icon.js","evaluate":true} - ] + {"name":"primetimelato.img","url":"icon.js","evaluate":true}, + {"name":"primetimelato.settings.js","url":"settings.js"} + + ], + "data": [{"name":"primetimelato.json"}] } diff --git a/apps/primetimelato/settings.js b/apps/primetimelato/settings.js new file mode 100644 index 000000000..19702ed84 --- /dev/null +++ b/apps/primetimelato/settings.js @@ -0,0 +1,34 @@ +(function(back) { + const SETTINGS_FILE = "primetimelato.json"; + + // initialize with default settings... + let s = { + 'buzz_on_prime': true, + } + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = storage.readJSON(SETTINGS_FILE, 1) || {} + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + E.showMenu({ + '': { 'title': 'Prime Time Lato' }, + '< Back': back, + 'Buzz on Prime': { + value: !!s.buzz_on_prime, + onchange: v => { + s.buzz_on_prime = v; + save(); + }, + } + }) +}) From dbc325748d2ae0d8b78ef742b9d40fda68101e41 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sat, 5 Nov 2022 15:10:04 +0000 Subject: [PATCH 008/228] added option to buzz on prime, with settings --- apps/primetimelato/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/primetimelato/ChangeLog b/apps/primetimelato/ChangeLog index 9db0e26c5..46690e360 100644 --- a/apps/primetimelato/ChangeLog +++ b/apps/primetimelato/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: added option to buzz on prime, with settings From 409cba80b9ec036c070a513fa4b343395a6a2a4a Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sat, 5 Nov 2022 15:20:31 +0000 Subject: [PATCH 009/228] added option to buzz on prime, with settings --- apps/primetimelato/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json index eaecf94f5..6b292c380 100644 --- a/apps/primetimelato/metadata.json +++ b/apps/primetimelato/metadata.json @@ -1,6 +1,6 @@ { "id": "primetimelato", "name": "Prime Time Lato Clock", - "version": "0.01", + "version": "0.02", "type": "clock", "description": "A clock that tells you the primes of the time in the Lato font", "icon": "app.png", From 453895c134ed70b346f944dbe0dd05f32e0f4624 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sat, 5 Nov 2022 15:52:38 +0000 Subject: [PATCH 010/228] added option to buzz on prime, with settings --- apps/primetimelato/README.md | 6 ++++++ apps/primetimelato/app.js | 1 + 2 files changed, 7 insertions(+) diff --git a/apps/primetimelato/README.md b/apps/primetimelato/README.md index 6760fe41a..924a6fae6 100644 --- a/apps/primetimelato/README.md +++ b/apps/primetimelato/README.md @@ -4,6 +4,12 @@ A watchface that displays time and its prime factors in the Lato font. For example when the time is 21:05, the prime factors are 5,421. Displays 'Prime Time!' when the time is a prime number. +There is a settings option added in the Settings App. If 'Buzz on +Prime' is ticked then the buzzer will sound when 'Prime Time!' is +detected. Note the buzzer is limited to between 8am and 8pm so it +should not go off when you want to sleep. + + ![](screenshot.jpg) Written by: [Hugh Barney](https://github.com/hughbarney) diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js index e03e8fa51..5cbc68cfe 100644 --- a/apps/primetimelato/app.js +++ b/apps/primetimelato/app.js @@ -76,6 +76,7 @@ function draw() { g.setFontLatoSmall(); g.drawString(primeStr, w/2, 3*h/4); + // Buzz if Prime Time and between 8am and 8pm if (settings.buzz_on_prime && primeStr == "Prime Time!" && intTime >= 800 && intTime <= 2000) buzzer(2); From c54dde90ea899bf6bae97f927da0d5dd13e0f081 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Sat, 5 Nov 2022 17:52:29 +0000 Subject: [PATCH 011/228] added option to buzz on prime, with settings --- apps/primetimelato/app.js | 2 +- apps/primetimelato/settings.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js index 5cbc68cfe..817da7cda 100644 --- a/apps/primetimelato/app.js +++ b/apps/primetimelato/app.js @@ -1,6 +1,6 @@ const h = g.getHeight(); const w = g.getWidth(); -const SETTINGS_FILE = "pastel.json"; +const SETTINGS_FILE = "primetimelato.json"; let settings; Graphics.prototype.setFontLato = function(scale) { diff --git a/apps/primetimelato/settings.js b/apps/primetimelato/settings.js index 19702ed84..5550055eb 100644 --- a/apps/primetimelato/settings.js +++ b/apps/primetimelato/settings.js @@ -3,7 +3,7 @@ // initialize with default settings... let s = { - 'buzz_on_prime': true, + 'buzz_on_prime': true } // ...and overwrite them with any saved values @@ -16,8 +16,8 @@ } function save() { - settings = s - storage.write(SETTINGS_FILE, settings) + settings = s; + storage.write(SETTINGS_FILE, settings); } E.showMenu({ From 80ed8c919260641b4bbc10677aa587859c37a090 Mon Sep 17 00:00:00 2001 From: Marco H Date: Sat, 5 Nov 2022 20:39:51 +0100 Subject: [PATCH 012/228] Write AGPS data chunks with delay to improve reliability --- apps/agpsdata/ChangeLog | 1 + apps/agpsdata/app.js | 2 +- apps/agpsdata/lib.js | 104 +++++++++++++++++++++--------------- apps/agpsdata/metadata.json | 2 +- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog index 89844a132..8ada244d7 100644 --- a/apps/agpsdata/ChangeLog +++ b/apps/agpsdata/ChangeLog @@ -2,3 +2,4 @@ 0.02: Load AGPS data on app start and automatically in background 0.03: Do not load AGPS data on boot Increase minimum interval to 6 hours +0.04: Write AGPS data chunks with delay to improve reliability diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js index 647723bb4..4a6d2ba5c 100644 --- a/apps/agpsdata/app.js +++ b/apps/agpsdata/app.js @@ -36,7 +36,7 @@ function updateAgps() { g.clear(); if (!waiting) { waiting = true; - display("Updating A-GPS..."); + display("Updating A-GPS...", "takes ~ 10 seconds"); require("agpsdata").pull(function() { waiting = false; display("A-GPS updated.", "touch to close"); diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js index 7d9758c0a..34608a5c6 100644 --- a/apps/agpsdata/lib.js +++ b/apps/agpsdata/lib.js @@ -8,41 +8,52 @@ var FILE = "agpsdata.settings.json"; var settings; readSettings(); -function setAGPS(data) { - var js = jsFromBase64(data); - try { - eval(js); - return true; - } - catch(e) { - console.log("error:", e); - } - return false; +function setAGPS(b64) { + return new Promise(function(resolve, reject) { + var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on + const gnsstype = settings.gnsstype || 1; // default GPS + initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) + + eval(initCommands); + + try { + writeChunks(atob(b64), resolve); + } catch (e) { + console.log("error:", e); + reject(); + } + }); } -function jsFromBase64(b64) { - var bin = atob(b64); - var chunkSize = 128; - var js = "Bangle.setGPSPower(1);\n"; // turn GPS on - var gnsstype = settings.gnsstype || 1; // default GPS - js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode - // What about: - // NAV-TIMEUTC (0x01 0x10) - // NAV-PV (0x01 0x03) - // or AGPS.zip uses AID-INI (0x0B 0x01) +var chunkI = 0; +function writeChunks(bin, resolve) { + return new Promise(function(resolve2) { + const chunkSize = 128; + setTimeout(function() { + if (chunkI < bin.length) { + var chunk = bin.substr(chunkI, chunkSize); + js = `Serial1.write(atob("${btoa(chunk)}"))\n`; + eval(js); - for (var i=0;i { - let result = setAGPS(event.resp); - if (result) { - updateLastUpdate(); - if (successCallback) successCallback(); - } else { - console.log("error applying AGPS data"); - if (failureCallback) failureCallback("Error applying AGPS data"); - } - }).catch((e)=>{ - console.log("error", e); - if (failureCallback) failureCallback(e); - }); + const uri = "https://www.espruino.com/agps/casic.base64"; + if (Bangle.http) { + Bangle.http(uri, {timeout : 10000}) + .then(event => { + setAGPS(event.resp) + .then(r => { + updateLastUpdate(); + if (successCallback) + successCallback(); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); } else { console.log("error: No http method found"); - if (failureCallback) failureCallback(/*LANG*/"No http method"); + if (failureCallback) + failureCallback(/*LANG*/ "No http method"); } }; diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json index 1ce299532..203a00f72 100644 --- a/apps/agpsdata/metadata.json +++ b/apps/agpsdata/metadata.json @@ -2,7 +2,7 @@ "name": "A-GPS Data Downloader App", "shortName":"A-GPS Data", "icon": "agpsdata.png", - "version":"0.03", + "version":"0.04", "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", "tags": "boot,tool,assisted,gps,agps,http", "allow_emulator":true, From b4b3775b23a90003011bbdcdf71e0572599bda0d Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 6 Nov 2022 00:48:38 +0100 Subject: [PATCH 013/228] exit by pressing upper left corner --- apps/torch/ChangeLog | 1 + apps/torch/README.md | 2 ++ apps/torch/app.js | 6 ++++++ apps/torch/metadata.json | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 apps/torch/README.md diff --git a/apps/torch/ChangeLog b/apps/torch/ChangeLog index fd904b6e8..22b1c179a 100644 --- a/apps/torch/ChangeLog +++ b/apps/torch/ChangeLog @@ -3,3 +3,4 @@ 0.03: Add Color Changing Settings 0.04: Add Support For Bangle.js 2 0.05: Default full Brightness +0.06: Press upper left corner to exit on Bangle.js 2 diff --git a/apps/torch/README.md b/apps/torch/README.md new file mode 100644 index 000000000..e06861071 --- /dev/null +++ b/apps/torch/README.md @@ -0,0 +1,2 @@ +On Bangle.js 2, pressing the upper left corner where the red back button would be exits the app if the screen is unlocked. + diff --git a/apps/torch/app.js b/apps/torch/app.js index 2af35fdb6..78eda214b 100644 --- a/apps/torch/app.js +++ b/apps/torch/app.js @@ -13,8 +13,14 @@ Bangle.setLCDTimeout(0); g.reset(); g.setColor(settings.bg); g.fillRect(0,0,g.getWidth(),g.getHeight()); + // Any button turns off setWatch(()=>load(), BTN1); if (global.BTN2) setWatch(()=>load(), BTN2); if (global.BTN3) setWatch(()=>load(), BTN3); +// Press upper left corner to exit (where red back button would be) +Bangle.setUI({ + mode : 'custom', + back : load +}); diff --git a/apps/torch/metadata.json b/apps/torch/metadata.json index af85370ac..580ae4e8a 100644 --- a/apps/torch/metadata.json +++ b/apps/torch/metadata.json @@ -2,7 +2,7 @@ "id": "torch", "name": "Torch", "shortName": "Torch", - "version": "0.05", + "version": "0.06", "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets on Bangle.js 1. You can also set the color through the app's setting menu.", "icon": "app.png", "tags": "tool,torch", From ad88625b8c4e54b36e2045b148a4e7084d769fda Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 30 Oct 2022 10:54:23 +0100 Subject: [PATCH 014/228] imageclock - Use widget_utils module --- apps/imageclock/app.js | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index ff3f5a62d..a0f1e6a74 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -604,12 +604,8 @@ let firstDraw = true; promise.then(()=>{ let currentDrawingTime = Date.now(); - if (showWidgets && global.WIDGETS){ - //print("Draw widgets"); + if (showWidgets){ restoreWidgetDraw(); - Bangle.drawWidgets(); - g.setColor(g.theme.fg); - g.drawLine(0,24,g.getWidth(),24); } lastDrawTime = Date.now() - start; isDrawing=false; @@ -754,13 +750,8 @@ let firstDraw = true; let currentDragDistance = 0; let restoreWidgetDraw = function(){ - if (global.WIDGETS) { - for (let w in global.WIDGETS) { - let wd = global.WIDGETS[w]; - wd.draw = originalWidgetDraw[w]; - wd.area = originalWidgetArea[w]; - } - } + require("widget_utils").show(); + Bangle.drawWidgets(); }; let handleDrag = function(e){ @@ -814,17 +805,7 @@ let firstDraw = true; let clearWidgetsDraw = function(){ //print("Clear widget draw calls"); - if (global.WIDGETS) { - originalWidgetDraw = {}; - originalWidgetArea = {}; - for (let w in global.WIDGETS) { - let wd = global.WIDGETS[w]; - originalWidgetDraw[w] = wd.draw; - originalWidgetArea[w] = wd.area; - wd.draw = () => {}; - wd.area = ""; - } - } + require("widget_utils").hide(); } handleLock(Bangle.isLocked(), true); From 866155bbd053088f1f5bfd70c70945d9f4e6798a Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 30 Oct 2022 21:56:24 +0100 Subject: [PATCH 015/228] imageclock - Directly set cached resource --- apps/imageclock/app.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index a0f1e6a74..0f34457cf 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -292,14 +292,7 @@ let firstDraw = true; if (resource){ prepareImg(resource); //print("lastElem", typeof resource) - if (resource) { - element.cachedImage[cacheKey] = resource; - //print("cache res ",typeof element.cachedImage[cacheKey]); - } else { - element.cachedImage[cacheKey] = null; - //print("cache null",typeof element.cachedImage[cacheKey]); - //print("Could not create image from", resource); - } + element.cachedImage[cacheKey] = resource; } else { //print("Could not get resource from", element, lastElem); } From 94df58412b207f3da3a540a7ffb302c927aa3cc9 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 30 Oct 2022 22:12:38 +0100 Subject: [PATCH 016/228] imageclock - Cache searches for numerical images --- apps/imageclock/app.js | 44 +++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index 0f34457cf..7cbd9f008 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -202,27 +202,39 @@ let firstDraw = true; let firstDigitY = element.Y; let imageIndex = element.ImageIndex ? element.ImageIndex : 0; - let firstImage; - if (imageIndex){ - firstImage = getByPath(resources, [], "" + (0 + imageIndex)); - } else { - firstImage = getByPath(resources, element.ImagePath, 0); + let firstImage = element.cachedFirstImage; + if (!firstImage && !element.cachedFirstImageMissing){ + if (imageIndex){ + firstImage = getByPath(resources, [], "" + (0 + imageIndex)); + } else { + firstImage = getByPath(resources, element.ImagePath, 0); + } + element.cachedFirstImage = firstImage; + if (!firstImage) element.cachedFirstImageMissing = true; } - let minusImage; - if (imageIndexMinus){ - minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus)); - } else { - minusImage = getByPath(resources, element.ImagePath, "minus"); + let minusImage = element.cachedMinusImage; + if (!minusImage && !element.cachedMinusImageMissing){ + if (imageIndexMinus){ + minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus)); + } else { + minusImage = getByPath(resources, element.ImagePath, "minus"); + } + element.cachedMinusImage = minusImage; + if (!minusImage) element.cachedMinusImageMissing = true; } - let unitImage; + let unitImage = element.cachedUnitImage; //print("Get image for unit", imageIndexUnit); - if (imageIndexUnit !== undefined){ - unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit)); - //print("Unit image is", unitImage); - } else if (element.Unit){ - unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown")); + if (!unitImage && !element.cachedUnitImageMissing){ + if (imageIndexUnit !== undefined){ + unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit)); + //print("Unit image is", unitImage); + } else if (element.Unit){ + unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown")); + } + unitImage = element.cachedUnitImage; + if (!unitImage) element.cachedUnitImageMissing = true; } let numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing); From 0d0acfe0754ccd100d7e7627414ad88329c37175 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 30 Oct 2022 22:21:40 +0100 Subject: [PATCH 017/228] imageclock - Change from drag to swipe event for widgets --- apps/imageclock/app.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index 7cbd9f008..7dd619e28 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -752,25 +752,19 @@ let firstDraw = true; let showWidgetsChanged = false; - let currentDragDistance = 0; let restoreWidgetDraw = function(){ require("widget_utils").show(); Bangle.drawWidgets(); }; - - let handleDrag = function(e){ - //print("handleDrag"); - currentDragDistance += e.dy; - if (Math.abs(currentDragDistance) < 10) return; - dragDown = currentDragDistance > 0; - currentDragDistance = 0; - if (!showWidgets && dragDown){ + + let handleSwipe = function(lr, ud){ + if (!showWidgets && ud == 1){ //print("Enable widgets"); restoreWidgetDraw(); showWidgetsChanged = true; } - if (showWidgets && !dragDown){ + if (showWidgets && ud == -1){ //print("Disable widgets"); clearWidgetsDraw(); firstDraw = true; @@ -779,12 +773,12 @@ let firstDraw = true; if (showWidgetsChanged){ showWidgetsChanged = false; //print("Draw after widget change"); - showWidgets = dragDown; + showWidgets = ud == 1; initialDraw(); } }; - Bangle.on('drag', handleDrag); + Bangle.on('swipe', handleSwipe); if (!events || events.includes("pressure")){ Bangle.on('pressure', handlePressure); @@ -823,7 +817,7 @@ let firstDraw = true; Bangle.setHRMPower(0, "imageclock"); Bangle.setBarometerPower(0, 'imageclock'); - Bangle.removeListener('drag', handleDrag); + Bangle.removeListener('swipe', handleSwipe); Bangle.removeListener('lock', handleLock); Bangle.removeListener('charging', handleCharging); Bangle.removeListener('HRM', handleHrm); From 218cfd9c08318a799a8922e1d1d3838eaefa4b0d Mon Sep 17 00:00:00 2001 From: thyttan <97237430+thyttan@users.noreply.github.com> Date: Sun, 6 Nov 2022 01:01:33 +0100 Subject: [PATCH 018/228] Update app.js --- apps/torch/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/torch/app.js b/apps/torch/app.js index 78eda214b..418463144 100644 --- a/apps/torch/app.js +++ b/apps/torch/app.js @@ -13,13 +13,11 @@ Bangle.setLCDTimeout(0); g.reset(); g.setColor(settings.bg); g.fillRect(0,0,g.getWidth(),g.getHeight()); - // Any button turns off setWatch(()=>load(), BTN1); if (global.BTN2) setWatch(()=>load(), BTN2); if (global.BTN3) setWatch(()=>load(), BTN3); - -// Press upper left corner to exit (where red back button would be) +// Pressing upper left corner turns off (where red back button would be) Bangle.setUI({ mode : 'custom', back : load From 97ce6bab9ead99a2f145dbb68b824a5d0b9177f1 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 01:09:07 +0100 Subject: [PATCH 019/228] imageclock - Fix color setting in promises --- apps/imageclock/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/imageclock/custom.html b/apps/imageclock/custom.html index e595b51ca..8bf7a7bd0 100644 --- a/apps/imageclock/custom.html +++ b/apps/imageclock/custom.html @@ -714,13 +714,13 @@ } if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n"; - code += "" + colorsetting; code += (condition.length > 0 ? "if (" + condition + "){\n" : ""); if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){ code += "p = p.then(()=>delay(0)).then(()=>{\n"; } else { code += "p = p.then(()=>{\n"; } + code += "" + colorsetting; if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n"; code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n"; From a06f8d28a6822d50c6fc157779cd2426df05dec3 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 01:45:53 +0100 Subject: [PATCH 020/228] widbars - Set battery bar color on load --- apps/widbars/ChangeLog | 1 + apps/widbars/metadata.json | 2 +- apps/widbars/widget.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/widbars/ChangeLog b/apps/widbars/ChangeLog index 61e28e6e4..0065c1b27 100644 --- a/apps/widbars/ChangeLog +++ b/apps/widbars/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Widget! 0.02: Battery bar turns yellow on charge Memory status bar does not trigger garbage collect +0.03: Set battery color on load diff --git a/apps/widbars/metadata.json b/apps/widbars/metadata.json index a9981305c..06965e77e 100644 --- a/apps/widbars/metadata.json +++ b/apps/widbars/metadata.json @@ -1,7 +1,7 @@ { "id": "widbars", "name": "Bars Widget", - "version": "0.02", + "version": "0.03", "description": "Display several measurements as vertical bars.", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/widbars/widget.js b/apps/widbars/widget.js index cceeb0897..0a4bba76c 100644 --- a/apps/widbars/widget.js +++ b/apps/widbars/widget.js @@ -42,7 +42,7 @@ if (top) g .clearRect(x,y, x+w-1,y+top-1); // erase above bar if (f) g.setColor(col).fillRect(x,y+top, x+w-1,y+h-1); // even for f=0.001 this is still 1 pixel high } - let batColor='#0f0'; + let batColor=Bangle.isCharging()?'#ff0':'#0f0'; function draw() { g.reset(); const x = this.x, y = this.y, From 2216f3fb19dfd68c824d3b306722a6a2e564c3db Mon Sep 17 00:00:00 2001 From: Bruce Blore Date: Sat, 5 Nov 2022 21:05:47 -0700 Subject: [PATCH 021/228] Renamed bgtimer to keytimer --- apps/bgtimer/boot.js | 11 ----------- apps/infoclk/app.js | 2 +- apps/infoclk/settings.js | 2 +- apps/{bgtimer => keytimer}/app.js | 8 ++++---- apps/keytimer/boot.js | 11 +++++++++++ apps/{bgtimer => keytimer}/common.js | 2 +- apps/{bgtimer => keytimer}/icon.js | 0 apps/{bgtimer => keytimer}/icon.png | Bin apps/{bgtimer => keytimer}/img/pause.png | Bin apps/{bgtimer => keytimer}/img/play.png | Bin apps/{bgtimer => keytimer}/img/reset.png | Bin apps/{bgtimer => keytimer}/keypad.js | 2 +- apps/{bgtimer => keytimer}/metadata.json | 18 +++++++++--------- apps/{bgtimer => keytimer}/ring.js | 2 +- apps/{bgtimer => keytimer}/timerview.js | 4 ++-- 15 files changed, 31 insertions(+), 31 deletions(-) delete mode 100644 apps/bgtimer/boot.js rename apps/{bgtimer => keytimer}/app.js (79%) create mode 100644 apps/keytimer/boot.js rename apps/{bgtimer => keytimer}/common.js (98%) rename apps/{bgtimer => keytimer}/icon.js (100%) rename apps/{bgtimer => keytimer}/icon.png (100%) rename apps/{bgtimer => keytimer}/img/pause.png (100%) rename apps/{bgtimer => keytimer}/img/play.png (100%) rename apps/{bgtimer => keytimer}/img/reset.png (100%) rename apps/{bgtimer => keytimer}/keypad.js (98%) rename apps/{bgtimer => keytimer}/metadata.json (64%) rename apps/{bgtimer => keytimer}/ring.js (88%) rename apps/{bgtimer => keytimer}/timerview.js (96%) diff --git a/apps/bgtimer/boot.js b/apps/bgtimer/boot.js deleted file mode 100644 index 67840b3ce..000000000 --- a/apps/bgtimer/boot.js +++ /dev/null @@ -1,11 +0,0 @@ -const BGTIMER_common = require("bgtimer-com.js"); - -//Only start the timeout if the timer is running -if (BGTIMER_common.state.running) { - setTimeout(() => { - //Check now to avoid race condition - if (Bangle.BGTIMER_ACTIVE === undefined) { - load('bgtimer-ring.js'); - } - }, BGTIMER_common.getTimeLeft()); -} \ No newline at end of file diff --git a/apps/infoclk/app.js b/apps/infoclk/app.js index 6bc626018..3d51191df 100644 --- a/apps/infoclk/app.js +++ b/apps/infoclk/app.js @@ -40,7 +40,7 @@ let config = Object.assign({ // false = no shortcut // '#LAUNCHER' = open the launcher // any other string = name of app to open - 'stlap', 'bgtimer', 'pomoplus', 'alarm', + 'stlap', 'keytimer', 'pomoplus', 'alarm', 'rpnsci', 'calendar', 'torch', 'weather' ], diff --git a/apps/infoclk/settings.js b/apps/infoclk/settings.js index d12225f99..0bc3d4b15 100644 --- a/apps/infoclk/settings.js +++ b/apps/infoclk/settings.js @@ -37,7 +37,7 @@ // false = no shortcut // '#LAUNCHER' = open the launcher // any other string = name of app to open - 'stlap', 'bgtimer', 'pomoplus', 'alarm', + 'stlap', 'keytimer', 'pomoplus', 'alarm', 'rpnsci', 'calendar', 'torch', 'weather' ], diff --git a/apps/bgtimer/app.js b/apps/keytimer/app.js similarity index 79% rename from apps/bgtimer/app.js rename to apps/keytimer/app.js index 66f22a7a2..7d235f9a8 100644 --- a/apps/bgtimer/app.js +++ b/apps/keytimer/app.js @@ -1,9 +1,9 @@ -Bangle.BGTIMER_ACTIVE = true; -const common = require("bgtimer-com.js"); +Bangle.keytimer_ACTIVE = true; +const common = require("keytimer-com.js"); const storage = require("Storage"); -const keypad = require("bgtimer-keys.js"); -const timerView = require("bgtimer-tview.js"); +const keypad = require("keytimer-keys.js"); +const timerView = require("keytimer-tview.js"); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/keytimer/boot.js b/apps/keytimer/boot.js new file mode 100644 index 000000000..f202bcbdf --- /dev/null +++ b/apps/keytimer/boot.js @@ -0,0 +1,11 @@ +const keytimer_common = require("keytimer-com.js"); + +//Only start the timeout if the timer is running +if (keytimer_common.state.running) { + setTimeout(() => { + //Check now to avoid race condition + if (Bangle.keytimer_ACTIVE === undefined) { + load('keytimer-ring.js'); + } + }, keytimer_common.getTimeLeft()); +} \ No newline at end of file diff --git a/apps/bgtimer/common.js b/apps/keytimer/common.js similarity index 98% rename from apps/bgtimer/common.js rename to apps/keytimer/common.js index 67a6660d3..8c702de66 100644 --- a/apps/bgtimer/common.js +++ b/apps/keytimer/common.js @@ -1,7 +1,7 @@ const storage = require("Storage"); const heatshrink = require("heatshrink"); -exports.STATE_PATH = "bgtimer.state.json"; +exports.STATE_PATH = "keytimer.state.json"; exports.BUTTON_ICONS = { play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), diff --git a/apps/bgtimer/icon.js b/apps/keytimer/icon.js similarity index 100% rename from apps/bgtimer/icon.js rename to apps/keytimer/icon.js diff --git a/apps/bgtimer/icon.png b/apps/keytimer/icon.png similarity index 100% rename from apps/bgtimer/icon.png rename to apps/keytimer/icon.png diff --git a/apps/bgtimer/img/pause.png b/apps/keytimer/img/pause.png similarity index 100% rename from apps/bgtimer/img/pause.png rename to apps/keytimer/img/pause.png diff --git a/apps/bgtimer/img/play.png b/apps/keytimer/img/play.png similarity index 100% rename from apps/bgtimer/img/play.png rename to apps/keytimer/img/play.png diff --git a/apps/bgtimer/img/reset.png b/apps/keytimer/img/reset.png similarity index 100% rename from apps/bgtimer/img/reset.png rename to apps/keytimer/img/reset.png diff --git a/apps/bgtimer/keypad.js b/apps/keytimer/keypad.js similarity index 98% rename from apps/bgtimer/keypad.js rename to apps/keytimer/keypad.js index d08df1e3f..a5edeb2f2 100644 --- a/apps/bgtimer/keypad.js +++ b/apps/keytimer/keypad.js @@ -58,7 +58,7 @@ let StartButton = { common.state.wasRunning = true; common.state.running = true; feedback(true); - require('bgtimer-tview.js').show(common); + require('keytimer-tview.js').show(common); } }; diff --git a/apps/bgtimer/metadata.json b/apps/keytimer/metadata.json similarity index 64% rename from apps/bgtimer/metadata.json rename to apps/keytimer/metadata.json index 3f63d0ec5..a982594f1 100644 --- a/apps/bgtimer/metadata.json +++ b/apps/keytimer/metadata.json @@ -1,6 +1,6 @@ { - "id": "bgtimer", - "name": "Timer", + "id": "keytimer", + "name": "Keypad Timer", "version": "0.02", "description": "A timer with a keypad that runs in the background", "icon": "icon.png", @@ -12,32 +12,32 @@ "allow_emulator": true, "storage": [ { - "name": "bgtimer.app.js", + "name": "keytimer.app.js", "url": "app.js" }, { - "name": "bgtimer.img", + "name": "keytimer.img", "url": "icon.js", "evaluate": true }, { - "name": "bgtimer.boot.js", + "name": "keytimer.boot.js", "url": "boot.js" }, { - "name": "bgtimer-com.js", + "name": "keytimer-com.js", "url": "common.js" }, { - "name": "bgtimer-ring.js", + "name": "keytimer-ring.js", "url": "ring.js" }, { - "name": "bgtimer-keys.js", + "name": "keytimer-keys.js", "url": "keypad.js" }, { - "name": "bgtimer-tview.js", + "name": "keytimer-tview.js", "url": "timerview.js" } ] diff --git a/apps/bgtimer/ring.js b/apps/keytimer/ring.js similarity index 88% rename from apps/bgtimer/ring.js rename to apps/keytimer/ring.js index 9df5cb4bd..c42c11394 100644 --- a/apps/bgtimer/ring.js +++ b/apps/keytimer/ring.js @@ -1,4 +1,4 @@ -const common = require('bgtimer-com.js'); +const common = require('keytimer-com.js'); Bangle.loadWidgets() Bangle.drawWidgets() diff --git a/apps/bgtimer/timerview.js b/apps/keytimer/timerview.js similarity index 96% rename from apps/bgtimer/timerview.js rename to apps/keytimer/timerview.js index 4c9f7acd3..48c896ba0 100644 --- a/apps/bgtimer/timerview.js +++ b/apps/keytimer/timerview.js @@ -39,7 +39,7 @@ function drawTimer() { else return `${parseInt(minutes)}:${pad(seconds)}`; })(), g.getWidth() / 2, g.getHeight() / 2) - if (timeLeft <= 0) load('bgtimer-ring.js'); + if (timeLeft <= 0) load('keytimer-ring.js'); } let timerInterval; @@ -81,7 +81,7 @@ exports.touch = (button, xy) => { common.state.setTime = setTime; common.state.inputString = inputString; clearTimerInterval(); - require('bgtimer-keys.js').show(common); + require('keytimer-keys.js').show(common); } else { if (common.state.running) { //Record the exact moment that we paused From 90a259ce65344d6260819a284a320f65ebb3a4a4 Mon Sep 17 00:00:00 2001 From: Bruce Blore Date: Sat, 5 Nov 2022 22:25:28 -0700 Subject: [PATCH 022/228] Replaced stolen icons --- apps/gallery/ChangeLog | 2 ++ apps/infoclk/ChangeLog | 3 +++ apps/infoclk/icon.js | 3 +-- apps/infoclk/icon.png | Bin 1989 -> 249 bytes apps/keytimer/ChangeLog | 2 ++ apps/pomoplus/ChangeLog | 3 +++ apps/pomoplus/icon.js | 2 +- apps/pomoplus/icon.png | Bin 2122 -> 1269 bytes apps/pomoplus/metadata.json | 2 +- apps/random/ChangeLog | 2 ++ apps/rpnsci/ChangeLog | 3 +++ apps/stlap/ChangeLog | 3 +++ apps/stlap/icon.js | 2 +- apps/stlap/icon.png | Bin 1566 -> 432 bytes apps/stlapview/ChangeLog | 2 ++ apps/stlapview/icon.js | 2 +- apps/stlapview/icon.png | Bin 1566 -> 564 bytes 17 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 apps/gallery/ChangeLog create mode 100644 apps/infoclk/ChangeLog create mode 100644 apps/keytimer/ChangeLog create mode 100644 apps/pomoplus/ChangeLog create mode 100644 apps/random/ChangeLog create mode 100644 apps/rpnsci/ChangeLog create mode 100644 apps/stlap/ChangeLog create mode 100644 apps/stlapview/ChangeLog diff --git a/apps/gallery/ChangeLog b/apps/gallery/ChangeLog new file mode 100644 index 000000000..76db22053 --- /dev/null +++ b/apps/gallery/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Submitted to app loader \ No newline at end of file diff --git a/apps/infoclk/ChangeLog b/apps/infoclk/ChangeLog new file mode 100644 index 000000000..4744f872a --- /dev/null +++ b/apps/infoclk/ChangeLog @@ -0,0 +1,3 @@ +0.01: New app! +0.02-0.07: Bug fixes +0.08: Submitted to the app loader \ No newline at end of file diff --git a/apps/infoclk/icon.js b/apps/infoclk/icon.js index 2db8a7a9a..ae230d8f4 100644 --- a/apps/infoclk/icon.js +++ b/apps/infoclk/icon.js @@ -1,2 +1 @@ -//TODO replace app icon with my own -require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA==")) +require("heatshrink").decompress(atob("mEwgOAA4YFS/4AKEf5BlABcAjAgBjAfBAuhH/Apo")) \ No newline at end of file diff --git a/apps/infoclk/icon.png b/apps/infoclk/icon.png index a38093c5f3b6f88dbc9b75017422dbe237d4407f..24423fbd62b2a443ac8a529f2c61a25c781ac1dc 100644 GIT binary patch delta 232 zcmVuE7=Z>Vhein$@==$^0~#fOI^q~5fB>dTug^f-Mq#ZmT))q)rk@=j`;I3$ z9pH;WG0>oGNvH;rBqRyF5ST5A-uxVWfZ}z!!7INFQ6Eqrcp$LnEu?X}VuD^t^u}{E iGD%1hx)IpY0|(#5ZNCagkW>Hw002ovPDHLkV1fYHxnk!4 delta 1986 zcmV;z2R-=t0mToH8Gix*007uvZqNV#2aHKXK~z}7?O0t*Q&$}S(aJ|1h>ls;g4h+@ zB+AGl)8UNHxGbA|xTyGG;sOsoEFnM;B+-{a7Q@E_V_6nMW&&<;@}SXB3>rlzVu;X< zRWXwk`@zScE!aY#?d`|e!>RXPZf_BAEI#a?lbioJ=XcKe-G6(||Kr>ULI_?bk>E9x z{|Wt1=v8G^xDKDsH!(3`G#X=LV-*U8TrOXDqLGmilgUK&#Kc6EN+kdc4-cEoX0}QQ z*}i?dOePafM8w2kFdRO7m=7x{DH$0V5e0j_-m_=V3Z|{9s&YD=eAwyJrv(^`#S%K3 z2qBxzmYSLx`hN`y3JT`t=0fr3&YhD;M7F-UxtYZ<46||L#$^(^di83!ed+D(C4@{& zOjs-yi^VcGH|KCTl9H16n23l7R(HGIbWWX4$8TSNkjR^LcXzY8Ua#-%?PaT@qoV@@ z0~H&dOV(ykrCQkT3X65OnG@Zi=UX7pnuAvM~@a65=02q*VnUpQc}{o zb?ew_S63H*UcpOeWMo7}M)E(H!{MOHjT<+pQd3j2Oe+(oFOl#G1p(}#J#>U2O z-@eU%9{^UX^}>Y<0168WUuvx{Ar!jtTeog?yWPRTtL6=FZEYMVBWjNgS_V&)sPS%G}B_$%Z5oY6qtTenW{1Nu^g`m} z<4>G8QB+jKf{jMwlP6E8URztc{8*NimDSVJLv{8QshCWryu3WB-@kuHJ4%z_z)>FVmDx>~KaTCMyOIXO8QA0HnT74;HACXwg7ckH-_vDjOOaLMP{&$oTm9qN2UoY*s3j zp$k-3SBvIDeSLkQ;GCQstJS(Np~a>jgpg;?o|ToA30{|B7!pRFK7CqOSGQ-+9vU&4 zo0~=15JIkBzg|&MQBhHG^5n^>si{yvL^#7CggBi}n!+lT%5dRAkoEfg{-~&^mwyJV zg`(%{c5_{|Li5heVRRH03*>SnCSvt!A;4yX)rzSpC=^gC5figulK+bG%P%A?jR3)- zRuhee>z$q^RaIOsK(=io_wFrts9#0e+U70BmOFQXfFFJk2WYhdLRKr$>4@J?2q6v! z`Qs1b^%6qJ>@4Z+CDg1@L7z6pDG^{(i2SkbpPdKwMlnD;pTVci*9=hFgSs zgt0MHSEIZfVy=i!h!F4{kf=Wi^-z~Cfu=;}wcszLg7?YD27=M7%NyqKl1-Cm`wryLQYX}5_n<$Y$t;Rd=@Cijlp`igT z7pA8X8;jgrI2<^029c4dtqtxYA+Xy~SqZxxem@;2l_EMiSR_mQl~P#=wHoig549R) zWtf>+0YAjI`L;Gf2&t&xTJ(C-(h}Cne*HDfRr)^@l?oqxz*T?xiGSU)*;zC{H zE;Kda=byp9j!jSF`|t7mIj3K~jM-Uw_S)K@(Lke_XY-4Z^XGBv7Lt>LRjn4!pHo7A z{t3Gs9uLjDk)4gKEL^_K4gLLhIGynM0N~CYeEB6*D%`pSU~mw5dC1L0ZZ0gAr4fpb z#+z^A-FJgkyB$6sHGlhEyD&AyUdQ2bp|us6nb7Oe-Hnew#wVYEy>UvVFc>g11H1h{ ziS|=dXl%rhBS=q&%Y_36AV}0d{S^-&q4nIc68v%6&MW2&W1#Sn>XQf;(x2J0BqTUEnC3*|HA1l zEy&D-(a5<(Lh$z69KdWwOblM!|B#xBHEXEZd%eW%CT=(JdO4k+urdq>AVk>96Q`5N z<>FSIpHBh-Vlss_`<9lKNk2L}#ngwjPIXiTSuzz@0DyR1QVY3BO*tgz-R4N2842(v2JWwjJb}a{Z?UMX|0QIbl USs20!PXGV_07*qoM6N<$f};A%-v9sr diff --git a/apps/keytimer/ChangeLog b/apps/keytimer/ChangeLog new file mode 100644 index 000000000..c819919ed --- /dev/null +++ b/apps/keytimer/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Submitted to the app loader \ No newline at end of file diff --git a/apps/pomoplus/ChangeLog b/apps/pomoplus/ChangeLog new file mode 100644 index 000000000..1a137aad0 --- /dev/null +++ b/apps/pomoplus/ChangeLog @@ -0,0 +1,3 @@ +0.01: New app! +0.02-0.04: Bug fixes +0.05: Submitted to the app loader \ No newline at end of file diff --git a/apps/pomoplus/icon.js b/apps/pomoplus/icon.js index 020df6f5c..e4ecc7d1c 100644 --- a/apps/pomoplus/icon.js +++ b/apps/pomoplus/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwgQNhvQFCoXX7ooQFy0N7vdCYQDDIJ4YDC6QwC7oDBDwYcLN4QRDAApELEYYWGBQKEFCAoWKBIRaIBIIFEEA5iEEooUEDgYhEFxgDB6BfFGAgeFCAYXCBhAEJGYYrGMxQJDC4aIHHIjhGBQQXNE5L5DCIq3GIgiuHWQ7SHhoXKABRCBC6plDC6xVKJBgXVGIIWVAH5pTQK4X/cRAXvJCnQC7JIUCwQwTFwYwTCwgwSC4owQIwowQCw4wPCxAYNFxIYMCxgYJCpoZHBpI=")) +require("heatshrink").decompress(atob("mEwwcBkmSpICG5AIHCLMmoQOKycJAoUyiQRLAgIOBAQQyKsmSpAROpgEBhmQzIHBC4JTIDIxcHDQYgCBQUSphEGpMJkwcEwgLByBoFCIMyCIgpDL4RQEBwWQ5ICBDoRKCBAIFBNYeSjJHDKYYaCR4YLBiYgDKYo4DEwQgECIpiECISqFkJlCCIILETwYRGDo1CsiJECIiPCdIaqCSoabFCgYRHAQ5iBCJ8hcAgRNKwOQgARLU4IRCvwRMa4QRPfwQR5YooR/cAYOGgAADvwEDCI8H/4AG/Ek5IRXGpMkzJZNoQGByYRNiQJCsgRLyAJDpgRQpIRLwgJEWxARBkIJFUg4RChIJGQA4RJNw4RKLg0kCJQ4DVoUACIY")) \ No newline at end of file diff --git a/apps/pomoplus/icon.png b/apps/pomoplus/icon.png index 4ea72f270d5633755b90ede38c67cdc7e121e4c5..60d8023dbc454fead695a3af38600b584664d1df 100644 GIT binary patch delta 1250 zcmV<81ReXz5cLU=BYy_QBu_s_W1vYSoRW6*s#06+-m{}I6ba)1y+I9<2% zK00kx1RlcyjtdNVhUYcns{oKeI0J;Riy{05bZ#tC1IN-p8Kx5hM;eE=clj z3L)>2H(mm77X+~j0lkdY8W4fLBZCkE0*uY9TNB0TL`%@AXcRfYG!7bx(O#58;;YzC(-o43rd3)PzDLspINOgkVmV6e~EkI@er z;kM$pk?Y(VP2lWzDbYE{Tns2|{zZoaS4b?dG$%41c?ICm^I`diz`jwQK^3g`C} z1s_mNaOqJqh3Tab&M6UKquR!oV9o*JYFuKXS0}0P+0grN$cmR$GyP0G*0U(2wXJjl zQGeP9ovxo$Pspn=VpmXXCNp;t08=combsF|{UJ`pUu!_QbETZg0AK{#7X;b%X`95d zlu+t7@=I0=^$9ew_Bn6K?HqZ6o~~am0{e~#U=Fy(s*QU2h!THU;gU?wMWFTU%P7=> zy^2EE3i9T`b)n-{WNQk|>-riBVdtAw;(wNx*4}T?K>Dm=63;#>t$x-EJyV5R;MY)S zY^O1if%@)etI#$S;#siYl6Lxmrx3VrYqp^+KbG%5gF=%nr5@Pkbzt70Ru6-eSbEt? z-1}^{rPS@|7KQ9=3RAB!OUGf$k&$Mu%rBkTtIC?^d!CsBFsB5ybm-}YUQQuJOn-aT zjS5+h7j;apj8;$Pbh63IizSv6#%`iQvWfT8&oZT!4?Ps}XE-e%v?4HfB2ra;em9^? zZ_!S#DP;WK<~XyvYnCoOa%_39cK>fER5L#1YQs986R)7q?XoYv_~_07Kak&rVY|Vb zkaatW!>A&0csj+dyF zZ|V&cX}HY$1aQq<7dyHJwLO?d_+uKpvjcpK%obPvQ^mhG>9Xw)N|?mDA7t(x00000 MNkvXXu0mjf0D^#Bu>b%7 delta 2110 zcmV-E2*LOD3Ca+VBYyx1a7bBm000ie000ie0hKEb8vp z@AG}$=led-d)_1dPm)AItHFLj53o0k(}9&3x6n9{CXy&(cR5(NdI z8z_ZwI#ws1r6q})3WnN*b%L${*MP^so&&7`idH??b${Tn!N&yOGpr=E-M85ZYzCU4 z+^b~Rb)jaUA9yN^)4JBlk`5$MQ>Q_n&;vj(a5Rk5s1@ii z=n$+1K7R#l1?x3b5MbDhr&%#K>s0GZ8n#dH^Dv&C9gUiH2t91@Tj0&EIK5Gq1D=PW z!B4^ZgbI06{(jkDXIGq_NTQ$!+zsqv6mw|`of`9j*X2B)|;~sqEXX$Bgu;ANXHSC7( zKPl9oo3kQtmtpNf?*m7=;&c$KNAQBMLm7DAqZfDu?5jx>wA6F}mjMnLMETIn3DW>* z7jPa}Yj6l>fXrE7R`7QrUvbr8*xNu{ZUN1LO~8JzeMuCwfvy7o0CIG(>~A;dpkdqc zV1LTkyl1Z)JUD-0SPHuA7NYG1O&Ns3-U6+w7%KNX1@;PPH_&Yug42eckaKt&&|(#0 z&ItX%u>D=}^l%ad9l)2tK5u9PaHn5O043maLTAD_t%|^73xJ<-Gp*-=uLHY5CEz~6 zQ3pPo(QJiSV$R6~EO6ijs8_CH?*Wb&Hh-n8f;dV&E_8G>3buN?F9JISLm7D2Ud^Dr zz#ZTzlte+NVb7EyRDzcR2f+@6aoP_w0|$U#0}oaQ(Pj-!0nZAZbf8qoAR06q^oDUd zX4oO2r@&4EX(dCkMc6)IBUo0{dR?5#;~=;n*fJV5Q5=n$3b=~8A)EHxf^7k|8-Mx} zKv$ezPom&u;39A@ZlTQp4fl|t$7sA#9?{vk}Q^2c96ubt6-gXNx z0XiARX(@?7^hdlI8A_G`D+o; zW#C$cLIC=B_G=Oao4_uF@$@C&Qh&auThKfSwpBUsbM#F@Yl^ty)0+=bJ__bS`(5DP zBnn;t-VGPnb!PPg*aG~*U*Tf%LtSxtGKqr229JQX`+t%C3*a|& z>kW%QPiAW`vuAmjIoGRo3WCoR4O{Q_FPb+?HKfPyd&(xV*~vyQ_(~E5r^7fM9*u&- zLVJK!U>Q|f!P*v~M_sx>cR}crlmS#{A!_Qi%3OJz2)AGncHFQZ<2Gso?wPt5r$>Fd zV0D(Ns$_9^OS=c$4SIGo3V&9G@$@yJ9|4~YDx?vqfpgr@eJ__Zw zbTxGfxa@UtGzxy=7ySiqxLxQA%cV_TH!LUs%_~hIFC7MZ1V0#!f_8A3N@y$Y6|+3% za&-VDQSbrS>U;#2io`-$Vlf>^fZqcj0K0)Tiws}47&cO@fVKsi)PGKJR;TVIH2D7f z#8~_FrXC%bEXr*Ks>3qRiHfdy+R}P8e``Hoj?P2@m)^>Q=kK+|a&udc-iWh}=^8G8 z4QB?KcQH?|%tC#7cG*Xu1OF<3&Sf5Xugdq#*SuaSBK7H&YP)U;9PuPpDI!J@Jf8@HUE0{4V*It;mkt*&_b zEbudf^cJSj4DfsgzRCe-lw&}SO8)6BKq3F+4DJ}SEL9E&5`V#SLYHr~EF?4mc9c0M z7}fhWFGqJQ$XwUnM2u$-|a^ch2E1xJD3Ekv8V)LXRrg)SNVMecOEvymL$V39h4 zJN+J63cRz?p!kmuPaF2A&{~7GY@lUQsASlPPz)Rk<8*MT4tZG?MU$urbKi%*2=-~A zUc=T3HP=HS+`-Z!qb+89dNwudx8lW%}kGC+CwY^&Wa)KJI;(Bw|>UFyQa@9y+B0CIlr zbDb@uJx|}&>+v}(q!rD{#b5vcRQMUl24Vp47LI`o0z`{HV1M-47yy_hehfkh5G(#z z{Tg>U6AFMi;Ri=@u&n0E<3-&=MI~RXlB@DFArnc^gdyu=&2V zUw%F+e-E%yG4pqM>Z^plCd>dUWC42E$}VWR4OBcWW!+e(Wudk`WkRR91007*qoM6N<$f&fVJ BwnhK| delta 1550 zcmV+p2J!i@1D*_!BYyw}VoOIv0RI600RN!9r;`8x1=C4HK~!jg?U-9kR7V)ce`kSZ zaWC9NanZDrZGihXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=w zMNO+ffoiD_T1#yUim(D(ys%vMfQPeirVqOZR|L*1ZtO$(KYz{4`ELI?GvCZNFpX*a zzoEp_*#5&O&x5keLhSsu*Gux_<Sozv!G2Zyuqle<|FEQQ*V@1ifU2|*(wSZq44wWAN?1QdY^y4=phZ{UNv^6Lc zc>;mW+6*TlyMG;kBwfcbI6!~;%!3H3vD;3uHb1y^>(-=zV`ddl5V7JHiYPgG;Qe18 zAA!pHJPcz8PzubD)EWei75&!W%;@hc9rI076y7*~pzNrOp{Wz7xR8Pp+Ka+lOmQjN zgh6hHk7s??NidA)sQOMEC`*&EF||E#qBa*omB7O2VSle9DOfTq)lr<`PVzdE$VpBD zpmQjMUkgzc2!<+e_i27DG6#BUZr!wEAw+((0R3rhg#a#W&AA;jk%;M240!=-MS4mK=e~ z`n*WC0iZn3t5^jOVFEXNlM{e2iLwRWs1flt6*bv1p~(>l=lMhkZ`e&anNoVKzIIyM zZc4e>GA}dXX=n04r0c?#ut!GEI*`K+9>Rwd0OfgJm+Z!AZS&F6)+QyZ0Q>U1&if-` zBYzb&mcQAWfJ0kDph|ZjAf;27?qZHTQH^w+B-qwi=~xp` zC^DO`%X0tfMog^E`)xVoZ+l^QM zl2xNP(`}FHE}l>@0+~NB-)zoJu}>yBihsWTe!lqX$Y}q|qZQn_+byL#H~9e{^Cyxs zA@K+Bn9Xdv+|}u9O)Z1>?$KSak~>AK(L*|C&Q(e2&Pjg2$HPbX7{qj$le(M$A|j}a z43ExGVxpWYL)J+IGE1o6Y~Ip`rHrmBDr9z678w_6$gDrh?5r$SuPm0*9pj_VN`GEv zT5bR{0Xj6DrzG}4s>j3EpMT1a)jt8Ss%SYLw_Cy$v}^iU-;=_cfKaZ0SOQSht?Gs8 zE~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@!_e$Lm9|hmt>bvj55SBaNntrAr?+kAM0Bgg9fRV@+Ufeh@fjc6T)U)ff|n6bu2|n>wRr zM>)!d6_&FlTMv{2A%LcVA=jby?hy<5m~pVVZ#dP=fx>J ztJEz5af%2DfIO#NEziwy6=ulQ3bj2d+nYMos5KAJj=>r>6gI{Z&eUG!Dr&N^*}emo zMh|D(6V-LuY4+kww>>9034d?Y!&avjqB;=NPj>a{QK{05;ul7Yl2~T`(Fg#XKAjwX zJrM5zPD}amp(DgcI0xU8eMT9JM}OR{sL2i&;9?{4Lw!_q<+FAX;|Z5R?_~jNUqMz00000Ne4wvM6N<$f{y9$ A761SM diff --git a/apps/stlapview/ChangeLog b/apps/stlapview/ChangeLog new file mode 100644 index 000000000..c819919ed --- /dev/null +++ b/apps/stlapview/ChangeLog @@ -0,0 +1,2 @@ +0.01: New app! +0.02: Submitted to the app loader \ No newline at end of file diff --git a/apps/stlapview/icon.js b/apps/stlapview/icon.js index 32281b7ab..9e10b10a8 100644 --- a/apps/stlapview/icon.js +++ b/apps/stlapview/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) +require("heatshrink").decompress(atob("mEwwcCpMkyQCCB4oLFAQsf/4AC/ARvIYM//hHNAQIRBBxgRliQECCJgHFCJWJA4slCJEiDI+UIhYCFAw1IJ5NSAwtECI8/yVKNBOQYYMCpP/Nw2RAgYQBAAMP/gIBlIRHCAYAB8gRGRgVICIsESQwRCoANBA4OAAgIRGJgQRBSQWeCIck0gRFBYmf4AXDCI5BCkn/5ARGagSMBCIUn/xfBSQLaDCIiwD+SDBCJyVCCJrCCCJtPYQQROYQQ1OboYRKR4bdCCJChFCIShCCI7FDbooRCdJAXFa4wRBgAFBwARLIIIAFfY2JO4YAFNYMlQ4YRDkgSGCIuRAgaSBEAI7ChMpCJCzFgEBCImSCJEgCQPpBIlECI5NCjoJEpARISQMDPoYCBWAYCEL4V0BIjaDAQmeCI6SFAQMlkvACI5uGAYO4CJBKEBAWX//0yQ=")) \ No newline at end of file diff --git a/apps/stlapview/icon.png b/apps/stlapview/icon.png index 92ffe73b7f172f7019486231d5bc0ba327c13a67..be24b8e6f9e0b034a69e5b7fc5d29db54056c1c0 100644 GIT binary patch delta 540 zcmV+%0^|Lj473E0BYy$$1Zj2!xaOf9RYsSEJxApsi=7{o6|RV*xc? zm-uH3hFBx(LyOM`Z`rkZQOrtFv6BpN6U+w7AiHRbTX|_wMvR zfXMtj*LE%;cAg&H+vC$A5Gz`f8-ob|M1_|?J`e{$Z{aZzBY(hX@fQs5h5_JH;%yKl zz*zBD^g8d@2?D?>9)*0LT=VF%+%^PwnngSkUN-BtVDY$|IsjOOzd_QWk0=!NA&sXq z*^h~TP?!smQegUajsRNXH9?*=?J`m#BJtWy5N9$)mhT35ZCY7vd$I!vK;mWuEO+Dj zAcg_h#P9xz_kVd5hXNRreR*VhoP0wF5v%|xYfsrcna>Nilz`%W5r7+nlL6q}c47yx zcxfC@5K?aiO)dmRz|w_KK-r`#Zdqh$fzaa07TrZGihXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=w zMNO+ffoiD_T1#yUim(D(ys%vMfQPeirVqOZR|L*1ZtO$(KYz{4`ELI?GvCZNFpX*a zzoEp_*#5&O&x5keLhSsu*Gux_<Sozv!G2Zyuqle<|FEQQ*V@1ifU2|*(wSZq44wWAN?1QdY^y4=phZ{UNv^6Lc zc>;mW+6*TlyMG;kBwfcbI6!~;%!3H3vD;3uHb1y^>(-=zV`ddl5V7JHiYPgG;Qe18 zAA!pHJPcz8PzubD)EWei75&!W%;@hc9rI076y7*~pzNrOp{Wz7xR8Pp+Ka+lOmQjN zgh6hHk7s??NidA)sQOMEC`*&EF||E#qBa*omB7O2VSle9DOfTq)lr<`PVzdE$VpBD zpmQjMUkgzc2!<+e_i27DG6#BUZr!wEAw+((0R3rhg#a#W&AA;jk%;M240!=-MS4mK=e~ z`n*WC0iZn3t5^jOVFEXNlM{e2iLwRWs1flt6*bv1p~(>l=lMhkZ`e&anNoVKzIIyM zZc4e>GA}dXX=n04r0c?#ut!GEI*`K+9>Rwd0OfgJm+Z!AZS&F6)+QyZ0Q>U1&if-` zBYzb&mcQAWfJ0kDph|ZjAf;27?qZHTQH^w+B-qwi=~xp` zC^DO`%X0tfMog^E`)xVoZ+l^QM zl2xNP(`}FHE}l>@0+~NB-)zoJu}>yBihsWTe!lqX$Y}q|qZQn_+byL#H~9e{^Cyxs zA@K+Bn9Xdv+|}u9O)Z1>?$KSak~>AK(L*|C&Q(e2&Pjg2$HPbX7{qj$le(M$A|j}a z43ExGVxpWYL)J+IGE1o6Y~Ip`rHrmBDr9z678w_6$gDrh?5r$SuPm0*9pj_VN`GEv zT5bR{0Xj6DrzG}4s>j3EpMT1a)jt8Ss%SYLw_Cy$v}^iU-;=_cfKaZ0SOQSht?Gs8 zE~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@!_e$Lm9|hmt>bvj55SBaNntrAr?+kAM0Bgg9fRV@+Ufeh@fjc6T)U)ff|n6bu2|n>wRr zM>)!d6_&FlTMv{2A%LcVA=jby?hy<5m~pVVZ#dP=fx>J ztJEz5af%2DfIO#NEziwy6=ulQ3bj2d+nYMos5KAJj=>r>6gI{Z&eUG!Dr&N^*}emo zMh|D(6V-LuY4+kww>>9034d?Y!&avjqB;=NPj>a{QK{05;ul7Yl2~T`(Fg#XKAjwX zJrM5zPD}amp(DgcI0xU8eMT9JM}OR{sL2i&;9?{4Lw!_q<+FAX;|Z5R?_~jNUqMz00000Ne4wvM6N<$f~_v^ A8vp Date: Sun, 6 Nov 2022 10:20:03 +0100 Subject: [PATCH 023/228] imageclock - Add UI when first draw is complete --- apps/imageclock/app.js | 77 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index 7dd619e28..3d7a830d2 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -617,6 +617,7 @@ let firstDraw = true; firstDraw=false; requestRefresh = false; endPerfLog("initialDraw"); + if (!Bangle.uiRemove) setUi(); }).catch((e)=>{ print("Error during drawing", e); }); @@ -809,47 +810,49 @@ let firstDraw = true; handleLock(Bangle.isLocked(), true); - Bangle.setUI({ - mode : "clock", - remove : function() { - //print("remove calls"); - // Called to unload all of the clock app - Bangle.setHRMPower(0, "imageclock"); - Bangle.setBarometerPower(0, 'imageclock'); + let setUi = function(){ + Bangle.setUI({ + mode : "clock", + remove : function() { + //print("remove calls"); + // Called to unload all of the clock app + Bangle.setHRMPower(0, "imageclock"); + Bangle.setBarometerPower(0, 'imageclock'); - Bangle.removeListener('swipe', handleSwipe); - Bangle.removeListener('lock', handleLock); - Bangle.removeListener('charging', handleCharging); - Bangle.removeListener('HRM', handleHrm); - Bangle.removeListener('pressure', handlePressure); + Bangle.removeListener('swipe', handleSwipe); + Bangle.removeListener('lock', handleLock); + Bangle.removeListener('charging', handleCharging); + Bangle.removeListener('HRM', handleHrm); + Bangle.removeListener('pressure', handlePressure); - if (deferredTimout) clearTimeout(deferredTimout); - if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked); - if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked); + if (deferredTimout) clearTimeout(deferredTimout); + if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked); + if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked); - for (let i of unlockedDrawInterval){ - //print("Clearing unlocked", i); - clearInterval(i); + for (let i of global.unlockedDrawInterval){ + //print("Clearing unlocked", i); + clearInterval(i); + } + delete global.unlockedDrawInterval; + for (let i of global.lockedDrawInterval){ + //print("Clearing locked", i); + clearInterval(i); + } + delete global.lockedDrawInterval; + delete global.showWidgets; + delete global.firstDraw; + + delete Bangle.printPerfLog; + if (settings.perflog){ + delete Bangle.resetPerfLog; + delete performanceLog; + } + + cleanupDelays(); + restoreWidgetDraw(); } - delete unlockedDrawInterval; - for (let i of lockedDrawInterval){ - //print("Clearing locked", i); - clearInterval(i); - } - delete lockedDrawInterval; - delete showWidgets; - delete firstDraw; - - delete Bangle.printPerfLog; - if (settings.perflog){ - delete Bangle.resetPerfLog; - delete performanceLog; - } - - cleanupDelays(); - restoreWidgetDraw(); - } - }); + }); + } Bangle.loadWidgets(); clearWidgetsDraw(); From f5d3bb0a529ad4d5742c4338f48e10fee6b4861d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 01:20:20 +0100 Subject: [PATCH 024/228] imageclock - Bump version --- apps/imageclock/ChangeLog | 4 ++++ apps/imageclock/metadata.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/imageclock/ChangeLog b/apps/imageclock/ChangeLog index f81bbf185..d681176a7 100644 --- a/apps/imageclock/ChangeLog +++ b/apps/imageclock/ChangeLog @@ -12,3 +12,7 @@ 0.10: Fix clock not correctly refreshing when drawing in timeouts option is not on 0.11: Additional option in customizer to force drawing directly Fix some problems in handling timeouts +0.12: Use widget_utils module + Fix colorsetting in promises in generated code + Some performance improvements by caching lookups + Activate UI after first draw is complete to prevent drawing over launcher diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json index e068b9fa7..51257b435 100644 --- a/apps/imageclock/metadata.json +++ b/apps/imageclock/metadata.json @@ -2,7 +2,7 @@ "id": "imageclock", "name": "Imageclock", "shortName": "Imageclock", - "version": "0.11", + "version": "0.12", "type": "clock", "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", "icon": "app.png", From 3462b36f9c8e7b0a24871839ab5abadb1d88014b Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Thu, 27 Oct 2022 21:27:02 +0200 Subject: [PATCH 025/228] wid_edit - Wrap loadWidgets instead of replacing it --- apps/wid_edit/boot.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/wid_edit/boot.js b/apps/wid_edit/boot.js index 872965c97..8dc120fd6 100644 --- a/apps/wid_edit/boot.js +++ b/apps/wid_edit/boot.js @@ -1,18 +1,14 @@ -Bangle.loadWidgets = function() { - global.WIDGETS={}; - require("Storage").list(/\.wid\.js$/) - .forEach(w=>{ - try { eval(require("Storage").read(w)); } - catch (e) { print(w, e); } - }); - const s = require("Storage").readJSON("wid_edit.json", 1) || {}, - c = s.custom || {}; +Bangle.loadWidgets = (o => ()=>{ + o(); + const s = require("Storage").readJSON("wid_edit.json", 1) || {}; + const c = s.custom || {}; for (const w in c){ if (!(w in WIDGETS)) continue; // widget no longer exists // store defaults of customized values in _WIDGETS - global._WIDGETS=global._WIDGETS||{}; - _WIDGETS[w] = {}; + if (!global._WIDGETS) global._WIDGETS = {}; + if (!global._WIDGETS[w]) global._WIDGETS[w] = {}; Object.keys(c[w]).forEach(k => _WIDGETS[w][k] = WIDGETS[w][k]); + //overide values in widget with configured ones Object.assign(WIDGETS[w], c[w]); } const W = WIDGETS; @@ -21,4 +17,4 @@ Bangle.loadWidgets = function() { .sort() .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder)) .forEach(k => WIDGETS[k] = W[k]); -} \ No newline at end of file +})(Bangle.loadWidgets); From 81632cda7efca73e7fef1be515cc6b0e550fec12 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 12:42:11 +0100 Subject: [PATCH 026/228] iconlaunch - Cleanup and reset timeout as needed --- apps/iconlaunch/app.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js index 479956019..e7cf34840 100644 --- a/apps/iconlaunch/app.js +++ b/apps/iconlaunch/app.js @@ -133,6 +133,7 @@ g.flip(); const itemsN = Math.ceil(launchCache.apps.length / appsN); let onDrag = function(e) { + updateTimeout(); g.setColor(g.theme.fg); g.setBgColor(g.theme.bg); let dy = e.dy; @@ -182,6 +183,7 @@ drag: onDrag, touch: (_, e) => { if (e.y < R.y - 4) return; + updateTimeout(); let i = YtoIdx(e.y); selectItem(i, e); }, @@ -202,16 +204,23 @@ delete idxToY; delete YtoIdx; delete settings; + if (timeout) clearTimeout(timeout); setTimeout(eval, 0, s.read(".bootcde")); }; if (settings.oneClickExit) mode.btn = returnToClock; + + let timeout; + const updateTimeout = function(){ if (settings.timeOut!="Off"){ let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt - setTimeout(returnToClock,time*1000); + if (timeout) clearTimeout(timeout); + timeout = setTimeout(returnToClock,time*1000); + } } + updateTimeout(); Bangle.setUI(mode); } From d3286bab65bb59fb8cdecf63217a6fd238b66029 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 14:20:55 +0100 Subject: [PATCH 027/228] iconlaunch - Bump version --- apps/iconlaunch/ChangeLog | 2 ++ apps/iconlaunch/metadata.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog index c71da1467..b0fcaf856 100644 --- a/apps/iconlaunch/ChangeLog +++ b/apps/iconlaunch/ChangeLog @@ -12,3 +12,5 @@ used Object.assing for the settings fix cache not deleted when "showClocks" options is changed added timeOut to return to the clock +0.11: Cleanup timeout when changing to clock + Reset timeout on swipe and drag diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json index 13e7aee08..8154d830a 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.10", + "version": "0.11", "icon": "app.png", "description": "A launcher inspired by smartphones, with an icon-only scrollable menu.", "tags": "tool,system,launcher", From ffd85de6356ff33ea069b51f3796c1e977f06960 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 6 Nov 2022 15:15:42 +0100 Subject: [PATCH 028/228] Fast load to clockface on B2 --- apps/dtlaunch/ChangeLog | 1 + apps/dtlaunch/app-b2.js | 114 +++++++++++++++++++++++++----------- apps/dtlaunch/metadata.json | 2 +- 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 16c550334..05a1637c0 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -14,3 +14,4 @@ 0.14: Don't move pages when doing exit swipe - Bangle 2. 0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2. 0.16: Use default Bangle formatter for booleans +0.17: Bangle 2: Fast loading on exit to clock face diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 8cd5790bb..ddbb74023 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -1,28 +1,31 @@ +{ // must be inside our own scope here so that when we are unloaded everything disappears + /* Desktop launcher * */ -var settings = Object.assign({ +let settings = Object.assign({ showClocks: true, showLaunchers: true, direct: false, oneClickExit:false, - swipeExit: false + swipeExit: false, + fastload: false, }, require('Storage').readJSON("dtlaunch.json", true) || {}); if( settings.oneClickExit) - setWatch(_=> load(), BTN1); + setWatch(_=> returnToClock, BTN1); -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{ - var a=s.readJSON(app,1); +let s = require("Storage"); + var apps = s.list(/\.info$/).map(app=>{ + let a=s.readJSON(app,1); return a && { name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src };}).filter( app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); - + apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); + let n=(0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first if (a.nameb.name) return 1; @@ -33,28 +36,28 @@ apps.forEach(app=>{ app.icon = s.read(app.icon); // should just be a link to a memory area }); -var Napps = apps.length; -var Npages = Math.ceil(Napps/4); -var maxPage = Npages-1; -var selected = -1; -var oldselected = -1; -var page = 0; +let Napps = apps.length; +let Npages = Math.ceil(Napps/4); +let maxPage = Npages-1; +let selected = -1; +let oldselected = -1; +let page = 0; const XOFF = 24; const YOFF = 30; function draw_icon(p,n,selected) { - var x = (n%2)*72+XOFF; - var y = n>1?72+YOFF:YOFF; + let x = (n%2)*72+XOFF; + let y = n>1?72+YOFF:YOFF; (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); g.clearRect(x+12,y+4,x+59,y+51); g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} g.setFontAlign(0,-1,0).setFont("6x8",1); - var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); - var lineY = 0; - var line = ""; + let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); + let lineY = 0; + let line = ""; while (txt.length > 0){ - var c = txt.shift(); + let c = txt.shift(); if (c.length + 1 + line.length > 13){ if (line.length > 0){ @@ -72,24 +75,24 @@ function draw_icon(p,n,selected) { function drawPage(p){ g.reset(); g.clearRect(0,24,175,175); - var O = 88+YOFF/2-12*(Npages/2); - for (var j=0;j{ +function swipeListenerDt(dirLeftRight, dirUpDown){ selected = 0; oldselected=-1; - if(settings.swipeExit && dirLeftRight==1) load(); + if(settings.swipeExit && dirLeftRight==1) returnToClock(); if (dirUpDown==-1||dirLeftRight==-1){ ++page; if (page>maxPage) page=0; drawPage(page); @@ -97,17 +100,18 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ --page; if (page<0) page=maxPage; drawPage(page); } -}); +} +Bangle.on("swipe",swipeListenerDt); function isTouched(p,n){ if (n<0 || n>3) return false; - var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; - var x2 = x1+71; var y2 = y1+81; + let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; + let x2 = x1+71; let y2 = y1+81; return (p.x>x1 && p.y>y1 && p.x{ - var i; +function touchListenerDt(_,p){ + let i; for (i=0;i<4;i++){ if((page*4+i){ if (selected!=i && !settings.direct){ draw_icon(page,selected,false); } else { - load(apps[page*4+i].src); + loadApp(apps[page*4+i].src); } } selected=i; @@ -128,9 +132,53 @@ Bangle.on("touch",(_,p)=>{ draw_icon(page,selected,false); selected=-1; } -}); +} +Bangle.on("touch",touchListenerDt); + +const returnToClock = function() { // Delete virtually everything, don't know if some deletes are unnecessary. + Bangle.setUI(); + if (watch) clearWatch(watch); + delete s; + delete a; + delete n; + delete Napps; + delete Npages; + delete maxPage; + delete selected; + delete oldselected; + delete page; + delete XOFF; + delete YOFF; + delete x; + delete y; + delete txt; + delete lineY; + delete line; + delete c; + delete O; + delete x1; + delete x2; + delete i; + Bangle.removeListener("swipe", swipeListenerDt); + Bangle.removeListener("touch", touchListenerDt); + var apps = []; + delete apps; + delete returnToClock; + delete settings; + delete watch; + delete loadApp; + setTimeout(eval, 0, s.read(".bootcde")); + }; + + let watch; + let loadApp; + loadApp = function(name) { + load(name); + }; Bangle.loadWidgets(); g.clear(); Bangle.drawWidgets(); drawPage(0); + +} // end of app scope diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 36728f342..b71f4ca9b 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.16", + "version": "0.17", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", From 9d71928ba1bc67794a9111e62479b7d1fe0ee96f Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 15:18:05 +0100 Subject: [PATCH 029/228] bthrm - Prevent mixing of internal and external HRM events --- apps/bthrm/lib.js | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index 9e2f0fe63..d9acce90c 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -154,8 +154,8 @@ exports.enable = () => { src: "bthrm" }; - log("Emitting HRM", repEvent); - Bangle.emit("HRM_int", repEvent); + log("Emitting aggregated HRM", repEvent); + Bangle.emit("HRM_R", repEvent); } var newEvent = { @@ -538,35 +538,33 @@ exports.enable = () => { }; if (settings.replace){ + // register a listener for original HRM events and emit as HRM_int Bangle.on("HRM", (e) => { e.modified = true; Bangle.emit("HRM_int", e); + if (fallbackActive){ + // if fallback to internal HRM is active, emit as HRM_R to which everyone listens + Bangle.emit("HRM_R", e); + } }); - Bangle.origOn = Bangle.on; - Bangle.on = function(name, callback) { - if (name == "HRM") { - Bangle.origOn("HRM_int", callback); - } else { - Bangle.origOn(name, callback); - } - }; - - Bangle.origRemoveListener = Bangle.removeListener; - Bangle.removeListener = function(name, callback) { - if (name == "HRM") { - Bangle.origRemoveListener("HRM_int", callback); - } else { - Bangle.origRemoveListener(name, callback); - } - }; + // force all apps wanting to listen to HRM to actually get events for HRM_R + Bangle.on = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.on); + Bangle.removeListener = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.removeListener); } Bangle.origSetHRMPower = Bangle.setHRMPower; if (settings.startWithHrm){ - Bangle.setHRMPower = function(isOn, app) { log("setHRMPower for " + app + ": " + (isOn?"on":"off")); if (settings.enabled){ From 0f9a117149bfa56104c0b64be870fadb71cd6b6e Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 15:47:54 +0100 Subject: [PATCH 030/228] bthrm - Always use a grace period to force some steps into next idle period --- apps/bthrm/lib.js | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index d9acce90c..3b9e9faa5 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -312,13 +312,13 @@ exports.enable = () => { result = result.then(()=>{ log("Starting notifications", newCharacteristic); var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); - if (settings.gracePeriodNotification > 0){ - log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); - startPromise = startPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodNotification); - }); - } + + log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); + startPromise = startPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodNotification); + }); + return startPromise; }); } @@ -429,13 +429,11 @@ exports.enable = () => { var connectPromise = gatt.connect(connectSettings).then(function() { log("Connected."); }); - if (settings.gracePeriodConnect > 0){ - log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); - connectPromise = connectPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodConnect); - }); - } + log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); + connectPromise = connectPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodConnect); + }); return connectPromise; } else { return Promise.resolve(); @@ -476,13 +474,11 @@ exports.enable = () => { log("Supporting service", service.uuid); result = attachServicePromise(result, service); } - if (settings.gracePeriodService > 0) { - log("Add " + settings.gracePeriodService + "ms grace period after services"); - result = result.then(()=>{ - log("Wait after services"); - return waitingPromise(settings.gracePeriodService); - }); - } + log("Add " + settings.gracePeriodService + "ms grace period after services"); + result = result.then(()=>{ + log("Wait after services"); + return waitingPromise(settings.gracePeriodService); + }); return result; }); } else { From 0a1226ad1eeb31056e4f41117435a093c554afe2 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 6 Nov 2022 15:52:14 +0100 Subject: [PATCH 031/228] bthrm - Optionally use bonding for BLE sensor --- apps/bthrm/default.json | 3 ++- apps/bthrm/lib.js | 26 ++++++++++++++------------ apps/bthrm/settings.js | 6 ++++++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index fb284bcd2..2c729ec68 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -16,5 +16,6 @@ "gracePeriodNotification": 0, "gracePeriodConnect": 0, "gracePeriodService": 0, - "gracePeriodRequest": 0 + "gracePeriodRequest": 0, + "bonding": false } diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index 3b9e9faa5..dc3578453 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -439,18 +439,20 @@ exports.enable = () => { return Promise.resolve(); } }); - -/* promise = promise.then(() => { - log(JSON.stringify(gatt.getSecurityStatus())); - if (gatt.getSecurityStatus()['bonded']) { - log("Already bonded"); - return Promise.resolve(); - } else { - log("Start bonding"); - return gatt.startBonding() - .then(() => console.log(gatt.getSecurityStatus())); - } - });*/ + + if (settings.bonding){ + promise = promise.then(() => { + log(JSON.stringify(gatt.getSecurityStatus())); + if (gatt.getSecurityStatus()['bonded']) { + log("Already bonded"); + return Promise.resolve(); + } else { + log("Start bonding"); + return gatt.startBonding() + .then(() => console.log(gatt.getSecurityStatus())); + } + }); + } promise = promise.then(()=>{ if (!characteristics || characteristics.length === 0){ diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index 2b19ea46a..fb5aa04da 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -96,6 +96,12 @@ writeSettings("debuglog",v); } }, + 'Use bonding': { + value: !!settings.bonding, + onchange: v => { + writeSettings("bonding",v); + } + }, 'Grace periods': function() { E.showMenu(submenu_grace); } }; From e20a47bf35da6bddb35573300c7a5f2916ff4ce6 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 6 Nov 2022 16:20:42 +0100 Subject: [PATCH 032/228] some tweaks --- apps/dtlaunch/app-b2.js | 62 +++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index ddbb74023..8bbd9ed5c 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -10,7 +10,6 @@ let settings = Object.assign({ direct: false, oneClickExit:false, swipeExit: false, - fastload: false, }, require('Storage').readJSON("dtlaunch.json", true) || {}); if( settings.oneClickExit) @@ -45,7 +44,7 @@ let page = 0; const XOFF = 24; const YOFF = 30; -function draw_icon(p,n,selected) { +let drawIcon= function(p,n,selected) { let x = (n%2)*72+XOFF; let y = n>1?72+YOFF:YOFF; (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); @@ -56,9 +55,8 @@ function draw_icon(p,n,selected) { let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); let lineY = 0; let line = ""; - while (txt.length > 0){ + while (txt.length > 0){ let c = txt.shift(); - if (c.length + 1 + line.length > 13){ if (line.length > 0){ g.drawString(line.trim(),x+36,y+54+lineY*8); @@ -70,9 +68,9 @@ function draw_icon(p,n,selected) { } } g.drawString(line.trim(),x+36,y+54+lineY*8); -} +}; -function drawPage(p){ +let drawPage = function(p){ g.reset(); g.clearRect(0,24,175,175); let O = 88+YOFF/2-12*(Npages/2); @@ -84,12 +82,17 @@ function drawPage(p){ } for (let i=0;i<4;i++) { if (!apps[p*4+i]) return i; - draw_icon(p,i,selected==i && !settings.direct); + drawIcon(p,i,selected==i && !settings.direct); } g.flip(); -} +}; + +Bangle.loadWidgets(); +//g.clear(); +Bangle.drawWidgets(); +drawPage(0); -function swipeListenerDt(dirLeftRight, dirUpDown){ +let swipeListenerDt = function(dirLeftRight, dirUpDown){ selected = 0; oldselected=-1; if(settings.swipeExit && dirLeftRight==1) returnToClock(); @@ -100,27 +103,27 @@ function swipeListenerDt(dirLeftRight, dirUpDown){ --page; if (page<0) page=maxPage; drawPage(page); } -} +}; Bangle.on("swipe",swipeListenerDt); -function isTouched(p,n){ +let isTouched = function(p,n){ if (n<0 || n>3) return false; let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; let x2 = x1+71; let y2 = y1+81; return (p.x>x1 && p.y>y1 && p.x=0 || settings.direct) { if (selected!=i && !settings.direct){ - draw_icon(page,selected,false); + drawIcon(page,selected,false); } else { - loadApp(apps[page*4+i].src); + load(apps[page*4+i].src); } } selected=i; @@ -129,15 +132,14 @@ function touchListenerDt(_,p){ } } if ((i==4 || (page*4+i)>Napps) && selected>=0) { - draw_icon(page,selected,false); + drawIcon(page,selected,false); selected=-1; } -} +}; Bangle.on("touch",touchListenerDt); -const returnToClock = function() { // Delete virtually everything, don't know if some deletes are unnecessary. +const returnToClock = function() { Bangle.setUI(); - if (watch) clearWatch(watch); delete s; delete a; delete n; @@ -159,26 +161,18 @@ const returnToClock = function() { // Delete virtually everything, don't know if delete x1; delete x2; delete i; + delete drawIcon; + delete drawPage; + delete isTouched; Bangle.removeListener("swipe", swipeListenerDt); + delete swipeListenerDt; Bangle.removeListener("touch", touchListenerDt); + delete touchListenerDt; var apps = []; delete apps; delete returnToClock; delete settings; - delete watch; - delete loadApp; setTimeout(eval, 0, s.read(".bootcde")); - }; - - let watch; - let loadApp; - loadApp = function(name) { - load(name); - }; +}; -Bangle.loadWidgets(); -g.clear(); -Bangle.drawWidgets(); -drawPage(0); - } // end of app scope From 5fa96ee5f77dd617b90ffff9904882972d5ce578 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 24 Oct 2022 12:22:35 +0100 Subject: [PATCH 033/228] New medical info app Initial implementation to enable custom text in the medical alert widget Possible future updates could include better editing UI instead of raw json, more information eg. medications, ability to set info from other apps, etc. Signed-off-by: James Taylor --- apps/medicalinfo/ChangeLog | 1 + apps/medicalinfo/README.md | 27 ++++++ apps/medicalinfo/app-icon.js | 1 + apps/medicalinfo/app.js | 61 ++++++++++++ apps/medicalinfo/app.png | Bin 0 -> 713 bytes apps/medicalinfo/interface.html | 135 ++++++++++++++++++++++++++ apps/medicalinfo/lib.js | 21 ++++ apps/medicalinfo/medicalinfo.json | 6 ++ apps/medicalinfo/metadata.json | 20 ++++ apps/medicalinfo/screenshot_light.png | Bin 0 -> 3053 bytes apps/widmeda/ChangeLog | 1 + apps/widmeda/README.md | 4 +- apps/widmeda/metadata.json | 3 +- apps/widmeda/widget.js | 35 +++++-- 14 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 apps/medicalinfo/ChangeLog create mode 100644 apps/medicalinfo/README.md create mode 100644 apps/medicalinfo/app-icon.js create mode 100644 apps/medicalinfo/app.js create mode 100644 apps/medicalinfo/app.png create mode 100644 apps/medicalinfo/interface.html create mode 100644 apps/medicalinfo/lib.js create mode 100644 apps/medicalinfo/medicalinfo.json create mode 100644 apps/medicalinfo/metadata.json create mode 100644 apps/medicalinfo/screenshot_light.png diff --git a/apps/medicalinfo/ChangeLog b/apps/medicalinfo/ChangeLog new file mode 100644 index 000000000..e8739a121 --- /dev/null +++ b/apps/medicalinfo/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Medical Information application! diff --git a/apps/medicalinfo/README.md b/apps/medicalinfo/README.md new file mode 100644 index 000000000..6dd19d4c6 --- /dev/null +++ b/apps/medicalinfo/README.md @@ -0,0 +1,27 @@ +# Medical Information + +This app displays basic medical information, and provides a common way to set up the `medicalinfo.json` file, which other apps can use if required. + +## Medical information JSON file + +When the app is loaded from the app loader, a file named `medicalinfo.json` is loaded along with the javascript etc. +The file has the following contents: + +``` +{ + "bloodType": "", + "height": "", + "weight": "", + "medicalAlert": [ "" ] +} +``` + +## Medical information editor + +Clicking on the download icon of `Medical Information` in the app loader invokes the editor. +The editor downloads and displays the current `medicalinfo.json` file, which can then be edited. +The edited `medicalinfo.json` file is uploaded to the Bangle by clicking the `Upload` button. + +## Creator + +James Taylor ([jt-nti](https://github.com/jt-nti)) diff --git a/apps/medicalinfo/app-icon.js b/apps/medicalinfo/app-icon.js new file mode 100644 index 000000000..1ae7916fb --- /dev/null +++ b/apps/medicalinfo/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg+7kUiCykCC4MgFykgDIIXUAQgAMiMRiREBC4YABkILBCxEBC4pHCC4kQFxIXEAAgXCGBERif/+QXHl//mIXJj//+YXHn//+IXL/8yCwsjBIIXNABIX/C63d7oDB+czmaPPC7hHR/oWBAAPfC65HRC7qnXX/4XDABAXkIIQAFI5wXXL/5f/L/5fvC9sTC5cxC5IAOC48BCxsQC44wOCxAArA")) diff --git a/apps/medicalinfo/app.js b/apps/medicalinfo/app.js new file mode 100644 index 000000000..9c4941744 --- /dev/null +++ b/apps/medicalinfo/app.js @@ -0,0 +1,61 @@ +const medicalinfo = require('medicalinfo').load(); +// const medicalinfo = { +// bloodType: "O+", +// height: "166cm", +// weight: "73kg" +// }; + +function hasAlert(info) { + return (Array.isArray(info.medicalAlert)) && (info.medicalAlert[0]); +} + +// No space for widgets! +// TODO: no padlock widget visible so prevent screen locking? + +g.clear(); +const bodyFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; +g.setFont(bodyFont); + +const title = hasAlert(medicalinfo) ? "MEDICAL ALERT" : "Medical Information"; +var lines = []; + +lines = g.wrapString(title, g.getWidth() - 10); +var titleCnt = lines.length; +if (titleCnt) lines.push(""); // add blank line after title + +if (hasAlert(medicalinfo)) { + medicalinfo.medicalAlert.forEach(function (details) { + lines = lines.concat(g.wrapString(details, g.getWidth() - 10)); + }); + lines.push(""); // add blank line after medical alert +} + +if (medicalinfo.bloodType) { + lines = lines.concat(g.wrapString("Blood group: " + medicalinfo.bloodType, g.getWidth() - 10)); +} +if (medicalinfo.height) { + lines = lines.concat(g.wrapString("Height: " + medicalinfo.height, g.getWidth() - 10)); +} +if (medicalinfo.weight) { + lines = lines.concat(g.wrapString("Weight: " + medicalinfo.weight, g.getWidth() - 10)); +} + +lines.push(""); + +// TODO: display instructions for updating medical info if there is none! + +E.showScroller({ + h: g.getFontHeight(), // height of each menu item in pixels + c: lines.length, // number of menu items + // a function to draw a menu item + draw: function (idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx < titleCnt ? g.theme.bg2 : g.theme.bg). + setColor(idx < titleCnt ? g.theme.fg2 : g.theme.fg). + clearRect(r.x, r.y, r.x + r.w, r.y + r.h); + g.setFont(bodyFont).drawString(lines[idx], r.x, r.y); + } +}); + +// Show launcher when button pressed +setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" }); diff --git a/apps/medicalinfo/app.png b/apps/medicalinfo/app.png new file mode 100644 index 0000000000000000000000000000000000000000..16204ea894c93614f302be7e8c08c04a2c7d884f GIT binary patch literal 713 zcmV;)0yh1LP)JyIpq(iM5zZq3Z;jN z6c2hTQZMl&c=Hfb&q}={R4ix@_QTTt0p5DZWwD-&VEu|#yx8oHhbD21yO~{gHhakY zPCGmA%)C$D_t_iMI3H%}!5LWS&Q5ZUz9FZ503{2(V&Xwc`}b0I&uiVS_x7eV%jyjkY0RH|MTRzMq|* zs8|}kQJ+00`#9Z8P&ljJw z*4UNR)#ft;egI@kr&eygxFGb5&;!}28G}*Hsi)ft*styXs#d>UsS%t5`f`kIfC!kzvR8UQiYALy%wEjX(lg+s19qshwoe(F!OJIMCf5;CtK4XEY3y2ckw= z1-!IO7#`^E3b^&OUz7*_moR$TFWp@MBfTL8NRfWJBE3 + + + + + + +
+ + + + + +

+
+    
+    
+  
+
diff --git a/apps/medicalinfo/lib.js b/apps/medicalinfo/lib.js
new file mode 100644
index 000000000..683005359
--- /dev/null
+++ b/apps/medicalinfo/lib.js
@@ -0,0 +1,21 @@
+const storage = require('Storage');
+
+exports.load = function () {
+  const medicalinfo = storage.readJSON('medicalinfo.json') || {
+    bloodType: "",
+    height: "",
+    weight: "",
+    medicalAlert: [""]
+  };
+
+  // Don't return anything unexpected
+  const expectedMedicalinfo = [
+    "bloodType",
+    "height",
+    "weight",
+    "medicalAlert"
+  ].filter(key => key in medicalinfo)
+    .reduce((obj, key) => (obj[key] = medicalinfo[key], obj), {});
+
+  return expectedMedicalinfo;
+};
diff --git a/apps/medicalinfo/medicalinfo.json b/apps/medicalinfo/medicalinfo.json
new file mode 100644
index 000000000..8b49725cb
--- /dev/null
+++ b/apps/medicalinfo/medicalinfo.json
@@ -0,0 +1,6 @@
+{
+    "bloodType": "",
+    "height": "",
+    "weight": "",
+    "medicalAlert": [ "" ]
+}
diff --git a/apps/medicalinfo/metadata.json b/apps/medicalinfo/metadata.json
new file mode 100644
index 000000000..f1a0c145f
--- /dev/null
+++ b/apps/medicalinfo/metadata.json
@@ -0,0 +1,20 @@
+{ "id": "medicalinfo",
+  "name": "Medical Information",
+  "version":"0.01",
+  "description": "Provides 'medicalinfo.json' used by various health apps, as well as a way to edit it from the App Loader",
+  "icon": "app.png",
+  "tags": "health,medical",
+  "type": "app",
+  "supports" : ["BANGLEJS","BANGLEJS2"],
+  "readme": "README.md",
+  "screenshots": [{"url":"screenshot_light.png"}],
+  "interface": "interface.html",
+  "storage": [
+    {"name":"medicalinfo.app.js","url":"app.js"},
+    {"name":"medicalinfo.img","url":"app-icon.js","evaluate":true},
+    {"name":"medicalinfo","url":"lib.js"}
+  ],
+  "data": [
+    {"name":"medicalinfo.json","url":"medicalinfo.json"}
+  ]
+}
diff --git a/apps/medicalinfo/screenshot_light.png b/apps/medicalinfo/screenshot_light.png
new file mode 100644
index 0000000000000000000000000000000000000000..42970f9fc8589f7947e12e868ad1a43b3aeb6227
GIT binary patch
literal 3053
zcmeH}`#%#77soXtlaxzxm;1e#CaUo@oBRDv#&TPjO0KzOslLV#M&_FDQtqR%i7(d0
zC47~~)9uk(4m&imk8oUD%v%L?=H@g29dfxGc)
z%YQ;hkeA7{&jGx0B*x7e#@9S5&*tMh_RJP;;gJ}yhFnYf+$dW1@eq+v6Z$XYbm~H-UWCr~VCy#GrM8Rf@BF(H!>Y7%{pMFqpaJ_LyMNtd
zKA37?VD5MNVTU230f8-V`u`n~P(
zlsg8YNNx+D0h1j+j9%JnKAw>I0||ICyPq@sfj(4jBRyq0W@1*!f28{uR`N!I{6ujh
zfE2CvrT&e1T#D|t`nE(T^kZ$k6(Ob=29Be3oiz1`Rms-0(Gpl6jTGpC
zE2HDd5$M@)VSI#tY>EdKU1v5^#nZ)h020<`qb_|G7m$jGHFm1&k3XJ*dv&gfJ|;T{
zxhZ-|0SEJD*#++8!HR^ZT;Wu~-?D*U`MU&jx83uaJ@aI%yil>4>$m-17*VH<5~ii)
zG|WOr#o}5xKZH`Cs%%+KT2>rEywYs_#GSl(oywO&^6BZPrjQx-or2?p8R3+4y~M%s
zlPv(=D!o`$zq(gT#?Zl$3K?UD&dd)vxcA5&j_BgI0qbmx7e%x?BG5lt%#Q9em|oOJS!s&f2$GIeFDa|F{lO
zhdOk%493l^vY5%^{1Y^2oPMh64T2ALai;ek;E`}()0~HZD5KPib>aMd;0S$*uaF|u9MM-%gl{mvOZq}&U`8O*k=j(
ziA4&rE6f=B76aaFMTgvt&_zOEL*rVbjGbq0!=`gGS9`_oaaZ(}rH=OTx+pf&XVn23H;39xw4{zmlpu!UwRs}$
zPx~xdd0l#LRC`=Hs2r>>n)TmCPxZVWYvUZG@o&rp7(c94*eh_1olV#b?h$F5={%I^
z*e0npDP1$fPTFn{qUm7rb9)w=m47a`>IF*Iz)J4wz1&5aW{Lrp{Q7qrQOv?Rjj-H?
zIImIagsK@Wz1r23t&R5ESR1&OI`RBX-fq-|FZN~Kf#{$mue6tS@|-A8;|IsA0}-Yd
z&YjtaDImN%*j_@LaXRw~E4&pRz0B-eifC_IYUf6Bp`qD~KFnP^y0z0CrYynGuBfsG
zlP_H|ziheq@d2HL6as$E0$PRIE5HHiVE`;G1JGWA&>NQ@4f
z>WLSlXV$9ZG3r2m42OW1>MRAQauGNs?fa5?GOzIXKU0Xid2OwL)7{tAuMTbvIm$uZ
zYgba$Oo^?ItUeIlI6vvB0oJ_AT!*{Iv4g;Dy@IlJm?P4R#G)566T=}!T;EJ77dxh7
zYbZmY{a;(&b0f~#t-_G!xrz%z!I|Z&d&E+6iReSJJ)58Ar?-Nv6K9B*1lW@WR0{bR
zDp@BJdvRe+88{=DhQ^)zX!7Qd4SLSe;k`8AnFPEX`ux5g~3iomS|FqU+&+tSsp2BxP&ceIi19^l2GC@KZRbO)QQoe
zl1k>@n!`~`xuR}l&A|}`&HkrqNL_5%-TIJ-Y28IF>NV&{#k(jxMxl7R%wD6V)L|mc
z850{xv(EZzSQGDQQ#VyCy&9}Dz&@Z|T*ln0Fsy{5fHrF;`!uftm6$D|^jw4xUa|_x
zWDK2_%!7S35{lcB|32=v@9uifK?c-#_i~byMVVlJ%jt7J5vJ~~gJE~<1-uGRDz~@^
z*HOm`V90IZEoA`de)Wl@GoDjQ-k4+@!cbS|!lFmu=(EOY;BUkkIO@yLRR?bWgSW8u
zU$V~UhI~sd8Z_J0G^@g{=rUFY&}A%O8)=j)+NDm&|i_qCl4mT;MFfx8uq72xv@ur<`h}-vrH$rTJK&
zKK;3y8HrGR!ft0;jf!iXZYXfW?cb*-b8ET!V*;MHTRlEd^u(sJD?jXX_v-Rh-%3|#
zpjOIyVL-8ry(w-;>%JvRDqIrW>b+_MC76<)p&%fwrH_ds)Riz9oHa7pYyS}k_So15
zcz)}zni{ifqe9rRl2xk^2`2Ml{!SBpe(+G)U}YRk-f6*E126hv$9}C|3@d_@%F5?#
zIANLz%I`C2+RROie4~Q<{0-@DIm2mgide~u!VO?W_agT!r>_pe
zuDn@uZe?hbAdfUL+6@M^mLg}CvH3Gif}3JLWJv?Bfo6AoHFLrNn(8eQ842lzdu*7C
zfI+`e$XLVLy8q`w=t2=V!X
zPY4eqGT=)gFgkFQaC|^T6$Kc6IIS$f!^l&NiImI{37-ceqQsL1SfkL_t%Ls`{XZ;x
a=P0M>wWyHO$rrr;ozE8G1aF3+?*1ENc&b {
+  function getAlertText() {
+    const medicalinfo = require("medicalinfo").load();
+    const alertText = ((Array.isArray(medicalinfo.medicalAlert)) && (medicalinfo.medicalAlert[0])) ? medicalinfo.medicalAlert[0] : "";
+    return (g.wrapString(alertText, g.getWidth()).length === 1) ? alertText : "MEDICAL ALERT";
+  }
+
   // Top right star of life logo
-  WIDGETS["widmedatr"]={
+  WIDGETS["widmedatr"] = {
     area: "tr",
     width: 24,
-    draw: function() {
+    draw: function () {
       g.reset();
       g.setColor("#f00");
       g.drawImage(atob("FhYBAAAAA/AAD8AAPwAc/OD/P8P8/x/z/n+/+P5/wP58A/nwP5/x/v/n/P+P8/w/z/Bz84APwAA/AAD8AAAAAA=="), this.x + 1, this.y + 1);
     }
   };
 
-  // Bottom medical alert message
-  WIDGETS["widmedabl"]={
+  // Bottom medical alert text
+  WIDGETS["widmedabl"] = {
     area: "bl",
-    width: Bangle.CLOCK?Bangle.appRect.w:0,
-    draw: function() {
+    width: Bangle.CLOCK ? Bangle.appRect.w : 0,
+    draw: function () {
       // Only show the widget on clocks
       if (!Bangle.CLOCK) return;
 
       g.reset();
       g.setBgColor(g.theme.dark ? "#fff" : "#f00");
       g.setColor(g.theme.dark ? "#f00" : "#fff");
-      g.setFont("Vector",18);
-      g.setFontAlign(0,0);
+      g.setFont("Vector", 16);
+      g.setFontAlign(0, 0);
       g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23);
-      g.drawString("MEDICAL ALERT", this.width / 2, this.y + ( 23 / 2 ));
+
+      const alertText = getAlertText();
+      g.drawString(alertText, this.width / 2, this.y + (23 / 2));
     }
   };
+
+  Bangle.on("touch", (_, c) => {
+    const bl = WIDGETS.widmedabl;
+    const tr = WIDGETS.widmedatr;
+    if ((bl && c.x >= bl.x && c.x < bl.x + bl.width && c.y >= bl.y && c.y <= bl.y + 24)
+      || (tr && c.x >= tr.x && c.x < tr.x + tr.width && c.y >= tr.y && c.y <= tr.y + 24)) {
+      load("medicalinfo.app.js");
+    }
+  });
 })();

From 0a22df94b2dc9932b4304eba80582fae07e5f10a Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 16:11:22 +0100
Subject: [PATCH 034/228] bthrm - Use increasing timeouts if device not found

---
 apps/bthrm/lib.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js
index dc3578453..652d9ec04 100644
--- a/apps/bthrm/lib.js
+++ b/apps/bthrm/lib.js
@@ -280,7 +280,11 @@ exports.enable = () => {
       log("Disconnect: " + reason);
       log("GATT", gatt);
       log("Characteristics", characteristics);
-      clearRetryTimeout(reason != "Connection Timeout");
+      
+      var retryTimeResetNeeded = true;
+      retryTimeResetNeeded &= reason != "Connection Timeout";
+      retryTimeResetNeeded &= reason != "No device found matching filters";
+      clearRetryTimeout(retryTimeResetNeeded);
       supportedCharacteristics["0x2a37"].active = false;
       startFallback();
       blockInit = false;

From fdcbbefd6c0f92f0f3e46240d5ced09f8af86fb2 Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 18:53:23 +0100
Subject: [PATCH 035/228] bthrm - Unset timout variable

---
 apps/bthrm/lib.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js
index 652d9ec04..13e8b0383 100644
--- a/apps/bthrm/lib.js
+++ b/apps/bthrm/lib.js
@@ -109,6 +109,7 @@ exports.enable = () => {
           if (supportedCharacteristics["0x2a37"].active) stopFallback();
           if (bpmTimeout) clearTimeout(bpmTimeout);
           bpmTimeout = setTimeout(()=>{
+            bpmTimeout = undefined;
             supportedCharacteristics["0x2a37"].active = false;
             startFallback();
           }, 3000);

From 02b5f3be5809bdf29c74396522faf88597baf25b Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 18:59:16 +0100
Subject: [PATCH 036/228] remove watcher for BTN1

---
 apps/dtlaunch/app-b2.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 8bbd9ed5c..e6620a50b 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -13,7 +13,7 @@ let settings = Object.assign({
 }, require('Storage').readJSON("dtlaunch.json", true) || {});
 
 if( settings.oneClickExit)
-  setWatch(_=> returnToClock, BTN1);
+  let buttonWatch = setWatch(_=> returnToClock, BTN1);
   
 let s = require("Storage");
   var apps = s.list(/\.info$/).map(app=>{
@@ -140,6 +140,7 @@ Bangle.on("touch",touchListenerDt);
   
 const returnToClock = function() {
   Bangle.setUI();
+  clearWatch(buttonWatch);
   delete s;
   delete a;
   delete n;

From eca777c8ecc819a506905b64127142d52074bac9 Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:03:51 +0100
Subject: [PATCH 037/228] fix

---
 apps/dtlaunch/app-b2.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index e6620a50b..1d36156bc 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -12,8 +12,7 @@ let settings = Object.assign({
   swipeExit: false,
 }, require('Storage').readJSON("dtlaunch.json", true) || {});
 
-if( settings.oneClickExit)
-  let buttonWatch = setWatch(_=> returnToClock, BTN1);
+if (settings.oneClickExit) let buttonWatch = setWatch(_=> returnToClock, BTN1);
   
 let s = require("Storage");
   var apps = s.list(/\.info$/).map(app=>{

From 5e2819bedcb7aed55cc3d143320dd21da70cf53d Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:07:01 +0100
Subject: [PATCH 038/228] new try at fix

---
 apps/dtlaunch/app-b2.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 1d36156bc..6b7d0b61b 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -12,8 +12,10 @@ let settings = Object.assign({
   swipeExit: false,
 }, require('Storage').readJSON("dtlaunch.json", true) || {});
 
-if (settings.oneClickExit) let buttonWatch = setWatch(_=> returnToClock, BTN1);
-  
+if (settings.oneClickExit) {
+  let buttonWatch = setWatch(_=> returnToClock, BTN1);
+} 
+
 let s = require("Storage");
   var apps = s.list(/\.info$/).map(app=>{
   let a=s.readJSON(app,1);

From 425675417819a0960668547c1df0572313e46d40 Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 15:55:35 +0100
Subject: [PATCH 039/228] bthrm - Bump version

---
 apps/bthrm/ChangeLog     | 4 ++++
 apps/bthrm/metadata.json | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog
index a70ae3f8a..c7b5a865f 100644
--- a/apps/bthrm/ChangeLog
+++ b/apps/bthrm/ChangeLog
@@ -30,3 +30,7 @@
       Allow recording unmodified internal HR
       Better connection retry handling
 0.13: Less time used during boot if disabled
+0.14: Allow bonding (Debug menu)
+      Prevent mixing of BT and internal HRM events if both are enabled
+      Always use a grace period (default 0 ms) to decouple some connection steps
+      Device not found errors now utilize increasing timeouts
diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json
index 7eedd223c..df0ac1fc1 100644
--- a/apps/bthrm/metadata.json
+++ b/apps/bthrm/metadata.json
@@ -2,7 +2,7 @@
   "id": "bthrm",
   "name": "Bluetooth Heart Rate Monitor",
   "shortName": "BT HRM",
-  "version": "0.13",
+  "version": "0.14",
   "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
   "icon": "app.png",
   "type": "app",

From 84837228f9c1ce8bf1316191d151fbe5f5179469 Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:21:22 +0100
Subject: [PATCH 040/228] fixfixfix

---
 apps/dtlaunch/app-b2.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 6b7d0b61b..aba09406f 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -13,7 +13,7 @@ let settings = Object.assign({
 }, require('Storage').readJSON("dtlaunch.json", true) || {});
 
 if (settings.oneClickExit) {
-  let buttonWatch = setWatch(_=> returnToClock, BTN1);
+  var buttonWatch = setWatch(_=> returnToClock, BTN1);
 } 
 
 let s = require("Storage");

From 9f17ea5c98c3854fac64429b589e8209035d541d Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 13:36:14 +0100
Subject: [PATCH 041/228] wid_edit - Access global variables explicitly

---
 apps/wid_edit/boot.js     | 10 +++++-----
 apps/wid_edit/settings.js | 32 ++++++++++++++++----------------
 2 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/apps/wid_edit/boot.js b/apps/wid_edit/boot.js
index 8dc120fd6..3cb545a34 100644
--- a/apps/wid_edit/boot.js
+++ b/apps/wid_edit/boot.js
@@ -7,14 +7,14 @@ Bangle.loadWidgets = (o => ()=>{
     // store defaults of customized values in _WIDGETS
     if (!global._WIDGETS) global._WIDGETS = {};
     if (!global._WIDGETS[w]) global._WIDGETS[w] = {};
-    Object.keys(c[w]).forEach(k => _WIDGETS[w][k] = WIDGETS[w][k]);
+    Object.keys(c[w]).forEach(k => global._WIDGETS[w][k] = global.WIDGETS[w][k]);
     //overide values in widget with configured ones
-    Object.assign(WIDGETS[w], c[w]);
+    Object.assign(global.WIDGETS[w], c[w]);
   }
-  const W = WIDGETS;
-  WIDGETS = {};
+  const W = global.WIDGETS;
+  global.WIDGETS = {};
   Object.keys(W)
     .sort()
     .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder))
-    .forEach(k => WIDGETS[k] = W[k]);
+    .forEach(k => global.WIDGETS[k] = W[k]);
 })(Bangle.loadWidgets);
diff --git a/apps/wid_edit/settings.js b/apps/wid_edit/settings.js
index 0969ed533..5f82cd211 100644
--- a/apps/wid_edit/settings.js
+++ b/apps/wid_edit/settings.js
@@ -9,7 +9,7 @@
 
   let cleanup = false;
   for (const id in settings.custom) {
-    if (!(id in WIDGETS)) {
+    if (!(id in global.WIDGETS)) {
       // widget which no longer exists
       cleanup = true;
       delete settings.custom[id];
@@ -24,12 +24,12 @@
    * Sort & redraw all widgets
    */
   function redrawWidgets() {
-    let W = WIDGETS;
+    let W = global.WIDGETS;
     global.WIDGETS = {};
     Object.keys(W)
       .sort()
       .sort((a, b) => (0|W[b].sortorder)-(0|W[a].sortorder))
-      .forEach(k => {WIDGETS[k] = W[k]});
+      .forEach(k => {global.WIDGETS[k] = W[k];});
     Bangle.drawWidgets();
   }
 
@@ -52,9 +52,9 @@
   }
 
   function edit(id) {
-    let WIDGET = WIDGETS[id],
+    let WIDGET = global.WIDGETS[id],
       def = {area: WIDGET.area, sortorder: WIDGET.sortorder|0}; // default values
-    Object.assign(def, _WIDGETS[id]||{}); // defaults were saved in _WIDGETS
+    Object.assign(def, global._WIDGETS[id]||{}); // defaults were saved in _WIDGETS
 
     settings.custom = settings.custom||{};
     let saved = settings.custom[id] || {},
@@ -102,7 +102,7 @@
         settings.custom = settings.custom || {};
         settings.custom[id] = saved;
       } else if (settings.custom) {
-         delete settings.custom[id]
+         delete settings.custom[id];
       }
       if (!Object.keys(settings.custom).length) delete settings.custom;
       require("Storage").writeJSON("wid_edit.json", settings);
@@ -112,8 +112,8 @@
       let _W = {};
       if (saved.area) _W.area = def.area;
       if ('sortorder' in saved) _W.sortorder = def.sortorder;
-      if (Object.keys(_W).length) _WIDGETS[id] = _W;
-      else delete _WIDGETS[id];
+      if (Object.keys(_W).length) global._WIDGETS[id] = _W;
+      else delete global._WIDGETS[id];
 
       // drawWidgets won't clear e.g. bottom bar if we just disabled the last bottom widget
       redrawWidgets();
@@ -149,7 +149,7 @@
         save();
         mainMenu(); // changing multiple values made the rest of the menu wrong, so take the easy out
       }
-    }
+    };
 
     let m = E.showMenu(menu);
   }
@@ -163,25 +163,25 @@
       if (!Object.keys(_WIDGETS).length) delete _WIDGETS; // no defaults to remember
       back();
     };
-    Object.keys(WIDGETS).forEach(id=>{
+    Object.keys(global.WIDGETS).forEach(id=>{
       // mark customized widgets with asterisk
-      menu[name(id)+((id in _WIDGETS) ? " *" : "")] = () => edit(id);
+      menu[name(id)+((id in global._WIDGETS) ? " *" : "")] = () => edit(id);
     });
-    if (Object.keys(_WIDGETS).length) { // only show reset if there is anything to reset
+    if (Object.keys(global._WIDGETS).length) { // only show reset if there is anything to reset
       menu[/*LANG*/"Reset All"] = () => {
         E.showPrompt(/*LANG*/"Reset all widgets?").then(confirm => {
           if (confirm) {
             delete settings.custom;
             require("Storage").writeJSON("wid_edit.json", settings);
-            for(let id in _WIDGETS) {
-              Object.assign(WIDGETS[id], _WIDGETS[id]) // restore defaults
+            for(let id in global._WIDGETS) {
+              Object.assign(global.WIDGETS[id], global._WIDGETS[id]); // restore defaults
             }
             global._WIDGETS = {};
             redrawWidgets();
           }
           mainMenu(); // reload with reset widgets
-        })
-      }
+        });
+      };
     }
 
     E.showMenu(menu);

From 53661fd11ce6163e9ddf46a209c340d5659b5b91 Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 13:37:10 +0100
Subject: [PATCH 042/228] wid_edit - Adds back functions as option

---
 apps/wid_edit/settings.js | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/apps/wid_edit/settings.js b/apps/wid_edit/settings.js
index 5f82cd211..6ca9b35c9 100644
--- a/apps/wid_edit/settings.js
+++ b/apps/wid_edit/settings.js
@@ -123,11 +123,11 @@
     }
 
     const menu = {
-      "": {"title": name(id)},
-      /*LANG*/"< Back": () => {
-        redrawWidgets();
-        mainMenu();
-      },
+      "": {"title": name(id),
+        back: () => {
+          redrawWidgets();
+          mainMenu();
+        } },
       /*LANG*/"Side": {
         value: (area === 'tl'),
         format: tl => tl ? /*LANG*/"Left" : /*LANG*/"Right",
@@ -157,11 +157,12 @@
 
   function mainMenu() {
     let menu = {
-      "": {"title": /*LANG*/"Widgets"},
-    };
-    menu[/*LANG*/"< Back"] = ()=>{
-      if (!Object.keys(_WIDGETS).length) delete _WIDGETS; // no defaults to remember
-      back();
+      "": {
+        "title": /*LANG*/"Widgets",
+        back: ()=>{
+          if (!Object.keys(global._WIDGETS).length) delete global._WIDGETS; // no defaults to remember
+          back();
+        }},
     };
     Object.keys(global.WIDGETS).forEach(id=>{
       // mark customized widgets with asterisk

From fa35629f451fbd2246b90f09c99e5dbf2221c44c Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 19:37:25 +0100
Subject: [PATCH 043/228] wid_edit - Allow setting all areas

---
 apps/wid_edit/settings.js | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/apps/wid_edit/settings.js b/apps/wid_edit/settings.js
index 6ca9b35c9..1d34ae0ca 100644
--- a/apps/wid_edit/settings.js
+++ b/apps/wid_edit/settings.js
@@ -122,17 +122,23 @@
       m.draw();
     }
 
+    const AREA_NAMES = [ "Top left", "Top right", "Bottom left", "Bottom right" ];
+    const AREAS = [ "tl", "tr", "bl", "br" ];
+
     const menu = {
       "": {"title": name(id),
         back: () => {
           redrawWidgets();
           mainMenu();
         } },
-      /*LANG*/"Side": {
-        value: (area === 'tl'),
-        format: tl => tl ? /*LANG*/"Left" : /*LANG*/"Right",
-        onchange: tl => {
-          area = tl ? "tl" : "tr";
+      /*LANG*/"Position": {
+        value: AREAS.indexOf(area),
+        format: v => AREA_NAMES[v],
+        min: 0,
+        max: AREAS.length - 1,
+        onchange: v => {
+          print("v", v);
+          area = AREAS[v];
           save();
         }
       },

From 0c7513335012ae5ac24c29ec6528d24e65bce15b Mon Sep 17 00:00:00 2001
From: Martin Boonk 
Date: Sun, 6 Nov 2022 13:41:33 +0100
Subject: [PATCH 044/228] wid_edit - Bump version

---
 apps/wid_edit/ChangeLog     | 5 ++++-
 apps/wid_edit/metadata.json | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/apps/wid_edit/ChangeLog b/apps/wid_edit/ChangeLog
index 2fa857bd8..d8e165029 100644
--- a/apps/wid_edit/ChangeLog
+++ b/apps/wid_edit/ChangeLog
@@ -1 +1,4 @@
-0.01: new Widget Editor!
\ No newline at end of file
+0.01: new Widget Editor!
+0.02: Wrap loadWidgets instead of replacing to keep original functionality intact
+      Change back entry to menu option
+      Allow changing widgets into all areas, including bottom widget bar
diff --git a/apps/wid_edit/metadata.json b/apps/wid_edit/metadata.json
index 66d0192f6..d963a53d0 100644
--- a/apps/wid_edit/metadata.json
+++ b/apps/wid_edit/metadata.json
@@ -1,6 +1,6 @@
 {
   "id": "wid_edit",
-  "version": "0.01",
+  "version": "0.02",
   "name": "Widget Editor",
   "icon": "icon.png",
   "description": "Customize widget locations",

From a46c76a2ffb749d2e8c153e0fce791df01e2ef6c Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:53:28 +0100
Subject: [PATCH 045/228] timeout to clock copied from Icon Launcher

---
 apps/dtlaunch/app-b2.js      | 353 ++++++++++++++++++-----------------
 apps/dtlaunch/settings-b2.js |  29 ++-
 2 files changed, 202 insertions(+), 180 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index aba09406f..151be96c4 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -1,180 +1,189 @@
 { // must be inside our own scope here so that when we are unloaded everything disappears
-
-/* Desktop launcher
-*
-*/
-
-let settings = Object.assign({
-  showClocks: true,
-  showLaunchers: true,
-  direct: false,
-  oneClickExit:false,
-  swipeExit: false,
-}, require('Storage').readJSON("dtlaunch.json", true) || {});
-
-if (settings.oneClickExit) {
-  var buttonWatch = setWatch(_=> returnToClock, BTN1);
-} 
-
-let s = require("Storage");
-  var apps = s.list(/\.info$/).map(app=>{
-  let a=s.readJSON(app,1);
-  return a && {
-    name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
-  };}).filter(
-    app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
   
-apps.sort((a,b)=>{
-  let n=(0|a.sortorder)-(0|b.sortorder);
-  if (n) return n; // do sortorder first
-  if (a.nameb.name) return 1;
-  return 0;
-});
-apps.forEach(app=>{
-    if (app.icon)
-      app.icon = s.read(app.icon); // should just be a link to a memory area
+  /* Desktop launcher
+  *
+  */
+  
+  let settings = Object.assign({
+    showClocks: true,
+    showLaunchers: true,
+    direct: false,
+    oneClickExit: false,
+    swipeExit: false,
+    timeOut: "Off"
+  }, require('Storage').readJSON("dtlaunch.json", true) || {});
+  
+  if (settings.oneClickExit) {
+    var buttonWatch = setWatch(_=> returnToClock, BTN1);
+  } 
+  
+  // taken from Icon Launcher with minor alterations
+  if (settings.timeOut!="Off"){
+    let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
+    var timeoutToClock = setTimeout(returnToClock,time*1000);  
+  }
+  let s = require("Storage");
+    var apps = s.list(/\.info$/).map(app=>{
+    let a=s.readJSON(app,1);
+    return a && {
+      name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
+    };}).filter(
+      app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
+    
+  apps.sort((a,b)=>{
+    let n=(0|a.sortorder)-(0|b.sortorder);
+    if (n) return n; // do sortorder first
+    if (a.nameb.name) return 1;
+    return 0;
   });
-
-let Napps = apps.length;
-let Npages = Math.ceil(Napps/4);
-let maxPage = Npages-1;
-let selected = -1;
-let oldselected = -1;
-let page = 0;
-const XOFF = 24;
-const YOFF = 30;
-
-let drawIcon= function(p,n,selected) {
-    let x = (n%2)*72+XOFF; 
-    let y = n>1?72+YOFF:YOFF;
-    (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
-    g.clearRect(x+12,y+4,x+59,y+51);
-    g.setColor(g.theme.fg);
-    try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
-    g.setFontAlign(0,-1,0).setFont("6x8",1);
-    let txt =  apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
-    let lineY = 0;
-    let line = "";
-    while (txt.length > 0){
-      let c = txt.shift();
-      if (c.length + 1 + line.length > 13){
-        if (line.length > 0){
-          g.drawString(line.trim(),x+36,y+54+lineY*8);
-          lineY++;
+  apps.forEach(app=>{
+      if (app.icon)
+        app.icon = s.read(app.icon); // should just be a link to a memory area
+    });
+  
+  let Napps = apps.length;
+  let Npages = Math.ceil(Napps/4);
+  let maxPage = Npages-1;
+  let selected = -1;
+  let oldselected = -1;
+  let page = 0;
+  const XOFF = 24;
+  const YOFF = 30;
+  
+  let drawIcon= function(p,n,selected) {
+      let x = (n%2)*72+XOFF; 
+      let y = n>1?72+YOFF:YOFF;
+      (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
+      g.clearRect(x+12,y+4,x+59,y+51);
+      g.setColor(g.theme.fg);
+      try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
+      g.setFontAlign(0,-1,0).setFont("6x8",1);
+      let txt =  apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
+      let lineY = 0;
+      let line = "";
+      while (txt.length > 0){
+        let c = txt.shift();
+        if (c.length + 1 + line.length > 13){
+          if (line.length > 0){
+            g.drawString(line.trim(),x+36,y+54+lineY*8);
+            lineY++;
+          }
+          line = c;
+        } else {
+          line += " " + c;
         }
-        line = c;
-      } else {
-        line += " " + c;
       }
-    }
-    g.drawString(line.trim(),x+36,y+54+lineY*8);
-};
-
-let drawPage = function(p){
-    g.reset();
-    g.clearRect(0,24,175,175);
-    let O = 88+YOFF/2-12*(Npages/2);
-    for (let j=0;jmaxPage) page=0;
-        drawPage(page);
-    } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
-        --page; if (page<0) page=maxPage;
-        drawPage(page);
-    }
-};
-Bangle.on("swipe",swipeListenerDt);
-
-let isTouched = function(p,n){
-    if (n<0 || n>3) return false;
-    let x1 = (n%2)*72+XOFF;  let y1 =  n>1?72+YOFF:YOFF;
-    let x2 = x1+71; let y2 = y1+81;
-    return (p.x>x1 && p.y>y1 && p.x=0 || settings.direct) {
-                    if (selected!=i && !settings.direct){
-                        drawIcon(page,selected,false);
-                    } else {
-                        load(apps[page*4+i].src);
-                    }
-                }
-                selected=i;
-                break;
-            }
-        }
-    }
-    if ((i==4 || (page*4+i)>Napps) && selected>=0) {
-        drawIcon(page,selected,false);
-        selected=-1;
-    }
-};
-Bangle.on("touch",touchListenerDt);
+  let drawPage = function(p){
+      g.reset();
+      g.clearRect(0,24,175,175);
+      let O = 88+YOFF/2-12*(Npages/2);
+      for (let j=0;jmaxPage) page=0;
+          drawPage(page);
+      } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
+          --page; if (page<0) page=maxPage;
+          drawPage(page);
+      }
+  };
+  Bangle.on("swipe",swipeListenerDt);
+  
+  let isTouched = function(p,n){
+      if (n<0 || n>3) return false;
+      let x1 = (n%2)*72+XOFF;  let y1 =  n>1?72+YOFF:YOFF;
+      let x2 = x1+71; let y2 = y1+81;
+      return (p.x>x1 && p.y>y1 && p.x=0 || settings.direct) {
+                      if (selected!=i && !settings.direct){
+                          drawIcon(page,selected,false);
+                      } else {
+                          load(apps[page*4+i].src);
+                      }
+                  }
+                  selected=i;
+                  break;
+              }
+          }
+      }
+      if ((i==4 || (page*4+i)>Napps) && selected>=0) {
+          drawIcon(page,selected,false);
+          selected=-1;
+      }
+  };
+  Bangle.on("touch",touchListenerDt);
+    
+  const returnToClock = function() {
+    Bangle.setUI();
+    clearWatch(buttonWatch);
+    delete buttonWatch;
+    clearTimeout(timeoutToClock);
+    delete timeoutToClock;
+    delete s;
+    delete a;
+    delete n;
+    delete Napps;
+    delete Npages;
+    delete maxPage;
+    delete selected;
+    delete oldselected;
+    delete page;
+    delete XOFF;
+    delete YOFF;
+    delete x;
+    delete y;
+    delete txt;
+    delete lineY;
+    delete line;
+    delete c;
+    delete O;
+    delete x1;
+    delete x2;
+    delete i;
+    delete drawIcon;
+    delete drawPage;
+    delete isTouched;
+    Bangle.removeListener("swipe", swipeListenerDt);
+    delete swipeListenerDt;
+    Bangle.removeListener("touch", touchListenerDt);
+    delete touchListenerDt;
+    var apps = [];
+    delete apps;
+    delete returnToClock;
+    delete settings;
+    setTimeout(eval, 0, s.read(".bootcde"));
+  };
   
-const returnToClock = function() {
-  Bangle.setUI();
-  clearWatch(buttonWatch);
-  delete s;
-  delete a;
-  delete n;
-  delete Napps;
-  delete Npages;
-  delete maxPage;
-  delete selected;
-  delete oldselected;
-  delete page;
-  delete XOFF;
-  delete YOFF;
-  delete x;
-  delete y;
-  delete txt;
-  delete lineY;
-  delete line;
-  delete c;
-  delete O;
-  delete x1;
-  delete x2;
-  delete i;
-  delete drawIcon;
-  delete drawPage;
-  delete isTouched;
-  Bangle.removeListener("swipe", swipeListenerDt);
-  delete swipeListenerDt;
-  Bangle.removeListener("touch", touchListenerDt);
-  delete touchListenerDt;
-  var apps = [];
-  delete apps;
-  delete returnToClock;
-  delete settings;
-  setTimeout(eval, 0, s.read(".bootcde"));
-};
-
 } // end of app scope
diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js
index fac9c0fff..315974c77 100644
--- a/apps/dtlaunch/settings-b2.js
+++ b/apps/dtlaunch/settings-b2.js
@@ -6,50 +6,63 @@
     showLaunchers: true,
     direct: false,
     oneClickExit:false,
-    swipeExit: false
+    swipeExit: false,
+    timeOut: "Off"
   }, require('Storage').readJSON(FILE, true) || {});
 
   function writeSettings() {
     require('Storage').writeJSON(FILE, settings);
   }
 
+  const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
+
   E.showMenu({
     "" : { "title" : "Desktop launcher" },
-    "< Back" : () => back(),
-    'Show clocks': {
+    /*LANG*/"< Back" : () => back(),
+    /*LANG*/'Show clocks': {
       value: settings.showClocks,
       onchange: v => {
         settings.showClocks = v;
         writeSettings();
       }
     },
-    'Show launchers': {
+    /*LANG*/'Show launchers': {
       value: settings.showLaunchers,
       onchange: v => {
         settings.showLaunchers = v;
         writeSettings();
       }
     },
-    'Direct launch': {
+    /*LANG*/'Direct launch': {
       value: settings.direct,
       onchange: v => {
         settings.direct = v;
         writeSettings();
       }
     },
-    'Swipe Exit': {
+    /*LANG*/'Swipe Exit': {
       value: settings.swipeExit,
       onchange: v => {
         settings.swipeExit = v;
         writeSettings();
       }
     },
-    'One click exit': {
+    /*LANG*/'One click exit': {
       value: settings.oneClickExit,
       onchange: v => {
         settings.oneClickExit = v;
         writeSettings();
       }
-    }
+    },
+    /*LANG*/'Time Out': {
+      value: timeOutChoices.indexOf(settings.timeOut),
+      min: 0,
+      max: timeOutChoices.length-1,
+      format: v => timeOutChoices[v],
+      onchange: m => {
+        settings.timeOut = m;
+        writeSettings();
+      }
+    },
   });
 })

From 8c427938fdddcdf4a7cd98174b651317705a6b1a Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:54:50 +0100
Subject: [PATCH 046/228] make the diff readable...

---
 apps/dtlaunch/app-b2.js | 362 ++++++++++++++++++++--------------------
 1 file changed, 181 insertions(+), 181 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 151be96c4..7c75f291b 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -1,189 +1,189 @@
 { // must be inside our own scope here so that when we are unloaded everything disappears
+
+/* Desktop launcher
+*
+*/
+
+let settings = Object.assign({
+  showClocks: true,
+  showLaunchers: true,
+  direct: false,
+  oneClickExit: false,
+  swipeExit: false,
+  timeOut: "Off"
+}, require('Storage').readJSON("dtlaunch.json", true) || {});
+
+if (settings.oneClickExit) {
+  var buttonWatch = setWatch(_=> returnToClock, BTN1);
+} 
+
+// taken from Icon Launcher with minor alterations
+if (settings.timeOut!="Off"){
+  let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
+  var timeoutToClock = setTimeout(returnToClock,time*1000);  
+}
+let s = require("Storage");
+  var apps = s.list(/\.info$/).map(app=>{
+  let a=s.readJSON(app,1);
+  return a && {
+    name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
+  };}).filter(
+    app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
   
-  /* Desktop launcher
-  *
-  */
-  
-  let settings = Object.assign({
-    showClocks: true,
-    showLaunchers: true,
-    direct: false,
-    oneClickExit: false,
-    swipeExit: false,
-    timeOut: "Off"
-  }, require('Storage').readJSON("dtlaunch.json", true) || {});
-  
-  if (settings.oneClickExit) {
-    var buttonWatch = setWatch(_=> returnToClock, BTN1);
-  } 
-  
-  // taken from Icon Launcher with minor alterations
-  if (settings.timeOut!="Off"){
-    let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
-    var timeoutToClock = setTimeout(returnToClock,time*1000);  
-  }
-  let s = require("Storage");
-    var apps = s.list(/\.info$/).map(app=>{
-    let a=s.readJSON(app,1);
-    return a && {
-      name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
-    };}).filter(
-      app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
-    
-  apps.sort((a,b)=>{
-    let n=(0|a.sortorder)-(0|b.sortorder);
-    if (n) return n; // do sortorder first
-    if (a.nameb.name) return 1;
-    return 0;
+apps.sort((a,b)=>{
+  let n=(0|a.sortorder)-(0|b.sortorder);
+  if (n) return n; // do sortorder first
+  if (a.nameb.name) return 1;
+  return 0;
+});
+apps.forEach(app=>{
+    if (app.icon)
+      app.icon = s.read(app.icon); // should just be a link to a memory area
   });
-  apps.forEach(app=>{
-      if (app.icon)
-        app.icon = s.read(app.icon); // should just be a link to a memory area
-    });
-  
-  let Napps = apps.length;
-  let Npages = Math.ceil(Napps/4);
-  let maxPage = Npages-1;
-  let selected = -1;
-  let oldselected = -1;
-  let page = 0;
-  const XOFF = 24;
-  const YOFF = 30;
-  
-  let drawIcon= function(p,n,selected) {
-      let x = (n%2)*72+XOFF; 
-      let y = n>1?72+YOFF:YOFF;
-      (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
-      g.clearRect(x+12,y+4,x+59,y+51);
-      g.setColor(g.theme.fg);
-      try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
-      g.setFontAlign(0,-1,0).setFont("6x8",1);
-      let txt =  apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
-      let lineY = 0;
-      let line = "";
-      while (txt.length > 0){
-        let c = txt.shift();
-        if (c.length + 1 + line.length > 13){
-          if (line.length > 0){
-            g.drawString(line.trim(),x+36,y+54+lineY*8);
-            lineY++;
-          }
-          line = c;
-        } else {
-          line += " " + c;
+
+let Napps = apps.length;
+let Npages = Math.ceil(Napps/4);
+let maxPage = Npages-1;
+let selected = -1;
+let oldselected = -1;
+let page = 0;
+const XOFF = 24;
+const YOFF = 30;
+
+let drawIcon= function(p,n,selected) {
+    let x = (n%2)*72+XOFF; 
+    let y = n>1?72+YOFF:YOFF;
+    (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
+    g.clearRect(x+12,y+4,x+59,y+51);
+    g.setColor(g.theme.fg);
+    try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
+    g.setFontAlign(0,-1,0).setFont("6x8",1);
+    let txt =  apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
+    let lineY = 0;
+    let line = "";
+    while (txt.length > 0){
+      let c = txt.shift();
+      if (c.length + 1 + line.length > 13){
+        if (line.length > 0){
+          g.drawString(line.trim(),x+36,y+54+lineY*8);
+          lineY++;
         }
+        line = c;
+      } else {
+        line += " " + c;
       }
-      g.drawString(line.trim(),x+36,y+54+lineY*8);
-  };
+    }
+    g.drawString(line.trim(),x+36,y+54+lineY*8);
+};
+
+let drawPage = function(p){
+    g.reset();
+    g.clearRect(0,24,175,175);
+    let O = 88+YOFF/2-12*(Npages/2);
+    for (let j=0;jmaxPage) page=0;
-          drawPage(page);
-      } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
-          --page; if (page<0) page=maxPage;
-          drawPage(page);
-      }
-  };
-  Bangle.on("swipe",swipeListenerDt);
-  
-  let isTouched = function(p,n){
-      if (n<0 || n>3) return false;
-      let x1 = (n%2)*72+XOFF;  let y1 =  n>1?72+YOFF:YOFF;
-      let x2 = x1+71; let y2 = y1+81;
-      return (p.x>x1 && p.y>y1 && p.x=0 || settings.direct) {
-                      if (selected!=i && !settings.direct){
-                          drawIcon(page,selected,false);
-                      } else {
-                          load(apps[page*4+i].src);
-                      }
-                  }
-                  selected=i;
-                  break;
-              }
-          }
-      }
-      if ((i==4 || (page*4+i)>Napps) && selected>=0) {
-          drawIcon(page,selected,false);
-          selected=-1;
-      }
-  };
-  Bangle.on("touch",touchListenerDt);
-    
-  const returnToClock = function() {
-    Bangle.setUI();
-    clearWatch(buttonWatch);
-    delete buttonWatch;
-    clearTimeout(timeoutToClock);
-    delete timeoutToClock;
-    delete s;
-    delete a;
-    delete n;
-    delete Napps;
-    delete Npages;
-    delete maxPage;
-    delete selected;
-    delete oldselected;
-    delete page;
-    delete XOFF;
-    delete YOFF;
-    delete x;
-    delete y;
-    delete txt;
-    delete lineY;
-    delete line;
-    delete c;
-    delete O;
-    delete x1;
-    delete x2;
-    delete i;
-    delete drawIcon;
-    delete drawPage;
-    delete isTouched;
-    Bangle.removeListener("swipe", swipeListenerDt);
-    delete swipeListenerDt;
-    Bangle.removeListener("touch", touchListenerDt);
-    delete touchListenerDt;
-    var apps = [];
-    delete apps;
-    delete returnToClock;
-    delete settings;
-    setTimeout(eval, 0, s.read(".bootcde"));
-  };
+Bangle.loadWidgets();
+//g.clear();
+Bangle.drawWidgets();
+drawPage(0);
+
+let swipeListenerDt = function(dirLeftRight, dirUpDown){
+    selected = 0;
+    oldselected=-1;
+    if(settings.swipeExit && dirLeftRight==1) returnToClock();
+    if (dirUpDown==-1||dirLeftRight==-1){
+        ++page; if (page>maxPage) page=0;
+        drawPage(page);
+    } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
+        --page; if (page<0) page=maxPage;
+        drawPage(page);
+    }
+};
+Bangle.on("swipe",swipeListenerDt);
+
+let isTouched = function(p,n){
+    if (n<0 || n>3) return false;
+    let x1 = (n%2)*72+XOFF;  let y1 =  n>1?72+YOFF:YOFF;
+    let x2 = x1+71; let y2 = y1+81;
+    return (p.x>x1 && p.y>y1 && p.x=0 || settings.direct) {
+                    if (selected!=i && !settings.direct){
+                        drawIcon(page,selected,false);
+                    } else {
+                        load(apps[page*4+i].src);
+                    }
+                }
+                selected=i;
+                break;
+            }
+        }
+    }
+    if ((i==4 || (page*4+i)>Napps) && selected>=0) {
+        drawIcon(page,selected,false);
+        selected=-1;
+    }
+};
+Bangle.on("touch",touchListenerDt);
   
+const returnToClock = function() {
+  Bangle.setUI();
+  clearWatch(buttonWatch);
+  delete buttonWatch;
+  clearTimeout(timeoutToClock);
+  delete timeoutToClock;
+  delete s;
+  delete a;
+  delete n;
+  delete Napps;
+  delete Npages;
+  delete maxPage;
+  delete selected;
+  delete oldselected;
+  delete page;
+  delete XOFF;
+  delete YOFF;
+  delete x;
+  delete y;
+  delete txt;
+  delete lineY;
+  delete line;
+  delete c;
+  delete O;
+  delete x1;
+  delete x2;
+  delete i;
+  delete drawIcon;
+  delete drawPage;
+  delete isTouched;
+  Bangle.removeListener("swipe", swipeListenerDt);
+  delete swipeListenerDt;
+  Bangle.removeListener("touch", touchListenerDt);
+  delete touchListenerDt;
+  var apps = [];
+  delete apps;
+  delete returnToClock;
+  delete settings;
+  setTimeout(eval, 0, s.read(".bootcde"));
+};
+
 } // end of app scope

From 479f0e3d6954be4ba9cad29dce12a72819ce0431 Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 19:59:04 +0100
Subject: [PATCH 047/228] comment in settings-b2.js

---
 apps/dtlaunch/settings-b2.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js
index 315974c77..6d4c98c38 100644
--- a/apps/dtlaunch/settings-b2.js
+++ b/apps/dtlaunch/settings-b2.js
@@ -54,7 +54,7 @@
         writeSettings();
       }
     },
-    /*LANG*/'Time Out': {
+    /*LANG*/'Time Out': { // Adapted from Icon Launcher
       value: timeOutChoices.indexOf(settings.timeOut),
       min: 0,
       max: timeOutChoices.length-1,

From 675953ddcdc414483d74668bcdf00da5396163ce Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 20:20:51 +0100
Subject: [PATCH 048/228] make timeout to clock work

---
 apps/dtlaunch/app-b2.js      | 13 +++++++------
 apps/dtlaunch/settings-b2.js |  6 +++---
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 7c75f291b..80ee36eca 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -15,13 +15,8 @@ let settings = Object.assign({
 
 if (settings.oneClickExit) {
   var buttonWatch = setWatch(_=> returnToClock, BTN1);
-} 
-
-// taken from Icon Launcher with minor alterations
-if (settings.timeOut!="Off"){
-  let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
-  var timeoutToClock = setTimeout(returnToClock,time*1000);  
 }
+
 let s = require("Storage");
   var apps = s.list(/\.info$/).map(app=>{
   let a=s.readJSON(app,1);
@@ -186,4 +181,10 @@ const returnToClock = function() {
   setTimeout(eval, 0, s.read(".bootcde"));
 };
 
+// taken from Icon Launcher with minor alterations
+if (settings.timeOut!="Off"){
+  let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
+  var timeoutToClock = setTimeout(returnToClock,time*1000);  
+}
+
 } // end of app scope
diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js
index 6d4c98c38..80ad0414a 100644
--- a/apps/dtlaunch/settings-b2.js
+++ b/apps/dtlaunch/settings-b2.js
@@ -59,10 +59,10 @@
       min: 0,
       max: timeOutChoices.length-1,
       format: v => timeOutChoices[v],
-      onchange: m => {
-        settings.timeOut = m;
+      onchange: v => {
+        settings.timeOut = timeOutChoices[v];
         writeSettings();
       }
     },
   });
-})
+});

From 23a0c2fc2f6881de4aa11aec3febeb7cf8d39f83 Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 20:25:36 +0100
Subject: [PATCH 049/228] add to changelog

---
 apps/dtlaunch/ChangeLog | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog
index 05a1637c0..1054aa387 100644
--- a/apps/dtlaunch/ChangeLog
+++ b/apps/dtlaunch/ChangeLog
@@ -14,4 +14,5 @@
 0.14: Don't move pages when doing exit swipe - Bangle 2.
 0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
 0.16: Use default Bangle formatter for booleans
-0.17: Bangle 2: Fast loading on exit to clock face
+0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
+clock face by timeout.

From ef1cf7ef7b3d2eecf11ea701fe2fdc82fef7ca5b Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 21:27:40 +0100
Subject: [PATCH 050/228] update timeout to clock per new implementation in
 Icon Launcher

---
 apps/dtlaunch/app-b2.js | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 80ee36eca..11ef7ba5f 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -24,7 +24,7 @@ let s = require("Storage");
     name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
   };}).filter(
     app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
-  
+
 apps.sort((a,b)=>{
   let n=(0|a.sortorder)-(0|b.sortorder);
   if (n) return n; // do sortorder first
@@ -88,7 +88,7 @@ let drawPage = function(p){
     }
     g.flip();
 };
-  
+
 Bangle.loadWidgets();
 //g.clear();
 Bangle.drawWidgets();
@@ -139,7 +139,7 @@ let touchListenerDt = function(_,p){
     }
 };
 Bangle.on("touch",touchListenerDt);
-  
+
 const returnToClock = function() {
   Bangle.setUI();
   clearWatch(buttonWatch);
@@ -182,9 +182,14 @@ const returnToClock = function() {
 };
 
 // taken from Icon Launcher with minor alterations
-if (settings.timeOut!="Off"){
-  let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
-  var timeoutToClock = setTimeout(returnToClock,time*1000);  
-}
+var timeoutToClock;
+const updateTimeoutToClock = function(){
+  if (settings.timeOut!="Off"){
+    let time=parseInt(settings.timeOut);  //the "s" will be trimmed by the parseInt
+    if (timeoutToClock) clearTimeout(timeoutToClock);
+    timeoutToClock = setTimeout(returnToClock,time*1000);  
+  }
+};
+updateTimeoutToClock();
 
 } // end of app scope

From e0073bd17f355e44a7113ec060b287d352d5d1f6 Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Sun, 6 Nov 2022 21:53:41 +0100
Subject: [PATCH 051/228] remove some delete statements, make BTN1 to clock
 work

---
 apps/dtlaunch/app-b2.js | 41 +++++++++--------------------------------
 1 file changed, 9 insertions(+), 32 deletions(-)

diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 11ef7ba5f..48d9b2f38 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -14,7 +14,7 @@ let settings = Object.assign({
 }, require('Storage').readJSON("dtlaunch.json", true) || {});
 
 if (settings.oneClickExit) {
-  var buttonWatch = setWatch(_=> returnToClock, BTN1);
+  var buttonWatch = setWatch(_=> returnToClock(), BTN1, {edge: 'falling'});
 }
 
 let s = require("Storage");
@@ -142,42 +142,19 @@ Bangle.on("touch",touchListenerDt);
 
 const returnToClock = function() {
   Bangle.setUI();
-  clearWatch(buttonWatch);
-  delete buttonWatch;
-  clearTimeout(timeoutToClock);
-  delete timeoutToClock;
-  delete s;
-  delete a;
-  delete n;
-  delete Napps;
-  delete Npages;
-  delete maxPage;
-  delete selected;
-  delete oldselected;
-  delete page;
-  delete XOFF;
-  delete YOFF;
-  delete x;
-  delete y;
-  delete txt;
-  delete lineY;
-  delete line;
-  delete c;
-  delete O;
-  delete x1;
-  delete x2;
-  delete i;
-  delete drawIcon;
-  delete drawPage;
-  delete isTouched;
+  if (buttonWatch) {
+    clearWatch(buttonWatch);
+    delete buttonWatch;
+  }
+  if (timeoutToClock) {
+    clearTimeout(timeoutToClock);
+    delete timeoutToClock;
+  }
   Bangle.removeListener("swipe", swipeListenerDt);
-  delete swipeListenerDt;
   Bangle.removeListener("touch", touchListenerDt);
-  delete touchListenerDt;
   var apps = [];
   delete apps;
   delete returnToClock;
-  delete settings;
   setTimeout(eval, 0, s.read(".bootcde"));
 };
 

From 351ce5f891b7d4db87a7360f2c9e2592537a1dca Mon Sep 17 00:00:00 2001
From: thyttan <6uuxstm66@mozmail.com⁩>
Date: Mon, 7 Nov 2022 00:07:27 +0100
Subject: [PATCH 052/228] exit by pressing upper left corner of screen

---
 apps/calculator/ChangeLog     | 1 +
 apps/calculator/README.md     | 8 ++++++++
 apps/calculator/app.js        | 4 ++++
 apps/calculator/metadata.json | 2 +-
 4 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog
index f3ce3a928..2e1ace7bf 100644
--- a/apps/calculator/ChangeLog
+++ b/apps/calculator/ChangeLog
@@ -4,3 +4,4 @@
 0.04: Display current operation on LHS
 0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
 0.06: Bangle.js 2: Exit with a short press of the physical button
+0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
diff --git a/apps/calculator/README.md b/apps/calculator/README.md
index b25d355bf..8681965f2 100644
--- a/apps/calculator/README.md
+++ b/apps/calculator/README.md
@@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus.
 
 ## Controls
 
+Bangle.js 1
 - UP: BTN1
 - DOWN: BTN3
 - LEFT: BTN4
 - RIGHT: BTN5
 - SELECT: BTN2
 
+Bangle.js 2
+- Swipes to change visible buttons
+- Click physical button exit
+- Press upper left corner of screen to exit (where the red back button would be)
 ## Creator
 
 
+
+## Contributors
+[thyttan](https://github.com/thyttan)
diff --git a/apps/calculator/app.js b/apps/calculator/app.js
index 571a5b27f..f31b591ad 100644
--- a/apps/calculator/app.js
+++ b/apps/calculator/app.js
@@ -402,6 +402,10 @@ if (process.env.HWVERSION==1) {
   swipeEnabled = false;
   drawGlobal();
 } else { // touchscreen?
+  Bangle.setUI({ // Pressing upper left corner turns off (where red back button would be)
+    mode : 'custom',
+    back : load
+  });
   setWatch(_ => {load();}, BTN1, {edge:'falling'}); // Exit with a short press to physical button
   selected = "NONE";
   swipeEnabled = true;
diff --git a/apps/calculator/metadata.json b/apps/calculator/metadata.json
index 536a6955e..1674b7843 100644
--- a/apps/calculator/metadata.json
+++ b/apps/calculator/metadata.json
@@ -2,7 +2,7 @@
   "id": "calculator",
   "name": "Calculator",
   "shortName": "Calculator",
-  "version": "0.06",
+  "version": "0.07",
   "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
   "icon": "calculator.png",
   "screenshots": [{"url":"screenshot_calculator.png"}],

From 1ad66dd16ef58558418ad5cce7effcae91126c6f Mon Sep 17 00:00:00 2001
From: Gordon Williams 
Date: Mon, 7 Nov 2022 09:22:36 +0000
Subject: [PATCH 053/228] Misc tweaks - and linking to privacy policy for
 collected data

---
 bin/firmwaremaker_c.js | 2 +-
 core                   | 2 +-
 index.html             | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js
index 87065dab4..e56e28ad9 100755
--- a/bin/firmwaremaker_c.js
+++ b/bin/firmwaremaker_c.js
@@ -172,7 +172,7 @@ Promise.all(APPS.map(appid => {
 // Generated by BangleApps/bin/build_bangles_c.js
 
 const int jsfStorageInitialContentLength = ${storageContent.length};
-const char jsfStorageInitialContents[] = {
+const unsigned char jsfStorageInitialContents[] = {
 `;
 for (var i=0;i
           
           
+ Send app analytics to banglejs.com +
- Send app analytics to banglejs.com + Send favourite and installed apps to banglejs.com
+ For 'Sort by Installed/Favourited' functionality
`; showPrompt("Which Bangle.js?",html,{},false); + var usageStats = document.getElementById("usage_stats"); + usageStats.addEventListener("change",event=>{ + console.log("Send Usage Stats "+(event.target.checked?"on":"off")); + SETTINGS.sendUsageStats = event.target.checked; + saveSettings(); + }); htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { button.addEventListener("click",event => { let rememberDevice = !!document.getElementById("remember_device").checked; - SETTINGS.sendUsageStats = !!document.getElementById("usage_stats").checked; - let button = event.currentTarget; let deviceId = button.getAttribute("deviceid"); hidePrompt(); From b3ba5d828f196c58c42471deab5e9517a1726365 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 7 Nov 2022 14:45:33 +0000 Subject: [PATCH 060/228] link in priv policy --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index c0cd15de5..9b337223b 100644 --- a/loader.js +++ b/loader.js @@ -162,7 +162,7 @@ window.addEventListener('load', (event) => { +