diff --git a/.eslintignore b/.eslintignore index 7bbe41136..fcbea07f9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ apps/animclk/V29.LBM.js apps/banglerun/rollup.config.js apps/schoolCalendar/fullcalendar/main.js apps/authentiwatch/qr_packed.js +apps/qrcode/qr-scanner.umd.min.js *.test.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..1eb009153 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,34 @@ +name: Node CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: install testing dependencies + run: npm i + - name: test all apps and widgets + run: npm run test + - name: install typescript dependencies + working-directory: ./typescript + run: npm ci + - name: build types + working-directory: ./typescript + run: npm run build:types + - name: build all TS apps and widgets + working-directory: ./typescript + run: npm run build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f3d0d2159..000000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: node_js -node_js: - - "node" diff --git a/apps/7x7dotsclock/7x7dotsclock.app.js b/apps/7x7dotsclock/7x7dotsclock.app.js index 3f2e9b9b1..aa174b2d2 100644 --- a/apps/7x7dotsclock/7x7dotsclock.app.js +++ b/apps/7x7dotsclock/7x7dotsclock.app.js @@ -5,7 +5,7 @@ by Peter Kuppelwieser */ -let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "", ColorMinutes: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); // position on screen var Xs = 0, Ys = 30,Xe = 175, Ye=175; @@ -13,9 +13,33 @@ var Xs = 0, Ys = 30,Xe = 175, Ye=175; var SegH = (Ye-Ys)/2,SegW = (Xe-Xs)/2; var Dx = SegW/14, Dy = SegH/16; -const hColor = [1,1,1]; -const mColor = [0.3,0.3,1]; -const bColor = [0.2,0.2,0.2]; +switch(settings.ColorMinutes) { +case "blue": + var mColor = [0.3,0.3,1]; + var sColor = [0,0,1]; + var sbColor = [1,1,1]; + break; +case "pink": + var mColor = [1,0.3,1]; + var sColor = [1,0,1]; + var sbColor = [1,1,1]; + break; +case "green": + var mColor = [0.3,1,0.3]; + var sColor = [0,1,0]; + var sbColor = [1,1,1]; + break; +case "yellow": + var mColor = [1,1,0.3]; + var sColor = [1,1,0]; + var sbColor = [0,0,0]; + break; +default: + var sColor = [0,0,1]; + var mColor = [0.3,0.3,1]; + var sbColor = [1,1,1]; +} +const bColor = [0.3,0.3,0.3]; const Font = [ [ @@ -114,16 +138,22 @@ const Font = [ var dho = -1, eho = -1, dmo = -1, emo = -1; -function drawHSeg(x1,y1,x2,y2,Num,dColor,Size) { - g.setColor(0,0,0); +function drawHSeg(x1,y1,x2,y2,Num,Color,Size) { + + + g.setColor(g.theme.bg); g.fillRect(x1, y1, x2, y2); for (let i = 1; i < 8; i++) { for (let j = 1; j < 8; j++) { if (Font[Num][j-1][i-1] == 1) { - g.setColor(dColor[0],dColor[1],dColor[2]); + if (Color == "fg") { + g.setColor(g.theme.fg); + } else { + g.setColor(mColor[0],mColor[1],mColor[2]); + } g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,Size); } else { - g.setColor(bColor[0],bColor[1],bColor[2]); + g.setColor(bColor[0],bColor[1],bColor[2]); g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1); } } @@ -131,11 +161,16 @@ function drawHSeg(x1,y1,x2,y2,Num,dColor,Size) { } -function drawSSeg(x1,y1,x2,y2,Num,dColor,Size) { +function drawSSeg(x1,y1,x2,y2,Num,Color,Size) { for (let i = 1; i < 8; i++) { for (let j = 1; j < 8; j++) { if (Font[Num][j-1][i-1] == 1) { - g.setColor(dColor[0],dColor[1],dColor[2]); + if (Color == "fg") { + g.setColor(sColor[0],sColor[1],sColor[2]); + } else { + g.setColor(g.theme.fg); + //g.setColor(0.7,0.7,0.7); + } g.fillCircle(x1+(i-1)*(x2-x1)/7,y1+(j-1)*(y2-y1)/7,Size); } } @@ -143,25 +178,27 @@ function drawSSeg(x1,y1,x2,y2,Num,dColor,Size) { } -function ShowSecons() { - g.setColor(1,1,1); - g.fillRect((Xe-Xs) / 2 - 14 + Xs -3, - (Ye-Ys) / 2 - 7 + Ys -3, - (Xe-Xs) / 2 + 14 + Xs +1, - (Ye-Ys) / 2 + 7 + Ys +1); +function ShowSeconds() { + + g.setColor(sbColor[0],sbColor[1],sbColor[2]); + + g.fillRect((Xe-Xs) / 2 - 14 + Xs -4, + (Ye-Ys) / 2 - 7 + Ys -4, + (Xe-Xs) / 2 + 14 + Xs +4, + (Ye-Ys) / 2 + 7 + Ys +4); drawSSeg( (Xe-Xs) / 2 - 14 + Xs -1, - (Ye-Ys) / 2 - 7 + Ys , + (Ye-Ys) / 2 - 7 + Ys +1, (Xe-Xs) / 2 + Xs -1, - (Ye-Ys) / 2 + 7 + Ys, - ds,mColor,1); + (Ye-Ys) / 2 + 7 + Ys +1, + ds,"fg",1); - drawSSeg( (Xe-Xs) / 2 + Xs +1, - (Ye-Ys) / 2 - 7 + Ys, - (Xe-Xs) / 2 + 14 + Xs +1, - (Ye-Ys) / 2 + 7 + Ys, - es,mColor,1); + drawSSeg( (Xe-Xs) / 2 + Xs +2, + (Ye-Ys) / 2 - 7 + Ys +1, + (Xe-Xs) / 2 + 14 + Xs +2, + (Ye-Ys) / 2 + 7 + Ys +1, + es,"fg",1); } @@ -185,29 +222,29 @@ function draw() { g.reset(); if (dh != dho) { g.setColor(1,1,1); - drawHSeg(Xs, Ys, Xs+SegW, Ys+SegH,dh,hColor,4); + drawHSeg(Xs, Ys, Xs+SegW, Ys+SegH,dh,"fg",4); dho = dh; } if (eh != eho) { g.setColor(1,1,1); - drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,hColor,4); + drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,"fg",4); eho = eh; } if (dm != dmo) { g.setColor(0.3,0.3,1); - drawHSeg(Xs, Ys+SegH+Dy, Xs+SegW, Ys+SegH*2,dm,mColor,4); + drawHSeg(Xs, Ys+SegH+Dy, Xs+SegW, Ys+SegH*2,dm,"",4); dmo = dm; } if (em != emo) { g.setColor(0.3,0.3,1); - drawHSeg(Xs+SegW+Dx, Ys+SegH+Dy, Xs+SegW*2, Ys+SegH*2,em,mColor,4); + drawHSeg(Xs+SegW+Dx, Ys+SegH+Dy, Xs+SegW*2, Ys+SegH*2,em,"",4); emo = em; } - if (!Bangle.isLocked()) ShowSecons(); + if (!Bangle.isLocked()) ShowSeconds(); } @@ -278,7 +315,7 @@ function drawWidgeds() { var x1M = 100; var y1M = y1B; - var x2M = x1M + 30; + var x2M = x1M + 25; var y2M = y2B; if (messages.some(m=>m.new)) { @@ -295,6 +332,7 @@ function drawWidgeds() { print(strDow[dow] + ' ' + day + '.' + month + ' ' + year); + g.setColor(g.theme.fg); g.setFontAlign(-1, -1,0); g.setFont("Vector", 20); g.drawString(strDow[dow] + ' ' + day, 0, 0, true); diff --git a/apps/7x7dotsclock/7x7dotsclock.settings.js b/apps/7x7dotsclock/7x7dotsclock.settings.js index 42473ec17..34935d668 100644 --- a/apps/7x7dotsclock/7x7dotsclock.settings.js +++ b/apps/7x7dotsclock/7x7dotsclock.settings.js @@ -1,21 +1,8 @@ (function(back) { -let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "",ColorMinutes: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); -function showMainMenu() { - const mainMenu = { - "": {"title": "7x7 Dots Clock Settings"}, - "< Back": ()=>load(), - "sw-up": ()=>showSelAppMenu("swupApp"), - "sw-down": ()=>showSelAppMenu("swdownApp"), - "sw-left": ()=>showSelAppMenu("swleftApp"), - "sw-right": ()=>showSelAppMenu("swrightApp") - - }; - - E.showMenu(mainMenu); -} function setSetting(key,value) { print("call " + key + " = " + value); @@ -26,6 +13,42 @@ function setSetting(key,value) { } + // Helper method which uses int-based menu item for set of string values + function stringItems(key, startvalue, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + setSetting(key,values[v]); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(name,settings[name], values); + } + +function showMainMenu() { + const mainMenu = { + "": {"title": "7x7 Dots Clock Settings"}, + "< Back": ()=>load(), + "Minutes": stringInSettings("ColorMinutes", ["blue","pink","green","yellow"]), + "swipe-up": ()=>showSelAppMenu("swupApp"), + "swipe-down": ()=>showSelAppMenu("swdownApp"), + "swipe-left": ()=>showSelAppMenu("swleftApp"), + "swipe-right": ()=>showSelAppMenu("swrightApp") + + }; + + E.showMenu(mainMenu); +} + + function showSelAppMenu(key) { var Apps = require("Storage").list(/\.info$/) .map(app => {var a=storage.readJSON(app, 1);return ( diff --git a/apps/7x7dotsclock/ChangeLog b/apps/7x7dotsclock/ChangeLog index 50c5eca4a..d2c98a472 100644 --- a/apps/7x7dotsclock/ChangeLog +++ b/apps/7x7dotsclock/ChangeLog @@ -1 +1,2 @@ 0.01: Initial version for upload +0.02: better theme support, configurable colors, small improvements diff --git a/apps/7x7dotsclock/README.md b/apps/7x7dotsclock/README.md index 7f899ff0f..28fcac1b1 100644 --- a/apps/7x7dotsclock/README.md +++ b/apps/7x7dotsclock/README.md @@ -2,16 +2,14 @@ ![](dotsfontclock.png) -looks best with dark theme so far - * A Clock with big numbers made of 7x7 dots * system widgeds ar not (yet) supported * when screen is locked it shows hours and minutes in full screen mode +* adjustable color for minutes and seconds ![](dotsfontclock-scr1.png) -* when screen is unlocked it shows additional info: bluetooth, battery, new message, date and seconds -* you can configure a app per swipe direction -* when swiping the configured apps are launced +* when screen is unlocked it shows additional info: bluetooth, battery, new message state, date and seconds +* you can configure an app per swipe direction +* when swiping the configured apps are launched * button press opens launcher - diff --git a/apps/7x7dotsclock/dotsfontclock-scr1.png b/apps/7x7dotsclock/dotsfontclock-scr1.png index dc86396c0..5ab2e4863 100644 Binary files a/apps/7x7dotsclock/dotsfontclock-scr1.png and b/apps/7x7dotsclock/dotsfontclock-scr1.png differ diff --git a/apps/7x7dotsclock/dotsfontclock-scr2.png b/apps/7x7dotsclock/dotsfontclock-scr2.png new file mode 100644 index 000000000..f301bb50c Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock-scr2.png differ diff --git a/apps/7x7dotsclock/metadata.json b/apps/7x7dotsclock/metadata.json index 7b2c52512..41f0836d3 100644 --- a/apps/7x7dotsclock/metadata.json +++ b/apps/7x7dotsclock/metadata.json @@ -1,17 +1,19 @@ { "id": "7x7dotsclock", "name": "7x7 Dots Clock", "shortName":"7x7 Dots Clock", - "version":"0.01", + "version":"0.02", "description": "A clock with a big 7x7 dots Font", "icon": "dotsfontclock.png", "tags": "clock", "type": "clock", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], + "allow_emulator": true, "readme": "README.md", "storage": [ {"name":"7x7dotsclock.app.js","url":"7x7dotsclock.app.js"}, {"name":"7x7dotsclock.settings.js","url":"7x7dotsclock.settings.js"}, {"name":"7x7dotsclock.img","url":"7x7dotsclock.img.js","evaluate":true} ], - "data": [{"name":"7x7dotsclock.json"}] + "data": [{"name":"7x7dotsclock.json"}], + "screenshots": [{"url":"dotsfontclock.png"},{"url":"dotsfontclock-scr1.png"},{"url":"dotsfontclock-scr2.png"}] } diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index d129e9f9f..4576237a5 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -13,3 +13,4 @@ Widgets now shown on Alarm screen 0.13: Alarm widget state now updates when setting/resetting an alarm 0.14: Order of 'back' menu item +0.15: Fix hour/minute wrapping code for new menu system diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 17062d44a..56184edf1 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -73,12 +73,12 @@ function editAlarm(alarmIndex) { '': { 'title': /*LANG*/'Alarm' }, /*LANG*/'< Back' : showMainMenu, /*LANG*/'Hours': { - value: hrs, - onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + value: hrs, min : 0, max : 23, wrap : true, + onchange: v => hrs=v }, /*LANG*/'Minutes': { - value: mins, - onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + value: mins, min : 0, max : 59, wrap : true, + onchange: v => mins=v }, /*LANG*/'Enabled': { value: en, @@ -138,12 +138,12 @@ function editTimer(alarmIndex) { const menu = { '': { 'title': /*LANG*/'Timer' }, /*LANG*/'Hours': { - value: hrs, - onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + value: hrs, min : 0, max : 23, wrap : true, + onchange: v => hrs=v }, /*LANG*/'Minutes': { - value: mins, - onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + value: mins, min : 0, max : 59, wrap : true, + onchange: v => mins=v }, /*LANG*/'Enabled': { value: en, diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 3e109bda9..d29298309 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Default Alarm & Timer", "shortName": "Alarms", - "version": "0.14", + "version": "0.15", "description": "Set and respond to alarms and timers", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/aptsciclk/ChangeLog b/apps/aptsciclk/ChangeLog new file mode 100644 index 000000000..ed32a45a2 --- /dev/null +++ b/apps/aptsciclk/ChangeLog @@ -0,0 +1,8 @@ +0.01: New App! +0.02: Icons, loading screen +0.03: Random icon, Shorter "loading" screen +0.04: Support for light and dark Themes +0.05: Small bugfix +0.06: Formatting +0.07: Added potato GLaDOS and quote functionality when you tap her +0.08: Fixed drawing issues with the quotes and added more diff --git a/apps/aptsciclk/README.md b/apps/aptsciclk/README.md new file mode 100644 index 000000000..718e3d408 --- /dev/null +++ b/apps/aptsciclk/README.md @@ -0,0 +1,11 @@ +# Description + +This is a simple clock based on the Portal Series. + +# Features + +The button in the center of the screen is interactable and the warning image will change when it is pressed. + +Potato GLaDOS in the bottom left corner is interactable and will display a quote when tapped. (You can add more quotes by editing the `aptsciclkquotes.txt` file seperating each quote with a `^`) + +When the app loads the Apeture Science Logo is displayed. diff --git a/apps/aptsciclk/app-icon.js b/apps/aptsciclk/app-icon.js new file mode 100644 index 000000000..d2a2dbbd6 --- /dev/null +++ b/apps/aptsciclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgNKxAACEaIVDDKWAhAXGwAtODA4HBLR4YFD4QWICIhABGAoMBJRBZHC4wwHOQ4IFAgQwGUQ4YBAg4uMJIwDDGAjRLIgYLHc5gXJIwbKLC4hICb4gZKfAhgETJKHJLwwXRUooWKCImAJogXRMopGMNwkIC4oWLYYqtHC5rFJC5h0GIxwsGFyD8CC4wwOIxBIQFwoeBCxrwEFwYXTFgTXReI4uQC4apPC4xNERqBlGFx4XCeJ4nHD4kIIxY3KPoIxNBwYXEJRInEP44iGOgwXFBYYcDChCHHC4wMBC5BnJEoouMGAYXEJJCCJC4pOEcpYKBFIpJFZRQXGD4gWKXBUICxjdFIhwyJOJMAA=")) diff --git a/apps/aptsciclk/app.js b/apps/aptsciclk/app.js new file mode 100644 index 000000000..c2903cf37 --- /dev/null +++ b/apps/aptsciclk/app.js @@ -0,0 +1,368 @@ +const big = g.getWidth()>200; +const timeFontSize = big?5:4; +const dateFontSize = big?3:2; +const gmtFontSize = 2; +const font = "6x8"; + +const xyCenter = g.getWidth() / 2; +const yposTime = xyCenter*0.73; +const yposDate = xyCenter*0.48; +const yposYear = xyCenter*1.8; + +const buttonTolerance = 20; +const buttonX = 88; +const buttonY = 104; + +var pause = false; //set to true to pause any sort of drawing (except for quotes) + +function getImg(img){ + if (img == "w0"){//drink + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOZFIQOD4EABwnwgEDBwf8g/4h4ODwYQBv4OC+AbDAIP+j/HAQIOC4Hwj4RBBwP8o8B/+PBwWOkEP/l/BwP4+JCB44OCj+Ih/+n4OB+PEoP38YOB/0YkUXGgIOB8cBi9f+IOCkEI+XvBwXigFG64OEg0/t4OEuP7BwkHx/PBwWigF8voOC+Uwg/ig4OCkMgv8QsIOB+cfSoOGLIUR/E/4ljBwPxx/B/0kO4UI/0P+J3C/HHVQOISoWEn+D/iPBBwIwC8IOCwcP84IBBwU4TAMHBwfAv+AcARBBgD3CBwX8gDnBBwfwewIODAgIABBwYHDB3oAEBwIHFByyDBABg")) +} + } + else if (img == "w1"){//cube dispenser + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOI/3/+fvBwYEBnwO/By3APgN/O6IeBh4OF8AOcwADCBwX8g4dM/8fBwt774OE+/9Bwt/BxodH3oOcFgyVG8BhCBwX8hRwCBwXA0C6BBwc/w4OE41MBwtEo6VF84sE/1/54OLDo4sHHYxKHLIxoGO44AD/kAABo")) + } + } + else if (img == "w2"){//acid + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOF+IOGngOF8/D8YGD/wdBB4nv4fzAwf4BwOfGQd/4f7/+//+f74OB4PwHIJKDx8P/4BBBwP8BwIBBBwXvh+Hw5ZD+Pwnl/NAcegJOBBwfgj0fBwvhBxcPgYEBBwXw/F+FghIB84OC/BfBOYQOBk/w/0f4f4nkGgFgh0hwED4H4jOBuF8hk/v/Hzlnx/zFgQZBGYLCD4EHaIn8gAOF8EDBwn+dgQOK/8AN4IOD+EABww0BBwqGEBwIWBBwk8CwIODg/gv4OEv4OD+4OBBAIOBRYIFBh+PcAQdC+gOCDoN+h/vBwPP/wOB/wOBwJCBBwP2oa3BLALgBiA7BOwIvB/+DQoV/d4hPBBwQsB/wJB8ZoEAAZoDAAQOPRQIAM")) + } + } + else if (img == "w3"){//turret + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOi+IOGh4OF8AOF/UNBwthx4OE+0YBwtBh4OE6mQBwn7rEfBwl22IOE99gBwn99UzBwUc/+90YsC8HH+++n98n/+g0++2Z+4OB4Fz73T74OCg877d8/YdC+d7u/v3gsBjEvt/+O4X+gvtIgI7CwG934OD8E326kD/0A+yzEwEO74OD/EArYOEgEDv4OD+PAl4OEnkBaInz0EPBwk3iAdE+XwSIYDBj2Oj4OD/fYvIOEvdHz4OD99unIOD/vt44OE3u4Dou3h4OE+3x/IOE70/Bwn78/9Bwl4LAQ7Dx75DBwP4Awb+EBwgAEBz0AABo=")) + } + } + else if (img == "w4"){//falling cube + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOC+YOF/0PBwvgv4OE/kFBwvAyIdFnYeBBwYeDDofng4OE8vYDonx7uPBwkf/+/Bwfh+czBwf+g/5z4OD+FevIdEhMDDon/0E3BwgeBJQgeB+5ZFvAEBBwfzgYOEw/XLInwn3BBwf8gH4LYIOCwUHDonwmE4HYkHwKkE8P4XYQOCv7dCYQkBWYsAWYvAiAsE/EDJQn/wF+CwJZDg/gBwgrBXYIOC8D+FNAL+F4eDBwn4nh2BBweHFYJ3EFYQOC/0P/AOECgIOE/E/BwsHBwvACAIODWAQOEJAIOFAgIOEQ4QsEAAOfBwoACBwgACBw8AABo")) +} + } + else if (img == "w5"){//ball + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOiv4OF8YOFAgQOEyYdGBw3zBw0BBwv4j4OB+EAgOD84OE+/ev4dD/3+BwvcugsE/u7t0f4aRC7e2sF8Bwlxg4dEu8YBwYsB/HDHYsMngOB8EDweHDon//PADoYABz0PBwfwnJKE/0OjZZC/kB4Hxz4OCwEYh+wBwXwgeA/+HBwUP8EP/0/BwPj/0DCQIOB/l/4DQBw4OBDIMPUoJKB+H/wY+B44OBj/4CoJKC+P/g7+FBAL+Fj4OFbwIOEI4IOF8YO6JQwAEaIgORgAANA")) + } + } + else if (img == "w6"){//ball recviver + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOR/YOG34Ob/e7Bwu7CwQOhGgQOD34OF/0LBwvfv4dMuPfBwn29oOFtwONDowsHHY3+h7CNj4OF+IOc4A7NDo7gGJQ4ACBwX+//vBwnvBAIOK8EH/kBBwd+v/PSwIOB/fnjiWBBwXesHPLQIOB/2AgEvBwfgh0AFgf8gAuBLKQObgAANA==")) + } + } + else if (img == "w7"){//falling portals + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gOE/v7Bxn5Bw2fHYv7/oOF3/cB118JQQOC4ODJQn8jEfLInBjBoE/0jO4pjD953CwCVF/EH5//+ykCwA8Cp4OB/MDz4DBEQUYjPzaIfn5k/74xC/l44f+BwePz1595ADDYPvv7vDMAN3Bwf4CAIOE4//BYIOB/0On47E8AFCBwcPTwYOCAgPAgE8Bwf8gEDBwOAGIJZDBwX9DofhUYRKDKIIOEAAQOD8EABwgcB+IODnoKB84OD37tCBwUzZ4QODZ4QdDnIFB/YODZwP+v47DJIIBBJQcAAwZyBABoA==")) + } + } + else if (img == "w8"){//flying portals + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gdF/YOF/4OF/IOGgA7F8ENBwn8gHcBw/5AoOAg4OCh4sD/vD+AFB45KBBwfwv//BwMJgEIFAXcnvggF4kEBBwPMSIIYBz/8nAEBw5ZD4IhBO48AhpoG953FSo/2Ugv/p4OF/LCGaIyIBB34OH4EAngODbAMDBwnfDoqeCBy7RBBwnh//xBwc9BQPnBwe/AYO/BwUzFYQODGgYOCnIFB/YOD57WBv47Dj//AIJKDgAGDOQIANA")) + } + } + else if (img == "w9"){//cake + return { + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOY/oOG94OF/1/Bwv3FgwKCBwfnFhn8HY0LAQPwvgOB8EP/5uBBwP2gF4j+PBwP+sEEj/x44OB90Ao/8Dodwg8/nkH4ZXBgHnx8ABwPv/k98+ABwZEB+EAJQPj/3+nkAv4OB5+fz0Aj4OB98Ag+Ah/nBwJXB4EDHYSTB/EA/wsCSoJfBwAODNIPgBwgcBHYQOCC4QODn8Ah4ODGgMH+47D8EB/A7KTYMf4A7Eg/wHYgcBHZx3DcAPggbRBFgQcBcAQOB/iUBBwgcBBwgcCd4V/HYL+D/YOBDgIOC8/+DgIOC/+HfwIOD/4cCBwYAEBwQADBz0AABoA=")) + } + } + else if (img == "butPress"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhPD5e7he7A4fBJn5XNKwJXCLAZX/ABUcKwhXDLAZN/VxhSCK4m8WH5XNVwZXEWARX/ABEcK4sAgBYDXYRP/K5RQC2ACE3e8K/5XPVgYDCK/4AKJIPLVYoEEBoPBIWPd6ICPK46uDAohXzjvd7oCMCAJX/K7cAAAZXFBQkBK/6v/ABPd6ICPK/4AaK4mwKwYEDV+4ARjhKBVQoDD3gMBK2MdAIRXXVYSuDK/5XN5ZRCgEAWQYLBK/4AJK4u7KwZXC4JXxiPd6JXV5hXH3hX1ACscWApXDMQRN/WBpYCK4QICV35XOLARXBA4ZX/ABccKAfMhgFEJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACIA==")) + } + } + else if (img == "butUnpress"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhQEK43BJn5X/AEMcK5fMJv6uPK46w/K/4AgjhPFgEALAxP/K5vAAQhX/K6KsDWApP/AA6uHWA/BIWOIwICPK46qFAohXxjGIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIwICPK/4AaKInAAhCv2ACMcVRC0FK2MYAIRXXVYSuFK/5XO5kAgCuFK/4AJJwvMKw3BK+MRxGBK/4ArjhXMJv6wQK4qu/K/4AjjhXKJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACI=")) + } + } + + else if (img == "apetureLaboratories"){ + return { + width : 173, height : 43, bpp : 4, + transparent : 0, + buffer : require("heatshrink").decompress(atob("AA+IAAeAIv5UTK34APhABBKwpeBJX5VLJgJWGAwKv/ABL8EKomABIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWAL4oA/KpALGXQhS/Ko4MIwAOMAHREOKv5ZVVgcAh///4LDAwQAB+AIHCQYHMDQQYBDwQEGDIoaGTx4MCwAiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiL+DzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPgGAEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwEIVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFhGAVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocP///dhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2AKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAv/EghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FgAGCKooA/AAZMBfopWDKv5WLVgxT/AB+AKopW/ACBU/ABwA==")) + } + } + +else if (img == "apetureLaboratoriesLight"){ + return { + width : 173, height : 43, bpp : 4, + transparent : 1, + buffer : require("heatshrink").decompress(atob("iIAGxAADwINHAH5ULK34APjABBKwpeBJX5VLJgJWGAwKv/ABL8EKomBBIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWBL4oA/KpALGXQhS/Ko4MIwIOMAHREOKv5ZVVgcRiEAgALDAwQABgIIHCQYHMDQQYBDwQEGDIoaGTx4MCwIiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiLgLzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPiOBEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwMYVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFjGBVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocQgEAdhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2BKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAsAEghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FiIGCKooA/AAZMBfopWDKv5WLVgxT/AB+BKopW/ACBU/ABsQA=")) + } + } + + else if (img == "potato"){ + return { + width : 54, height : 55, bpp : 4, + transparent : 6, + buffer : require("heatshrink").decompress(atob("swAEsEGA4oASEQ4ARGgNgDa8QsA3BKStowxTDDalikxTCRKsSNwY2BDSYvDGoI2TsoTDgwEBGx5IBs1VHIgaBGx4qCDQZOBUiUGstQDQ4FBKYwHGDQNWDRA3BCYsABotgJ4dkogABowcGAAUGOwogDDAVCAYScKOooFBooWCAAjqNAoQYHKYQ8ELQZzFsAMCiIeJAAdFMwiCEoIaNoICBoAaMiMUDA0ikMiigaIAAgaGDIIaBkcyoCHEMxqIBmdEkMzmcxDSVBkYXBGoMzmUQDSMSDIURiIbBkDBCDRtBiYwBDIMREAMiDR6qBiI0BkQEBKQKkBUJQAEoCfBC4MVgMSDYMkGwQaBeBMUiC3BGYNN8kSHgM0DQrsGAAMQQAMyCwIaBgK/BmIaKM4IGCiMTDQXd9waCmlENZIaEgESKAMhpxQEDQSiEoJTGiEimaGCqIaBmKABUQwyBUIzRBkIXBDoI8BkAaDGwgaFEIMCeQUSYAKNBmLYDGwJ/DDYlFqAaBGwILBGgLyBUIRSFQghrCgEjDYMiiTdCggaFDYgaFKIKIBmcTkciiA1GNwpsFgI4BmZsBkVEDRAbJiBTCNQMABIIZHfBCPBDQKSCoFEGhA2IKIMQgJ1CoCFHGxVBeASQDgAZJDQ8RQIMyDQkGDZQ1GDILtBAwNGsAaLs1hokECYNCaQMxDIVmDRoOBDQifCBggaNKgMRqUhiovFGxoMEsCADAQQaMsUWJBi+LsMRqtWDSwUBqvFqpVFQ54UCstVqEGsDbBQx4lFgAABotRAgQABT54ADqtRM5jjQAAQ")) + } + } + + else if (img == "apetureWatch"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 2, + buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+/4A/AH4A/AH4A/AD0RgAASgMQCqgrqiJX/K/5X/K/5XR7vQK/8IxBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xah////wK/5XTKwIABK4YAG//x7vRBhAA2jGIAYMfK4fxCRAOCK/5XFKwYABBgTBEh4LC7vQbabxLFYoVPFZMIxADBK4sAK4wLDK/5XSBYhX/K6MPBIQDBK/5XD+BXMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XF+CuEK4yuCK/5XHWAZOCK4cPKwhX/K45MFK4YAGK/5XGLApX/K6X/K5sPK/5XIWAZXJ/5X/K6sPK/5XPAoauDK/5XJ/5XDj5eEVwRX/K5y2FVwRX/K4/wK44GDVwRX/K50fWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLC+BXBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/Ch5X/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAnxFYRdBBohX/K5cAiIrJK/5XEfIhX/K/5Xr+BX/K/5Xu/5X/K/5WKFhRXWl5XB+BX/K6ywFK9XxK4SMFK7JWCK58PK7sP/5XDGoxXr/5Xc//4xBXEHIKyPK54fFK5CPBK7cPxAyBK4oHBK7wKFK5AQBK7UP/GPD4JWCiMfK4IJBK7jOGFgYwEK4XRBg4AQ/CtFAAZXCBQ4AQjBXCCRxXcUoJLDjnMhnMBgTqCK7RzJYYxXC6DbTAAf4UYInB5gABK4PM4KBDdYwrQhBXBBQ5XIAAJXYh6GDKwRXDLASwCK7BxIK8auDjhXH5iwPK5gKIK8iuBKwhXFLAJX/AA0PxCuBKAhXG4KwCK/6uEx/xK5qwNK/KuBjhXL5kRWAJXrbapXEJ4pXH4JXXABRXhh+I+JXPiP//5X/K4WP+McJ4oLBLAxX/K5vAAQhX/ABBDBiJXFVgawFiMf//wK/4gBAAKuHWA/BCYQhIK8uIwACOK5CqFAohXxhGIxACMCAJX/K7YaEK4pKFK/6v/ABOIwACOK/4AMFZRXI4AEIV+wrO///iMcVRC0FiMf//wQaZXZhABCFZ0P//xK4qrCVwpXB/+PbahX15gLBVwpX/K5ERJwvMKw3BiP4K98AxGAFaH//5XPj/4+BXvFaRXCjhXMiMfxBXhGoIAcK4nxWAxXF4MR/GPK4Q4e+UiADcvK4UPWARXMj/4NwayKACUPK8KwDjhXKcQOIKYZX/eIkRLAhXEiKuBx5XEY4QAYDgJXiIAXxiJXH4KuC/4VDK/6wGLAZXCKwKuGK/6wIiMcK4QFBVw5X/WA2IwJSCAAYJBVwpX/WA2IJwJVDj///GPVwpX/LA34K4PxVoYHBKwxX/AAynCK4ZWC+BX/K5ixB/6vEVo5X/IxAABK4asHK/5XLgJXCBxRX/K/5XgLAQNLK/5X/K6r7CACxX/K/5XVfJcBiINLK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K5o6bK/YaZK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K6o6bK/cAABUBiINLK/5X/K/5X/K/5X/K/AMLACBX/K7DMaAAcRADA4eA==")) + } + } + + else if (img == "apetureWatchLight"){ + return { + width : 176, height : 176, bpp : 4, + transparent : 2, + buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+gAA/AH4A/AH4A/AD0R/4AS+MfCqgrqiJX/K/5X/K/5XR7vfK//4xBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xa+EAgEPK/5XTKwIABK4YAGgEB7vRBhAA2jGIAYMQK4cBCRAOCK/5XFKwYABBgTBE+ALC7vfbabxLFYoVPFZP4xADBK4v/K4wLDK/5XSBYhX/K6PwBIQDBK/5XDh5XMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XFh6uEK4yuCK/5XHWAZOCK4fwKwhX/K45MFK4YAGK/5XGLApX/K6UAK5vwK/5XIWAZXJgBX/K6vwK/5XPAoauDK/5XJgBXDiBeEVwRX/K5y2FVwRX/K48PK44GDVwRX/K50QWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLCh5XBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/C+BX/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAkBFYRdBBohX/K5f/iIrJK/5XEfIhX/K/5Xrh5X/K/5XugBX/K/5WKFhRXWkBXBh5XvgJXCGgpXcWApXoF4KvD+COGK65WCK5/wK7gtCK4YmCLB5XfgBXbFgIzBK4mILCBXPDwpXIcIJXa+BPBKAJXFLARXdBQpXICAJXah/4x4qDAAMfK4IJBWBpXODgwsDAAcQK4XRBg4APgAwBVogADK4XwgInWjBXCCRxXbiD9BKwcc5kM5gNCRgXwK7JyJYYxXC77bTIwf4UYInB5gABK4PM4MRDQXwDpYrKawMABQ5XIAAJXYh6uDKwRXDLAQRDK64YIK8SuEjhXH5iwPK5gKIK8UP/APBKwhXFLAKwNK/ItBiJQEK43BDgRX/AAXw/GP+JXNGQXwK/5XDEgMcK5fMiIdBK9YAKK5cPK4RPFK4/BDoUPFagAJK8WI+JXPGYRX/IIWP+McJ4sAgBYGK/5XN4ACEK/4AH+AjCK4qsDWAsRDwIWCK/ogBAAKuHWA/BCYQhIK8uIx4COK5CqFAohXx/GIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIx4COK/4AMFZRXI4AEIV+wrN+AjCjiqIWgpUCCwRXr/ABCFZ0PBoJXFVYSuFK4P/x4VBK/5XI5kAgCuFK/5XIiJOF5hWG4MR/BXv/+Ix5XREgJXOj58BK94rR+AkCjhXMiMfxAUCK70AADpXE+KwGK4vBiP4x5XhgUiADcgEQTyCK5sf/ATDK/5DD+McK5UR/+IK/5XEeYcRLAhXEiKuBx4SDK/6wFiJXH4KuOK/SwELAZXCKwKuOK/ywBiMcK4QFBVwZX/K43/gACBxGBKQQADBIKuBh5X/K43/JAOIJwJVDIYP4x4NCK/5XHfAP4K4PxVoYHBBgRX/K5H/gCnCK4ZWDVxpX9LARABV4ZWQK/xYBgBXD+EAKx5X/AAMBK4RVQK/5ADK4RBSK/5YDIKZX/K/5XNfYQA1K/5X2eJkReKfxj4VTK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XvgAAYh5X8DTJX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XVgAAYK/orL+MRIKZX/K/5X/K/5X/K/5X/K/5XmgAAdiIA3")) + } + } +} + + +function drawStart(){ + g.clear(); + g.reset(); + if (g.theme.dark){apSciLab = getImg("apetureLaboratories");} + else {apSciLab = getImg("apetureLaboratoriesLight");} + g.drawImage(apSciLab, xyCenter-apSciLab.width/2, xyCenter-apSciLab.height/2); +} + +// Check settings for what type our clock should be +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; + +// timeout used to update every minute +var drawTimeout; + +//warnings +var maxWarning = 9; +var curWarning = Math.floor(Math.random() * (maxWarning+1)); + +function unPause(delay, quote){ + if (pause){ + setTimeout(function() { + if (quote == undefined || quoteNum == quote){ + pause = false; + draw(); + } + }, delay); + } +} + +var quoteNum; + +function quote(fontsize, width, height, specificQuote){ + pause = true; + var finalString = ""; + var quotesFile; + var finalFontSize; + quotesFile = require("Storage").read("aptsciclkquotes.txt", 0, 0); //opens the quotes file + //console.log(quotesFile); + var quotes = quotesFile.split("^"); + var numQuotes = quotes.length;//number of quotes + var curQuote; + + if (specificQuote == undefined){ + quoteNum = Math.round(Math.random()*numQuotes)-1; + curQuote = quotes[quoteNum]; //quote to be displayed + } + else{ + quoteNum = specificQuote; + curQuote = quotes[quoteNum]; + } + + unPause(10000, quoteNum); + + var curWords = curQuote.split(" "); //individual words + //console.log(numQuotes); + + var maxChar = width/6/fontsize; + var maxLines = height/10/fontsize; + var curLines = 0; + var curLength = 0; + + + for (var i = 0; i < curWords.length; i++){ + //console.log(curLength+curWords[i].length); + if (curLength + curWords[i].length <= maxChar){ + finalString += " "+curWords[i]; + curLength += curWords[i].length+1; + //console.log("next"); + } + else{ + //console.log("break"); + curLines++; + if (curLines > maxLines){ + curLength = 0; + finalString = ""; + i = -1; + if (fontsize > 1){fontsize--;} + maxChar = width/6/fontsize; + maxLines = height/10/fontsize; + console.log(maxLines); + console.log(maxChar); + + } + else{ + curLength = 0; + finalString += "\n"; + i--; + } + } + finalFontSize = fontsize; + } + + + //drawing actual stuff + g.setColor(g.getBgColor()); + g.fillRect(10, 10+28, g.getWidth()-10,g.getWidth()-10); + g.reset(); + g.setFont(font, finalFontSize); + g.setFontAlign(0, 0); + g.drawString(finalString, xyCenter, xyCenter+14); + //quote length*pixels per character = pixel width + //height ~120 width ~160 +} + +function buttonPressed(){ + if (curWarning < maxWarning) curWarning += 1; + else curWarning = 0; + g.reset(); + buttonImg = getImg("butPress"); + g.drawImage(buttonImg, 0, 0); + + warningImg = getImg("w"+String(curWarning)); + g.drawImage(warningImg, 1, g.getWidth()-61); + + setTimeout(buttonUnpressed, 500); +} +function buttonUnpressed(){ + if (!pause){ + buttonImg = getImg("butUnpress"); + g.drawImage(buttonImg, 0, 0); + } + else{ + setTimeout(buttonUnpressed, 500); + } +} + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + if (pause){} + else{ + // get date + var d = new Date(); + var da = d.toString().split(" "); + + g.reset(); // default draw styles + //draw watchface + if (g.theme.dark){apSciWatch = getImg("apetureWatch");} + else {apSciWatch = getImg("apetureWatchLight");} + g.drawImage(apSciWatch, xyCenter-apSciWatch.width/2, xyCenter-apSciWatch.height/2); + + potato = getImg("potato"); + g.drawImage(potato, 118, 118); + + g.drawImage(warningImg, 1, g.getWidth()-61);//update warning + + // drawString centered + g.setFontAlign(0, 0); + + // draw time + var time = da[4].substr(0, 5).split(":"); + var hours = time[0], + minutes = time[1]; + var meridian = ""; + if (is12Hour) { + hours = parseInt(hours,10); + meridian = "AM"; + if (hours == 0) { + hours = 12; + meridian = "AM"; + } else if (hours >= 12) { + meridian = "PM"; + if (hours>12) hours -= 12; + } + hours = (" "+hours).substr(-2); + } + + g.setFont(font, timeFontSize); + g.drawString(`${hours}:${minutes}`, xyCenter+2, yposTime, false); + g.setFont(font, gmtFontSize); + g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + // draw Day, name of month, Date + var date = [da[0], da[1], da[2]].join(" "); + g.setFont(font, dateFontSize); + g.drawString(String(date), xyCenter, yposDate, false); + + + // draw year + g.setFont(font, dateFontSize); + g.drawString(d.getFullYear(), xyCenter+1, yposYear, true); + } + queueDraw(); +} + + +// 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; + } +}); + +Bangle.on('touch',(n,e)=>{ + //button is 88 104 + if (!pause && buttonX-buttonTolerance < e.x && e.x < buttonX+buttonTolerance && buttonY-buttonTolerance < e.y && e.y < buttonY+buttonTolerance){ + buttonPressed(); + } + //Potato GLaDOS + else if (!pause && 117 < e.x && e.x < 172 && 117 < e.y && e.y < 172){ + quote(2, 150, 140); + } + else{ + unPause(0); + } +}); + +//show Apeture laboritories +drawStart(); + +setTimeout(function() { + // clean app screen + g.clear(); + // Show launcher when button pressed + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + //update warning image + buttonPressed(); + // draw now + draw(); + }, 500); diff --git a/apps/aptsciclk/app.png b/apps/aptsciclk/app.png new file mode 100644 index 000000000..b37efdaf8 Binary files /dev/null and b/apps/aptsciclk/app.png differ diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json new file mode 100644 index 000000000..c450d926e --- /dev/null +++ b/apps/aptsciclk/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "aptsciclk", + "name": "Apeture Science Clock", + "shortName":"AptSci Clock", + "version": "0.08", + "description": "A clock based on the portal series", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": false, + "readme":"README.md", + "storage": [ + {"name":"aptsciclkquotes.txt","url":"quotes.txt"}, + {"name":"aptsciclk.app.js","url":"app.js"}, + {"name":"aptsciclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/aptsciclk/quotes.txt b/apps/aptsciclk/quotes.txt new file mode 100644 index 000000000..bc7a04867 --- /dev/null +++ b/apps/aptsciclk/quotes.txt @@ -0,0 +1 @@ +Well here we are again^You euthanized your faithful Companion Cube more quickly than any test subject on record. Congratulations.^So get comfortable while I warm up the neurotoxin emitters^This isn't brave. It's murder. What did I ever do to you?^The difference between us is that I can feel pain.^Who's gonna make the cake when I'm gone? You?^Oh... It's you.^I've been really busy being dead. You know, after you MURDERED ME.^So. How are you holding up? BECAUSE I'M A POTATO.^You really do have brain damage, don't you?^You like revenge, right? Everybody likes revenge. Well, let's go get some.^It's been fun. Don't come back.^And then you showed up. You dangerous, mute lunatic.^Unbelievable. You, [subject name here] must be the pride of [subject hometown here.]^You are not a good person. You know that, right? Good people don't get up here.^Cake, and grief counseling, will be available at the conclusion of the test.^This is your fault. I'm going to kill you. And all the cake is gone. You don't even care, do you?^Momentum, a function of mass and velocity, is conserved between portals. In layman's terms, speedy thing goes in, speedy thing comes out. diff --git a/apps/banglerun/.gitignore b/apps/banglerun/.gitignore deleted file mode 100644 index c2658d7d1..000000000 --- a/apps/banglerun/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/apps/banglerun/ChangeLog b/apps/banglerun/ChangeLog deleted file mode 100644 index c778588cc..000000000 --- a/apps/banglerun/ChangeLog +++ /dev/null @@ -1,13 +0,0 @@ -0.01: First release -0.02: Bugfix time: Reset minutes to 0 when hitting 60 -0.03: Fix distance >=10 km (fix #529) -0.04: Use offscreen buffer for flickerless updates -0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging -0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above) -0.07: Fixed GPS update, added guards against NaN values -0.08: Fix issue with GPS coordinates being wrong after the first one -0.09: Another GPS fix (log raw coordinates - not filtered ones) -0.10: Removed kalman filtering to allow distance log to work - Only log data every 5 seconds (not 1 sec) - Don't create a file until the first log entry is ready - Add labels for buttons diff --git a/apps/banglerun/README.md b/apps/banglerun/README.md deleted file mode 100644 index 80e984bfa..000000000 --- a/apps/banglerun/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# BangleRun - -An app for running sessions. Displays info and logs your run for later viewing. - -## Compilation - -The app is written in Typescript, and needs to be transpiled in order to be -run on the BangleJS. The easiest way to perform this step is by using the -ubiquitous [NPM package manager](https://www.npmjs.com/get-npm). - -After having installed NPM for your platform, checkout the `BangleApps` repo, -open a terminal, and navigate into the `apps/banglerun` folder. Then issue: - -``` -npm i -``` - -to install the project's build tools, and: - -``` -npm run build -``` - -To build the app. The last command will generate the `app.js` file, containing -the transpiled code for the BangleJS. diff --git a/apps/banglerun/app-icon.js b/apps/banglerun/app-icon.js deleted file mode 100644 index 9eeaced6e..000000000 --- a/apps/banglerun/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwwIHEuAEDgP8ApMDAqAXBjAGD/E8AgUcgF8CAX/BgIFBn//wAFCv//8PwAoP///5Aon/8AcB+IFB4AFB8P/34FBgfj/8fwAFB4f+g4cBg/H/w/Cg+HKQcPx4FEh4/CAoMfAocOj4/CKYRwELIIFDLII6BAoZSBLIYeCgP+v4FD/k/GAQFBHgcD/ABBIIX4gIFBSYPwAoUPAog/B8AFEwAFDDQQCBQoQFCZYYFigCKEgFwgAA==")) diff --git a/apps/banglerun/app.js b/apps/banglerun/app.js deleted file mode 100644 index b79255171..000000000 --- a/apps/banglerun/app.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";var t;!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(t||(t={}));const n={STOP:63488,PAUSE:65504,RUN:2016};function e(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function i(i){var s;g.setFontVector(30),g.setFontAlign(0,-1,0),e((i.distance/1e3).toFixed(2),60,55),e(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,s=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+s).substr(-2)}(i.duration),172,55),e(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(i.speed),60,115),e(i.hr.toFixed(0),172,115),e(i.steps.toFixed(0),60,175),e(i.cadence.toFixed(0),172,175),g.setFont("6x8",2),g.setColor(i.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(s=new Date).getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2),120,220),g.setColor(n[i.status]),g.fillRect(160,216,230,240),g.setColor(0),g.drawString(i.status,200,220),g.setFont("6x8").setFontAlign(0,0,1).setColor(-1),i.status===t.Paused?g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1):i.status===t.Running?g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1):g.drawString("START",236,60,1).drawString(" ",236,180,1)}function s(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),i(t),Bangle.drawWidgets()}function a(n){n.status===t.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Running,i(n)}const r={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:t.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var o;o=r,Bangle.on("GPS",n=>function(n,e){n.lat=e.lat,n.lon=e.lon,n.alt=e.alt,n.vel=e.speed/3.6,n.fix=e.fix,n.dop=e.hdop,n.gpsValid=n.fix>0,function(n){const e=Date.now();let i=(e-n.t)/1e3;if(isFinite(i)||(i=0),n.t=e,n.timeSinceLog+=i,n.status===t.Running&&(n.duration+=i),!n.gpsValid)return;const s=6371008.8+n.alt,a=n.lat*Math.PI/180,r=n.lon*Math.PI/180,o=s*Math.cos(a)*Math.cos(r),g=s*Math.cos(a)*Math.sin(r),d=s*Math.sin(a);if(!n.x)return n.x=o,n.y=g,void(n.z=d);const u=o-n.x,l=g-n.y,c=d-n.z,f=Math.sqrt(u*u+l*l+c*c);n.x=o,n.y=g,n.z=d,n.status===t.Running&&(n.distance+=f,n.speed=n.distance/n.duration||0,n.cadence=60*n.steps/n.duration||0)}(n),i(n),n.gpsValid&&n.status===t.Running&&n.timeSinceLog>5&&(n.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(n))}(o,n)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,s=t.hrError/(t.hrError+i)||0;t.hr+=e*s,t.hrError+=(i-t.hrError)*s}(t,n)),Bangle.setHRMPower(1)}(r),function(n){Bangle.on("step",()=>function(n){n.status===t.Running&&(n.steps+=1)}(n))}(r),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&s(t)}),s(t)}(r),setWatch(()=>a(r),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(n){n.status===t.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Stopped,i(n)}(r),BTN3,{repeat:!0,edge:"falling"})}(); diff --git a/apps/banglerun/banglerun.png b/apps/banglerun/banglerun.png deleted file mode 100644 index bf2cd8af3..000000000 Binary files a/apps/banglerun/banglerun.png and /dev/null differ diff --git a/apps/banglerun/interface.html b/apps/banglerun/interface.html deleted file mode 100644 index 6388d3b65..000000000 --- a/apps/banglerun/interface.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - -
- - - - - diff --git a/apps/banglerun/jasmine.json b/apps/banglerun/jasmine.json deleted file mode 100644 index 813363b27..000000000 --- a/apps/banglerun/jasmine.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "spec_dir": "test", - "spec_files": [ - "**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/apps/banglerun/metadata.json b/apps/banglerun/metadata.json deleted file mode 100644 index d66441c8d..000000000 --- a/apps/banglerun/metadata.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "banglerun", - "name": "BangleRun", - "shortName": "BangleRun", - "version": "0.10", - "description": "An app for running sessions. Displays info and logs your run for later viewing.", - "icon": "banglerun.png", - "tags": "run,running,fitness,outdoors", - "supports": ["BANGLEJS"], - "interface": "interface.html", - "allow_emulator": false, - "storage": [ - {"name":"banglerun.app.js","url":"app.js"}, - {"name":"banglerun.img","url":"app-icon.js","evaluate":true} - ] -} diff --git a/apps/banglerun/package.json b/apps/banglerun/package.json deleted file mode 100644 index 1f5cc677b..000000000 --- a/apps/banglerun/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "banglerun", - "version": "0.5.0", - "description": "Bangle.js app for running sessions", - "main": "app.js", - "types": "app.d.ts", - "scripts": { - "build": "rollup -c", - "test": "ts-node -P tsconfig.spec.json node_modules/jasmine/bin/jasmine --config=jasmine.json" - }, - "author": { - "name": "Stefano Baldan", - "email": "singintime@gmail.com" - }, - "license": "ISC", - "devDependencies": { - "@rollup/plugin-typescript": "^4.1.1", - "@types/jasmine": "^3.5.10", - "jasmine": "^3.5.0", - "rollup": "^2.10.2", - "rollup-plugin-terser": "^5.3.0", - "terser": "^4.7.0", - "ts-node": "^8.10.2", - "tslib": "^2.0.0", - "typescript": "^3.9.2" - } -} diff --git a/apps/banglerun/rollup.config.js b/apps/banglerun/rollup.config.js deleted file mode 100644 index f7027eb2b..000000000 --- a/apps/banglerun/rollup.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import typescript from '@rollup/plugin-typescript'; -import { terser } from 'rollup-plugin-terser'; - -export default { - input: './src/app.ts', - output: { - dir: '.', - format: 'iife', - name: 'banglerun' - }, - plugins: [ - typescript(), - terser(), - ] -}; diff --git a/apps/banglerun/src/activity.ts b/apps/banglerun/src/activity.ts deleted file mode 100644 index c1a01f30b..000000000 --- a/apps/banglerun/src/activity.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { draw } from './display'; -import { initLog } from './log'; -import { ActivityStatus, AppState } from './state'; - -function startActivity(state: AppState): void { - if (state.status === ActivityStatus.Stopped) { - initLog(state); - } - - if (state.status === ActivityStatus.Running) { - state.status = ActivityStatus.Paused; - } else { - state.status = ActivityStatus.Running; - } - - draw(state); -} - -function stopActivity(state: AppState): void { - if (state.status === ActivityStatus.Paused) { - clearActivity(state); - } - - if (state.status === ActivityStatus.Running) { - state.status = ActivityStatus.Paused; - } else { - state.status = ActivityStatus.Stopped; - } - - draw(state); -} - -function clearActivity(state: AppState): void { - state.duration = 0; - state.distance = 0; - state.speed = 0; - state.steps = 0; - state.cadence = 0; -} - -export { clearActivity, startActivity, stopActivity }; diff --git a/apps/banglerun/src/app.ts b/apps/banglerun/src/app.ts deleted file mode 100644 index 7093e24e0..000000000 --- a/apps/banglerun/src/app.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { startActivity, stopActivity } from './activity'; -import { initDisplay } from './display'; -import { initGps } from './gps'; -import { initHrm } from './hrm'; -import { initState } from './state'; -import { initStep } from './step'; - -declare var BTN1: any; -declare var BTN3: any; -declare var setWatch: any; - -const appState = initState(); - -initGps(appState); -initHrm(appState); -initStep(appState); -initDisplay(appState); - -setWatch(() => startActivity(appState), BTN1, { repeat: true, edge: 'falling' }); -setWatch(() => stopActivity(appState), BTN3, { repeat: true, edge: 'falling' }); diff --git a/apps/banglerun/src/display.ts b/apps/banglerun/src/display.ts deleted file mode 100644 index 528890c35..000000000 --- a/apps/banglerun/src/display.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; -declare var g: any; - -const STATUS_COLORS = { - 'STOP': 0xF800, - 'PAUSE': 0xFFE0, - 'RUN': 0x07E0, -} - -function initDisplay(state: AppState): void { - Bangle.loadWidgets(); - Bangle.on('lcdPower', (on: boolean) => { - state.drawing = on; - if (on) { - drawAll(state); - } - }); - drawAll(state); -} - -function drawBackground(): void { - g.clear(); - g.setColor(0xC618); - g.setFont('6x8', 2); - g.setFontAlign(0, -1, 0); - g.drawString('DIST (KM)', 60, 32); - g.drawString('TIME', 172, 32); - g.drawString('PACE', 60, 92); - g.drawString('HEART', 172, 92); - g.drawString('STEPS', 60, 152); - g.drawString('CADENCE', 172, 152); -} - -function drawValue(value: string, x: number, y: number) { - g.setColor(0x0000); - g.fillRect(x - 60, y, x + 60, y + 30); - g.setColor(0xFFFF); - g.drawString(value, x, y); -} - -function draw(state: AppState): void { - g.setFontVector(30); - g.setFontAlign(0, -1, 0); - - drawValue(formatDistance(state.distance), 60, 55); - drawValue(formatTime(state.duration), 172, 55); - drawValue(formatPace(state.speed), 60, 115); - drawValue(state.hr.toFixed(0), 172, 115); - drawValue(state.steps.toFixed(0), 60, 175); - drawValue(state.cadence.toFixed(0), 172, 175); - - g.setFont('6x8', 2); - - g.setColor(state.gpsValid ? 0x07E0 : 0xF800); - g.fillRect(0, 216, 80, 240); - g.setColor(0x0000); - g.drawString('GPS', 40, 220); - - g.setColor(0xFFFF); - g.fillRect(80, 216, 160, 240); - g.setColor(0x0000); - g.drawString(formatClock(new Date()), 120, 220); - - g.setColor(STATUS_COLORS[state.status]); - g.fillRect(160, 216, 230, 240); - g.setColor(0x0000); - g.drawString(state.status, 200, 220); - - g.setFont("6x8").setFontAlign(0,0,1).setColor(-1); - if (state.status === ActivityStatus.Paused) { - g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1); - } else if (state.status === ActivityStatus.Running) { - g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1); - } else { - g.drawString("START",236,60,1).drawString(" ",236,180,1); - } -} - -function drawAll(state: AppState) { - drawBackground(); - draw(state); - Bangle.drawWidgets(); -} - -function formatClock(date: Date): string { - return ('0' + date.getHours()).substr(-2) + ':' + ('0' + date.getMinutes()).substr(-2); -} - -function formatDistance(meters: number): string { - return (meters / 1000).toFixed(2); -} - -function formatPace(speed: number): string { - if (speed < 0.1667) { - return `__'__"`; - } - const pace = Math.round(1000 / speed); - const min = Math.floor(pace / 60); - const sec = pace % 60; - return ('0' + min).substr(-2) + `'` + ('0' + sec).substr(-2) + `"`; -} - -function formatTime(time: number): string { - const seconds = Math.round(time); - const hrs = Math.floor(seconds / 3600); - const min = Math.floor(seconds / 60) % 60; - const sec = seconds % 60; - return (hrs ? hrs + ':' : '') + ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2); -} - -export { - draw, - drawAll, - drawBackground, - drawValue, - formatClock, - formatDistance, - formatPace, - formatTime, - initDisplay, -}; diff --git a/apps/banglerun/src/gps.ts b/apps/banglerun/src/gps.ts deleted file mode 100644 index 1886ecfb2..000000000 --- a/apps/banglerun/src/gps.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { draw } from './display'; -import { updateLog } from './log'; -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; - -interface GpsEvent { - lat: number; - lon: number; - alt: number; - speed: number; - hdop: number; - fix: number; -} - -const EARTH_RADIUS = 6371008.8; - -function initGps(state: AppState): void { - Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps)); - Bangle.setGPSPower(1); -} - -function readGps(state: AppState, gps: GpsEvent): void { - state.lat = gps.lat; - state.lon = gps.lon; - state.alt = gps.alt; - state.vel = gps.speed / 3.6; - state.fix = gps.fix; - state.dop = gps.hdop; - state.gpsValid = state.fix > 0; - - updateGps(state); - draw(state); - - /* Only log GPS data every 5 secs if we - have a fix and we're running. */ - if (state.gpsValid && - state.status === ActivityStatus.Running && - state.timeSinceLog > 5) { - state.timeSinceLog = 0; - updateLog(state); - } -} - -function updateGps(state: AppState): void { - const t = Date.now(); - let dt = (t - state.t) / 1000; - if (!isFinite(dt)) dt=0; - state.t = t; - state.timeSinceLog += dt; - - if (state.status === ActivityStatus.Running) { - state.duration += dt; - } - - if (!state.gpsValid) { - return; - } - - const r = EARTH_RADIUS + state.alt; - const lat = state.lat * Math.PI / 180; - const lon = state.lon * Math.PI / 180; - const x = r * Math.cos(lat) * Math.cos(lon); - const y = r * Math.cos(lat) * Math.sin(lon); - const z = r * Math.sin(lat); - - if (!state.x) { - state.x = x; - state.y = y; - state.z = z; - return; - } - - const dx = x - state.x; - const dy = y - state.y; - const dz = z - state.z; - const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz); - - state.x = x; - state.y = y; - state.z = z; - - if (state.status === ActivityStatus.Running) { - state.distance += dpMag; - state.speed = (state.distance / state.duration) || 0; - state.cadence = (60 * state.steps / state.duration) || 0; - } -} - -export { initGps, readGps, updateGps }; diff --git a/apps/banglerun/src/hrm.ts b/apps/banglerun/src/hrm.ts deleted file mode 100644 index 08dd237a7..000000000 --- a/apps/banglerun/src/hrm.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AppState } from './state'; - -interface HrmData { - bpm: number; - confidence: number; - raw: string; -} - -declare var Bangle: any; - -function initHrm(state: AppState) { - Bangle.on('HRM', (hrm: HrmData) => updateHrm(state, hrm)); - Bangle.setHRMPower(1); -} - -function updateHrm(state: AppState, hrm: HrmData) { - if (hrm.confidence === 0) { - return; - } - - const dHr = hrm.bpm - state.hr; - const hrError = Math.abs(dHr) + 101 - hrm.confidence; - const hrGain = (state.hrError / (state.hrError + hrError)) || 0; - - state.hr += dHr * hrGain; - state.hrError += (hrError - state.hrError) * hrGain; -} - -export { initHrm, updateHrm }; diff --git a/apps/banglerun/src/log.ts b/apps/banglerun/src/log.ts deleted file mode 100644 index b6714e407..000000000 --- a/apps/banglerun/src/log.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AppState } from './state'; - -declare var require: any; - -function initLog(state: AppState): void { - const datetime = new Date().toISOString().replace(/[-:]/g, ''); - const date = datetime.substr(2, 6); - const time = datetime.substr(9, 6); - const filename = `banglerun_${date}_${time}`; - state.file = require('Storage').open(filename, 'w'); - state.fileWritten = false; -} - -function updateLog(state: AppState): void { - if (!state.fileWritten) { - state.file.write([ - 'timestamp', - 'latitude', - 'longitude', - 'altitude', - 'duration', - 'distance', - 'heartrate', - 'steps', - ].join(',') + '\n'); - state.fileWritten = true; - } - state.file.write([ - Date.now().toFixed(0), - state.lat.toFixed(6), - state.lon.toFixed(6), - state.alt.toFixed(2), - state.duration.toFixed(0), - state.distance.toFixed(2), - state.hr.toFixed(0), - state.steps.toFixed(0), - ].join(',') + '\n'); -} - -export { initLog, updateLog }; diff --git a/apps/banglerun/src/state.ts b/apps/banglerun/src/state.ts deleted file mode 100644 index 14ef2dc5d..000000000 --- a/apps/banglerun/src/state.ts +++ /dev/null @@ -1,85 +0,0 @@ -enum ActivityStatus { - Stopped = 'STOP', - Paused = 'PAUSE', - Running = 'RUN', -} - -interface AppState { - // GPS NMEA data - fix: number; - lat: number; - lon: number; - alt: number; - vel: number; - dop: number; - gpsValid: boolean; - - // Absolute position data - x: number; - y: number; - z: number; - // Last fix time - t: number; - // Last time we saved log info - timeSinceLog : number; - - // HRM data - hr: number, - hrError: number, - - // Logger data - file: File; - fileWritten: boolean; - - // Drawing data - drawing: boolean; - - // Activity data - status: ActivityStatus; - duration: number; - distance: number; - speed: number; - steps: number; - cadence: number; -} - -interface File { - read: Function; - write: Function; - erase: Function; -} - -function initState(): AppState { - return { - fix: NaN, - lat: NaN, - lon: NaN, - alt: NaN, - vel: NaN, - dop: NaN, - gpsValid: false, - - x: NaN, - y: NaN, - z: NaN, - t: NaN, - timeSinceLog : 0, - - hr: 60, - hrError: 100, - - file: null, - fileWritten: false, - - drawing: false, - - status: ActivityStatus.Stopped, - duration: 0, - distance: 0, - speed: 0, - steps: 0, - cadence: 0, - } -} - -export { ActivityStatus, AppState, File, initState }; diff --git a/apps/banglerun/src/step.ts b/apps/banglerun/src/step.ts deleted file mode 100644 index c7fcb61ea..000000000 --- a/apps/banglerun/src/step.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ActivityStatus, AppState } from './state'; - -declare var Bangle: any; - -function initStep(state: AppState) { - Bangle.on('step', () => updateStep(state)); -} - -function updateStep(state: AppState) { - if (state.status === ActivityStatus.Running) { - state.steps += 1; - } -} - -export { initStep, updateStep }; diff --git a/apps/banglerun/tsconfig.json b/apps/banglerun/tsconfig.json deleted file mode 100644 index a341a5a5e..000000000 --- a/apps/banglerun/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "es2015", - "noImplicitAny": true, - "target": "es2015" - }, - "include": [ - "src" - ] -} diff --git a/apps/banglerun/tsconfig.spec.json b/apps/banglerun/tsconfig.spec.json deleted file mode 100644 index 136ae137b..000000000 --- a/apps/banglerun/tsconfig.spec.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "noImplicitAny": true, - "target": "es2015" - }, - "include": [ - "test" - ] -} diff --git a/apps/bthrv/ChangeLog b/apps/bthrv/ChangeLog index e144fd8f9..eefadac78 100644 --- a/apps/bthrv/ChangeLog +++ b/apps/bthrv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Write available data on reset or kill +0.03: Buzz short on every finished measurement and longer if all are done diff --git a/apps/bthrv/app.js b/apps/bthrv/app.js index 067c84f56..fbd0e2d05 100644 --- a/apps/bthrv/app.js +++ b/apps/bthrv/app.js @@ -75,7 +75,6 @@ function write(){ data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues; data += "\n"; file.write(data); - Bangle.buzz(500); } function onBtHrm(e) { @@ -87,6 +86,11 @@ function onBtHrm(e) { if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){ hrvValues[hrvSlots[currentSlot]] = hrv; currentSlot++; + if (currentSlot == hrvSlots.length){ + Bangle.buzz(500) + } else { + Bangle.buzz(50); + } } } diff --git a/apps/bthrv/metadata.json b/apps/bthrv/metadata.json index 183008034..7c57be682 100644 --- a/apps/bthrv/metadata.json +++ b/apps/bthrv/metadata.json @@ -2,7 +2,7 @@ "id": "bthrv", "name": "Bluetooth Heart Rate variance calculator", "shortName": "BT HRV", - "version": "0.02", + "version": "0.03", "description": "Calculates HRV from a a BT HRM with interval data", "icon": "app.png", "type": "app", diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 7165f8521..46eddd32b 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -20,3 +20,6 @@ Color depending on value (green -> red, red -> green) option Good HRM value will not be overwritten so fast anymore 0.10: Use roboto font for time, date and day of week and center align them +0.11: New color option: foreground color + Improve performance, reduce memory usage + Small optical adjustments diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index 903c7bdb2..a6aa1a8b1 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -3,23 +3,8 @@ const storage = require("Storage"); const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"); -const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); -const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"); -const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); -const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); -const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); -const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); -const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); -const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); -const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); -const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); -const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); - -const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); -const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { // Actual height 39 (40 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); @@ -67,7 +52,7 @@ const colorGreen = '#008000'; const colorBlue = '#0000ff'; const colorYellow = '#ffff00'; const widgetOffset = showWidgets ? 24 : 0; -const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date +const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date const h = g.getHeight() - widgetOffset; const w = g.getWidth(); const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; @@ -103,21 +88,24 @@ const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; const iconOffset = circleCount == 3 ? 6 : 8; const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; +function hideWidgets() { + /* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ + if (WIDGETS && typeof WIDGETS === "object") { + for (let wd of WIDGETS) { + wd.draw = () => {}; + wd.area = ""; + } + } +} function draw() { g.clear(true); if (!showWidgets) { - /* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ - if (WIDGETS && typeof WIDGETS === "object") { - for (let wd of WIDGETS) { - wd.draw = () => {}; - wd.area = ""; - } - } + hideWidgets(); } else { Bangle.drawWidgets(); } @@ -129,7 +117,7 @@ function draw() { g.setFontRobotoRegular50NumericOnly(); g.setFontAlign(0, -1); g.setColor(colorFg); - g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8); + g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6); now = Math.round(new Date().getTime() / 1000); // date & dow @@ -138,10 +126,19 @@ function draw() { g.drawString(locale.date(new Date()), w / 2, h2); g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); - drawCircle(1); - drawCircle(2); - drawCircle(3); - if (circleCount >= 4) drawCircle(4); + // draw the circles a little bit delayed so we decrease the blocking time + setTimeout(function() { + drawCircle(1); + }, 1); + setTimeout(function() { + drawCircle(2); + }, 1); + setTimeout(function() { + drawCircle(3); + }, 1); + setTimeout(function() { + if (circleCount >= 4) drawCircle(4); + }, 1); } function drawCircle(index) { @@ -248,6 +245,9 @@ function getGradientColor(color, percent) { const colorList = [ '#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000' ]; + if (color == "fg") { + color = colorFg; + } if (color == "green-red") { const colorIndex = Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00"; @@ -325,6 +325,8 @@ function drawStepsDistance(w) { function drawHeartRate(w) { if (!w) w = getCircleXPosition("hr"); + const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); + drawCircleBackground(w); const color = getCircleColor("hr"); @@ -349,6 +351,8 @@ function drawBattery(w) { if (!w) w = getCircleXPosition("battery"); const battery = E.getBattery(); + const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); + drawCircleBackground(w); let color = getCircleColor("battery"); @@ -426,6 +430,10 @@ function drawSunProgress(w) { if (!w) w = getCircleXPosition("sunprogress"); const percent = getSunProgress(); + // sunset icons: + const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); + const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); + drawCircleBackground(w); const color = getCircleColor("sunprogress"); @@ -559,6 +567,18 @@ function windAsBeaufort(windInKmh) { */ function getWeatherIconByCode(code) { const codeGroup = Math.round(code / 100); + + // weather icons: + const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); + const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); + const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); + const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); + const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); + const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); + const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); + const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); + const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); + switch (codeGroup) { case 2: return weatherStormy; @@ -823,7 +843,6 @@ if (isCircleEnabled("hr")) { enableHRMSensor(); } - Bangle.setUI("clock"); Bangle.loadWidgets(); diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index 3279ec2cf..b16f14c06 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.10", + "version":"0.11", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index bec539376..0b9e94aca 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -14,8 +14,10 @@ const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; - const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; - const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", + "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; + const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", + "cyan", "white", "black", "green->red", "red->green", "foreground"]; const weatherData = ["empty", "humidity", "wind"]; diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index 9c62e637b..032edc9b5 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -5,3 +5,4 @@ 0.23: Customizer! Unused fonts no longer take up precious memory. 0.24: Added previews to the customizer. 0.25: Fixed a bug that would let widgets change the color of the clock. +0.26: Time formatted to locale diff --git a/apps/contourclock/lib.js b/apps/contourclock/lib.js index 65a4622f4..c4f927953 100644 --- a/apps/contourclock/lib.js +++ b/apps/contourclock/lib.js @@ -1,3 +1,11 @@ +var is12; +function getHours(d) { + var h = d.getHours(); + if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"]; + if (!is12) return h; + return (h%12==0) ? 12 : h%12; +} + exports.drawClock = function(fontIndex) { var digits = []; fontFile=require("Storage").read("contourclock-"+Math.abs(parseInt(fontIndex+0.5))+".json"); @@ -15,8 +23,8 @@ exports.drawClock = function(fontIndex) { var y = g.getHeight()/2-digits[0].height/2; var date = new Date(); g.clearRect(0,38,g.getWidth()-1,138); - d1=parseInt(date.getHours()/10); - d2=parseInt(date.getHours()%10); + d1=parseInt(getHours(date)/10); + d2=parseInt(getHours(date)%10); d3=10; d4=parseInt(date.getMinutes()/10); d5=parseInt(date.getMinutes()%10); diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index 54d799127..ec29c390b 100644 --- a/apps/contourclock/metadata.json +++ b/apps/contourclock/metadata.json @@ -1,7 +1,7 @@ { "id": "contourclock", "name": "Contour Clock", "shortName" : "Contour Clock", - "version":"0.25", + "version":"0.26", "icon": "app.png", "description": "A Minimalist clockface with large Digits. Now with more fonts!", "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], diff --git a/apps/crowclk/ChangeLog b/apps/crowclk/ChangeLog index 5560f00bc..b7e18abe3 100644 --- a/apps/crowclk/ChangeLog +++ b/apps/crowclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". diff --git a/apps/crowclk/crow_clock.js b/apps/crowclk/crow_clock.js index 97e5e61d9..d06369fa8 100644 --- a/apps/crowclk/crow_clock.js +++ b/apps/crowclk/crow_clock.js @@ -133,15 +133,6 @@ Bangle.on('lcdPower', (on) => { clearTimers(); } }); -Bangle.on('faceUp',function(up){ - //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); - if (up && !Bangle.isLCDOn()) { - //console.log("faceUp and LCD off"); - clearTimers(); - Bangle.setLCDPower(true); - } -}); - g.clear(); diff --git a/apps/crowclk/metadata.json b/apps/crowclk/metadata.json index 365393e6c..752e30fb0 100644 --- a/apps/crowclk/metadata.json +++ b/apps/crowclk/metadata.json @@ -1,7 +1,7 @@ { "id": "crowclk", "name": "Crow Clock", - "version": "0.01", + "version": "0.02", "description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face", "icon": "crow_clock.png", "screenshots": [{"url":"screenshot_crow.png"}], diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog new file mode 100644 index 000000000..254124d4e --- /dev/null +++ b/apps/daisy/ChangeLog @@ -0,0 +1,3 @@ +0.01: first release +0.02: added settings menu to change color +0.03: fix metadata.json to allow setting as clock diff --git a/apps/daisy/README.md b/apps/daisy/README.md new file mode 100644 index 000000000..3f22f5dd9 --- /dev/null +++ b/apps/daisy/README.md @@ -0,0 +1,29 @@ +# Daisy + + *A beautiful digital clock with large ring guage, idle timer and a + cyclic information line that includes, day, date, steps, battery, + sunrise and sunset times* + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) + +* Derived from `The Ring` proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel) +* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer +* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset) +* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location +* The screen is updated every minute to save battery power +* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use + +## Future Development +* Add a heart rate option in the information line that turns on when selected +* Use mini icons in the information line rather that text +* Add weather icons as per Pastel clock +* Add a lock icon to the screen + +## Screenshots +![](screenshot_daisy1.png) + +It is worth looking at the real thing though as the screenshot does not do it justice. +(Though I need to redo this photo at some point) +![](screenshot_daisy2.jpg) diff --git a/apps/daisy/app-icon.js b/apps/daisy/app-icon.js new file mode 100644 index 000000000..b577b3d5e --- /dev/null +++ b/apps/daisy/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///pf+sdR0n8CAkCwAcJhNgBI8KwALBgWgCo8NqAZHhNYktYloLKkoLHqwLByoLEgXoBYsrK4UDq0CqulrVVwGV4AXCquCBYYEBC4UC6tiBYeJq57DytayttvNW0tWHgclq2VtNpAYNYKQgiBBYIkBKgUK9Q8B9Nq1Nrqug1WgCoOqytq1/61NW1XVsALBq2ttbMB9N62olBKQNVtNvBYP61dVKgWlqtY34LB/wGBvQ8CEgIKBAAOVq7NDGwILD2/qBQWqAAILDAwUAlIzB1YLD9X1q+oytVqtbBYflA4NeBZVWlQjJ9A7LKZhrLQZS9Bqvq16bGWZXgZY2JZYcK1TjC9WrcYOAcYL7FpL7EAAMlq219NrRYNYBYeVrWV9t7q2lqwKCFwNi6utvVXxLuCBYWCGAIuBAgILCgdegVXBYPVwG1C4eohNWktYyvglYLCKgVeBYO1KQgLCrElvElBY94loBBBY/ghtghILGhSsBsECRgQZHBI5XCJ4kAA=")) diff --git a/apps/daisy/app.js b/apps/daisy/app.js new file mode 100644 index 000000000..2717f94db --- /dev/null +++ b/apps/daisy/app.js @@ -0,0 +1,483 @@ +var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); +const storage = require('Storage'); +const locale = require("locale"); +const SETTINGS_FILE = "daisy.json"; +const LOCATION_FILE = "mylocation.json"; +const h = g.getHeight(); +const w = g.getWidth(); +let settings; +let location; + +// variable for controlling idle alert +let lastStep = getTime(); +let warned = 0; +let idle = false; +let IDLE_MINUTES = 26; + +var pal1; // palette for 0-40% +var pal2; // palette for 50-100% +const infoWidth = 50; +const infoHeight = 14; +var drawingSteps = false; + +function log_debug(o) { + //print(o); +} + +// https://www.1001fonts.com/rounded-fonts.html?page=3 +Graphics.prototype.setFontBloggerSansLight46 = function(scale) { + // Actual height 46 (45 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAA/AAAAAAAAPwAAAAAAAD4AAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAH/gAAAAAAP/wAAAAAAf/gAAAAAAf/AAAAAAA//AAAAAAB/+AAAAAAD/8AAAAAAH/4AAAAAAH/wAAAAAAP/gAAAAAAf/gAAAAAA//AAAAAAB/+AAAAAAA/8AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///8AAAAP////4AAAP/////AAAH/////4AAD+AAAB/AAA8AAAAHwAAeAAAAA+AAHgAAAAHgADwAAAAB4AA8AAAAAPAAPAAAAADwADwAAAAA8AA8AAAAAPAAPAAAAADwAB4AAAAB4AAeAAAAAeAAHwAAAAPgAA/AAAAPwAAH/////4AAA/////8AAAH////+AAAAf///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAPAAAAAAAAHwAAAAAAAB4AAAAAAAA+AAAAAAAAfAAAAAAAAHgAAAAAAAD4AAAAAAAB8AAAAAAAAeAAAAAAAAPgAAAAAAADwAAAAAAAB//////4AAf//////AAH//////gAA//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAD4AAHAAAAD+AAD4AAAB/gAA8AAAB/4AAfAAAA/+AAHgAAAf3gAB4AAAPx4AA8AAAH4eAAPAAAD4HgADwAAB8B4AA8AAA+AeAAPAAAfAHgADwAAPgB4AA8AAHwAeAAHgAD4AHgAB4AD8AB4AAfAB+AAeAAD8B/AAHgAAf//gAB4AAH//wAAeAAAf/wAAHgAAB/wAAA4AAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AADgAAAAPAAB4AAAADwAAeAAAAA+AAHgAAAAHgAB4ABgAB4AAeAA8AAeAAHgA/AADwAB4AfwAA8AAeAP8AAPAAHgH/AADwAB4H7wAA8AAeD48AAPAAHh8PAAHgAB5+BwAB4AAe/AeAA+AAH/AHwAfAAB/gA/AfgAAfwAH//wAAHwAA//4AAA4AAH/8AAAAAAAf4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAD+AAAAAAAD/gAAAAAAH/4AAAAAAH/+AAAAAAP/ngAAAAAP/h4AAAAAf/AeAAAAAf/AHgAAAA/+AB4AAAA/+AAeAAAB/8AAHgAAA/8AAB4AAAP4AAAeAAAB4AAAHgAAAAAAAB4AAAAAAAAeAAAAAAP///4AAAAH////AAAAA////gAAAAP///4AAAAAAB4AAAAAAAAeAAAAAAAAHgAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAD4AA8AAD///gAPAAB///4AD4AAf//+AAeAAH+APAAHgAB4AHgAA4AAeAB4AAOAAHgAcAADwAB4AHAAA8AAeADwAAPAAHgAcAADwAB4AHAAA8AAeAB4AAeAAHgAeAAHgAB4AHwAD4AAeAA+AB8AAHgAP4B+AAB4AB///gAAOAAP//gAABAAA//wAAAAAAD/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAB///4AAAAD////wAAAD////+AAAB/////4AAA/gPgB/AAAfgDwAHwAAPgA8AA+AADwAeAAHgAB4AHgAB4AAeAB4AAfAAHgAeAADwABwAHgAA8AAcAB4AAPAAHAAeAAHwAB4AHgAB4AAeAB8AAeAAHgAPAAPgAB4AD8APwAAOAAfwP4AADgAD//8AAAAAAf/+AAAAAAB/+AAAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAB4AAAAAAAAeAAAAAAAAHgAAAAAAAB4AAAAA4AAeAAAAB/AAHgAAAB/wAB4AAAB/4AAeAAAD/4AAHgAAD/wAAB4AAH/wAAAeAAH/gAAAHgAP/gAAAB4AP/AAAAAeAf/AAAAAHgf+AAAAAB4/+AAAAAAe/8AAAAAAH/8AAAAAAB/4AAAAAAAf4AAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAA/AB/+AAAA/8B//wAAA//gf/+AAAf/8PgPgAAH4fngB8AAD4B/wAPgAA8AP8AB4AAeAB+AAeAAHgAfgADwAB4ADwAA8AAcAA8AAPAAHAAPAADwAB4ADwAA8AAeAB+AAPAAHgAfgAHgAB8AP8AB4AAPgH/AA+AAD8H54AfAAAf/8fgPwAAD/+D//4AAAf/Af/8AAAB/AD/+AAAAAAAP+AAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAf/wAAAAAAf/+AAAAAAP//4AAwAAH//+AAeAAD+APwAHgAA+AA+AB4AAfAAHgAOAAHgAB4ADwAB4AAPAA8AAeAADwAPAAHgAA8ADwAB4AAPAA8AAeAADwAPAAHgAA8AHgAB8AAeAB4AAPgAHgA+AAD8ADwA/AAAfwA8A/gAAD/wef/wAAAf////4AAAB////4AAAAH///wAAAAAD/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AB4AAAAAfgA/AAAAAH4APwAAAAB+AD4AAAAAPAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DRAcHBwcHBwcHBwcDQ=="), 56+(scale<<8)+(1<<16)); + return this; +}; + +Graphics.prototype.setFontRoboto20 = function(scale) { + // Actual height 21 (20 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAH/zA/+YAAAAAAAHwAAwAAHwAA+AAAAAAAAAAAQACDAAYbADP4B/8A/zAGYZADH4A/+A/7AHYYADCAAAAAAAQAeHgH4eBzgwMMHnhw88GGBw4wHj+AcPgAAAAAAAAAAB4AA/gAGMAAwhwGMcAfuABzgABzgAc+AOMYBhBAAMYAB/AAHwAAAAAHwD5+A/8YGPDAw8YGPzA/HYD4fAADwAB/AAOYAABAAAAHwAA4AAAAAAAAAAH/gD//B8A+cAA7AADAAAAAAAYAAbwAHHgHwf/4A/8AAAAEAABiAAGwAA8AA/AAH+AAGwAByAAEAAAAAAAMAABgAAMAABgAH/wA/+AAMAABgAAMAABgAAAAAAAIAAfAADwAAAABgAAMAABgAAMAABgAAAAAAAAAAAAADAAAYAAAAAAAAADgAB8AB+AA+AA+AA/AAHAAAgAAAAAAB8AB/8Af/wHAHAwAYGADAwAYHAHAf/wB/8AAAAAAAAAAABgAAcAADAAAYAAH//A//4AAAAAAAAAAAAAAAAAAAAABwDAeA4HAPAwHYGBzAwcYHHDAfwYB8DAAAYAAAAAAABgOAcBwHADAwwYGGDAwwYHPHAf/wB58AAAAAAAAADAAB4AAfAAPYAHjAB4YA8DAH//A//4AAYAADAAAAAAAAAEMA/xwH+HAxgYGMDAxgYGODAw/4GD+AAHAAAAAAAAAf8AP/wD2HA5wYGMDAxgYGOHAA/wAD8AAAAAAAAAAAGAAAwAAGADAwB4GB+Aw+AGfAA/gAHwAAwAAAAAAADAB5+Af/wHPDAwwYGGDAwwYHPHAfvwB58AAAAAAAAAAAB+AAf4AHDjAwMYGBjAwM4HDOAf/gB/4AAAAAAAAAAAAYDADAYAAAAAAAAAAYDAfAYHwAAAABAAAcAADgAA+AAGwAB3AAMYABjgAYMAAAAAAAAAAAAAAABmAAMwABmAAMwABmAAMwABmAAMwAAiAAAAAAAAAYMADjgAMYAB3AAGwAA2AADgAAcAABAAAAAAAAAMAADgAA4AAGBzAweYGHAA/wAD8AAEAAAAwAB/4A/PwOAGDgAYYPxmH/Mw4ZmMDMxgZmM+Mx/5mHDAYAIDgDAPBwAf8AAMAAAAAAAYAAfAAPwAP4AH+AH4wA8GAH4wAP2AAPwAAfwAAfAAAYAAAAAAAAAAA//4H//AwwYGGDAwwYGGDAwwYH/HAf/wB58AAAAADAAH/AD/+AcBwHADAwAYGADAwAYGADA4A4DweAODgAAAAAAAAAAAAAAH//A//4GADAwAYGADAwAYGADAYAwD4+AP/gAfwAAAAAAAAAAAH//A//4GDDAwYYGDDAwYYGDDAwYYGCDAgAYAAAAAAAH//A//4GDAAwYAGDAAwYAGDAAwYAGAAAAAAAAAAH/AD/8AcBwHAHAwAYGADAwYYGDDA4YYDz/AOfwAAAAAAAAAAA//4H//A//4ADAAAYAADAAAYAADAAAYAADAA//4H//AAAAAAAAAAAAAAA//4H//AAAAAAAAABAAAeAAB4AADAAAYAADAAAYAAHA//wH/8AAAAAAAAAAAAAAA//4H//AAcAAPAAD4AA/wAOPADg8A4B4GAHAgAYAAAAAAAH//A//4AADAAAYAADAAAYAADAAAYAADAAAAAAAA//4H//A+AAB+AAD8AAD8AAH4AAPAAH4AH4AD8AD8AA+AAH//A//4AAAAAAAH//A//4H//AeAAB8AADwAAPgAAeAAA8AADwH//A//4AAAAAAAAAAAH/AB/8AeDwHAHAwAYGADAwAYGADA4A4DweAP/gA/4AAAAAAAAAAAH//A//4GBgAwMAGBgAwMAGBgAwcAH/AAfwAA8AAAAAA/4AP/gDgOA4A4GADAwAYGADAwAYHAHgeD+B/8wD+GAAAAAAAAAAA//4H//AwYAGDAAwYAGDgAweAHH8Afz4B8HAAAIAAYAPDwD8OA5w4GGDAwwYGHDAwYYHDnAePwBw8AAAAGAAAwAAGAAAwAAGAAA//4H//AwAAGAAAwAAGAAAwAAAAAAAAAH/4A//wAAPAAAYAADAAAYAADAAAYAAPA//wH/8AAAAAAAAgAAHAAA/AAB/AAD+AAD+AAD4AAfAAfwAfwAfwAH4AA4AAEAAA+AAH/AAH/gAD/AAD4AD+AH+AH8AA+AAH+AAD+AAD/AAD4AH/AP/AH+AA8AAAAAAAAAGADA4A4HweAPPgA/wAB8AAfwAPvgDweA8B4GADAAAIGAAA4AAHwAAPgAAfAAA/4AH/AD4AB8AA+AAHgAAwAAAAAAAAAGADAwB4GAfAwPYGDzAx4YGeDA/AYHwDA4AYGADAAAAAAAA///3//+wAA2AAGAAAGAAA+AAD8AAD8AAD4AAH4AAHgAAMAAAAwAA2AAG///3//+AAAAAAAAAAAOAAHwAD4AA8AAD8AADwAAGAAAAAAABgAAMAABgAAMAABgAAMAABgAAMAABgAAAEAAAwAADAAAIAAAAAAAAAAEeABn4Ad3ADMYAZjADMYAZmAB/4AP/AAAAAAAA//4H//ABgwAYDADAYAYDADg4AP+AA/gABwAAAAAAAAA/gAP+ADg4AYDADAYAYDADAYAOOABxwAAAAAEAAH8AB/wAcHADAYAYDADAYAcDA//4H//AAAAAAAAAAAAH8AB/wAdnADMYAZjADMYAZjAB84AHmAAMAAMAABgAB//gf/8HMAAxgAGIAAAAAAH8IB/zAcHMDAZgYDMDAZgcHcD//Af/wAAAAAAAAAAH//A//4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAGf/Az/4AAAAAAAAAAMz//mf/4AAAAAAAAAAH//A//4ABwAAeAAH4ABzwAcPACAYAABAAAAAAAA//4H//AAAAAAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/ABgAAYAADAAAYAADgAAP/AA/4AAAAAAAAf/AD/4AMAADAAAYAADAAAcAAD/4AP/AAAAAAAAAAAAH8AB/wAcHADAYAYDADAYAYDADx4AP+AA/gAAAAAAAAf/8D//gYDADAYAYDADAYAcHAB/wAH8AAEAAAAAAEAAH8AB/wAcHADAYAYDADAYAYDAD//gf/8AAAAAAAAAAAf/AD/4AcAADAAAYAACAAAAEAB5wAfnADMYAZjADGYAYzADn4AOeAAAAAAAADAAAYAAf/wD//ADAYAYDAAAAAAAAD/gAf/AAA4AADAAAYAADAAAwAf/AD/4AAAAAAAAYAAD4AAP4AAP4AAPAAH4AH4AD8AAcAAAAAAQAADwAAf4AAf4AAPAAP4AP4ADwAAfgAA/gAA/AAD4AH+AD+AAeAAAAAAAAACAYAcHADzwAH8AAfAAH8ADx4AcHACAIAcAMD4BgP4MAP/AAPwAP4AP4AD4AAcAAAAAAAAADAYAYHADD4AY7ADOYAfjADwYAcDADAYAAAAADAAA4AH//B/v8cABzAACAAAH//w//+AAAAAAACAACcAAx/n+H//AA4AAHAAAAAAAAAAAAAOAADgAAYAADAAAcAABgAAGAAAwAAGAADwAAcAAAAA"), 32, atob("BQUHDQwPDQQHBwkMBAYGCQwMDAwMDAwMDAwFBAsMCwoTDg0ODgwMDg8GDA0LEg8ODQ4NDA0ODRMNDQ0GCQYJCQYLDAsMCwcMDAUFCwUSDAwMDAcLBwwKEAoKCgcFBw4A"), 21+(scale<<8)+(1<<16)); + return this; +}; + +function assignPalettes() { + // palette for 0-40% + pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]); + // palette for 50-100% + pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]); +} + +function setSmallFont20() { + g.setFontRoboto20(); +} + +function setLargeFont() { + g.setFontBloggerSansLight46(1); +} + +function setSmallFont() { + g.setFont('Vector', 16); +} + +function getSteps() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return 0; + } +} + +/////////////// sunrise / sunset ///////////////////////////// + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.gy = settings.gy||'#020'; + settings.fg = settings.fg||'#0f0'; + settings.idle_check = settings.idle_check||true; + assignPalettes(); +} + +// requires the myLocation app +function loadLocation() { + location = require("Storage").readJSON(LOCATION_FILE,1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; + location.location = location.location||"London"; +} + +function extractTime(d){ + var h = d.getHours(), m = d.getMinutes(); + return(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2)); +} + +var sunRise = "00:00"; +var sunSet = "00:00"; +var drawCount = 0; + +function updateSunRiseSunSet(now, lat, lon, line){ + // get today's sunlight times for lat/lon + var times = SunCalc.getTimes(new Date(), lat, lon); + + // format sunrise time from the Date object + sunRise = extractTime(times.sunrise); + sunSet = extractTime(times.sunset); +} + +const infoData = { + ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, + ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, + ID_SR: { calc: () => 'Sunrise: ' + sunRise }, + ID_SS: { calc: () => 'Sunset: ' + sunSet }, + ID_STEP: { calc: () => 'Steps: ' + getSteps() }, + ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' } +}; + +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + +// putting into 1 function like this, rather than individual variables +// reduces ram usage from 70%-13% +function getGaugeImage(p) { + // p0 + if (p < 2) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLLLzWEABLgbVgohEGopYaiofDBihWVHJpYYDgYPbKx1ACJhYZIwT4OcAZWYHyRYUIgQXQH4RqOThCXUYRpCHNyQVVQQTwVQiSZWIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p2 + if (p >= 2 && p < 4) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette: pal1, + buffer : require("heatshrink").decompress(atob("AH4A/ADNUFE8FqtVq2q1AqkFIIrDAAOAFMEBFQYrE1WgKsYrGLL4qFFY2pqDWeFZdUVkAhCAQMKFYdVLDUVFQYMHlWq0oMJKyoOJlQrCLDBWDB5clB5xWOoARMCARYWKwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p4 + if (p >= 4 && p < 7) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFY2loAqjFY1VqDWeFZdUVkAhEhQrDLDcVFQYMHlQrCBhBWVHJpYYDgYPbKx1ACJhYZIwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p7 + if (p >= 7 && p < 10) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgWlKzVACJgrCqBWYawgAJcAOlNBhWMCZ8qFYJYUgoqBC6ECFYJqOAApWSS4jCNQQ5uSCqqCCeCqESFQKZUIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A==")) + }; + + // p10 + if (p >= 10 && p < 20) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAOkQSdUFacK1WloCCSCaAAEFYKaQQSyEC0pvQirZTbomlIh6CYZAZFOQTBxDQhyCYOQhoPQS4bQHaBzaVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p20 + if (p >= 20 && p < 30) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4AWgNVoAEGAERSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A=")) + }; + + // p30 + if (p >= 30 && p < 40) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccFawkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) + }; + + // p40 + if (p >= 40 && p < 50) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccKFYkVFcwFDitVFccqFYkFFcuoFeNAFcWqFYkBFcugFYtQFUMCFYsAFcuAFYtUFcMKFY0VFcgHFitVFcMqFY0FFceoFY9AFcGqFY0BqtQFT8C1WgFeMAqtUFb8K1WAFY7cglQrIioriBI8FqtAFb2q1ArJbjzaBFZEBbj7aB0ALIFcLaHbkLaJFYbcd1QrKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjUBQR6EaiqCPQjVVQSATCqtUFSZvB1WACiSEUY4KCQQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p50 + if (p >= 50 && p < 60) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjSCQQjSCQQjSCRAAIrB1AqTgorBoAUQQiyCSQgjdSbISCRQgZYSKwKCSQghYQKwSCSQghYQKwSCTAAMqFYOoCJsFFQNVFShYEwARMFQRWVLAiFMQIRWWLAosKFQZWXLAosIFQZWYLAzgFawZWbAAMKFgmq1IoEAANUFTQABFZtAFbgsFFYwqeWQorFVjZZJFYhVfcAwrCazoA/AHI")) + }; + + // p60 + if (p >= 60 && p < 70) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAelNBqCLVxqEC0oRPQS6EC0oSQQSyECFYKEVQSIABFYI/QAAcFFYJDRCgSCmYYjdSCqqYCLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p70 + if (p >= 70 && p < 80) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZAgoAggNVoAr/FbdUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; + + // p80 + if (p >= 80 && p < 90) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AcIdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; + + // p90 + if (p >= 90 && p < 100) return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESquq1ArTgqESNgOqwArTIYKERH4KCUQigSBbKTdGCKKCVQiTCCFSyERCALBQQjAPBoArXDZ7ARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) + }; + + // p100 + return { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLFjzWEABLgbVgohEGoqyaiofDBihWVHJpYYDgYPbKxz5NLDJGCfBzgDKzA+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) + }; +} + +function draw() { + if (!idle) + drawClock(); + else + drawIdle(); + queueDraw(); +} + +function drawClock() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var da = date.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + var steps = getSteps(); + var p_steps = Math.round(100*(steps/10000)); + + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(0, 0, w, h); + g.drawImage(getGaugeImage(p_steps), 0, 0); + setLargeFont(); + + g.setColor(settings.fg); + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 1, h/2); + + g.setColor(g.theme.fg); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 1, h/2); + + setSmallFont(); + g.setFontAlign(0,0); // left aligned + g.drawString((infoData[infoMode].calc()), w/2, (3*h/4) - 4); + + // recalc sunrise / sunset every hour + if (drawCount % 60 == 0) + updateSunRiseSunSet(new Date(), location.lat, location.lon); + drawCount++; +} + +function drawSteps() { + if (drawingSteps) return; + drawingSteps = true; + setSmallFont(); + g.setFontAlign(0,0); + var steps = getSteps(); + g.setColor(g.theme.bg); + g.fillRect((w/2) - infoWidth, (3*h/4) - infoHeight, (w/2) + infoWidth, (3*h/4) + infoHeight); + g.setColor(g.theme.fg); + g.drawString('Steps ' + steps, w/2, (3*h/4) - 4); + drawingSteps = false; +} + +/* +Bangle.on('step', s => { + drawSteps(); +}); +*/ + + +///////////////// IDLE TIMER ///////////////////////////////////// + +function drawIdle() { + let mins = Math.round((getTime() - lastStep) / 60); + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + g.setColor(g.theme.fg); + setSmallFont20(); + g.setFontAlign(0, 0); + g.drawString('Last step was', w/2, (h/3)); + g.drawString(mins + ' minutes ago', w/2, 20+(h/3)); + dismissBtn.draw(); +} + +/////////////// BUTTON CLASS /////////////////////////////////////////// + +// simple on screen button class +function BUTTON(name,x,y,w,h,c,f,tx) { + this.name = name; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.color = c; + this.callback = f; + this.text = tx; +} + +// if pressed the callback +BUTTON.prototype.check = function(x,y) { + //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); + + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + setSmallFont20(); + g.setFontAlign(0, 0); + g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2)); + g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h)); +}; + +function dismissPrompt() { + idle = false; + warned = false; + lastStep = getTime(); + Bangle.buzz(100); + draw(); +} + +var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss"); + +Bangle.on('touch', function(button, xy) { + var x = xy.x; + var y = xy.y; + // adjust for outside the dimension of the screen + // http://forum.espruino.com/conversations/371867/#comment16406025 + if (y > h) y = h; + if (y < 0) y = 0; + if (x > w) x = w; + if (x < 0) x = 0; + + if (idle && dismissBtn.check(x, y)) return; +}); + +// if we get a step then we are not idle +Bangle.on('step', s => { + lastStep = getTime(); + // redraw if we had been idle + if (idle == true) { + dismissPrompt(); + } + idle = false; + warned = 0; +}); + +function checkIdle() { + log_debug("checkIdle()"); + if (!settings.idle_check) { + idle = false; + warned = false; + return; + } + + let hour = (new Date()).getHours(); + let active = (hour >= 9 && hour < 21); + //let active = true; + let dur = getTime() - lastStep; + + if (active && dur > IDLE_MINUTES * 60) { + drawIdle(); + if (warned++ < 3) { + buzzer(warned); + log_debug("checkIdle: warned=" + warned); + Bangle.setLocked(false); + } + idle = true; + } else { + idle = false; + warned = 0; + } +} + +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + log_debug("buzzer n=" + n); + + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + +/////////////////////////////////////////////////////////////////////////////// + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + checkIdle(); + 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; + } +}); + +Bangle.setUI("clockupdown", btn=> { + if (btn<0) prevInfo(); + if (btn>0) nextInfo(); + draw(); +}); + +loadSettings(); +loadLocation(); + +g.clear(); +Bangle.loadWidgets(); +/* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ +for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +draw(); diff --git a/apps/daisy/app.png b/apps/daisy/app.png new file mode 100644 index 000000000..89252e5df Binary files /dev/null and b/apps/daisy/app.png differ diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json new file mode 100644 index 000000000..f4a9fcbbe --- /dev/null +++ b/apps/daisy/metadata.json @@ -0,0 +1,18 @@ +{ "id": "daisy", + "name": "Daisy", + "version":"0.03", + "dependencies": {"mylocation":"app"}, + "description": "A clock based on the Pastel clock with large ring guage for steps", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot_daisy2.jpg"}], + "readme": "README.md", + "storage": [ + {"name":"daisy.app.js","url":"app.js"}, + {"name":"daisy.img","url":"app-icon.js","evaluate":true}, + {"name":"daisy.settings.js","url":"settings.js"} + ], + "data": [{"name":"daisy.json"}] +} diff --git a/apps/daisy/screenshot_daisy1.png b/apps/daisy/screenshot_daisy1.png new file mode 100644 index 000000000..afef3a424 Binary files /dev/null and b/apps/daisy/screenshot_daisy1.png differ diff --git a/apps/daisy/screenshot_daisy2.jpg b/apps/daisy/screenshot_daisy2.jpg new file mode 100644 index 000000000..fec6a4c7b Binary files /dev/null and b/apps/daisy/screenshot_daisy2.jpg differ diff --git a/apps/daisy/settings.js b/apps/daisy/settings.js new file mode 100644 index 000000000..044eee0d1 --- /dev/null +++ b/apps/daisy/settings.js @@ -0,0 +1,51 @@ +(function(back) { + const SETTINGS_FILE = "daisy.json"; + + // initialize with default settings... + let s = {'gy' : '#020', + 'fg' : '#0f0', + 'color': 'Green', + 'check_idle' : 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) || s; + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; + var fg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; + var gy_code = ['#020','#220','#022','#202','#200','#002']; + + E.showMenu({ + '': { 'title': 'Daisy Clock' }, + '< Back': back, + 'Colour': { + value: 0 | color_options.indexOf(s.color), + min: 0, max: 5, + format: v => color_options[v], + onchange: v => { + s.color = color_options[v]; + s.fg = fg_code[v]; + s.gy = gy_code[v]; + save(); + }, + }, + 'Idle Warning': { + value: !!s.idle_check, + format: v => v ? /*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + s.idle_check = v; + save(); + }, + } + }); +}) diff --git a/apps/edisonsball/ChangeLog b/apps/edisonsball/ChangeLog new file mode 100644 index 000000000..b71b8bb0d --- /dev/null +++ b/apps/edisonsball/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial version +0.02: Added BangleJS Two diff --git a/apps/edisonsball/README.md b/apps/edisonsball/README.md index b8e9ec106..a3b013b6d 100644 --- a/apps/edisonsball/README.md +++ b/apps/edisonsball/README.md @@ -1,4 +1,4 @@ -The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3. This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. +The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3 (Mapped to top touch and bottom touch on Bangle 2). This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. When you press the middle button on the side, the HR monitor starts, the alarm will trigger when your heart rate average drops to the limit you’ve set and has a certain level of steadiness that is determined by a assessing the variance over several readings - the sensitivity of this variance can be adjusted in a variable in the app's code under 'ADVANCED SETTINGS' if needed. The code also has a basic logging function which shows, in a CSV file, when you started the HR tracker and when the alarm was triggered. diff --git a/apps/edisonsball/app.js b/apps/edisonsball/app.js index 557155c9a..2aa317829 100644 --- a/apps/edisonsball/app.js +++ b/apps/edisonsball/app.js @@ -3,6 +3,8 @@ var lower_limit_BPM = 49; var upper_limit_BPM = 140; var deviation_threshold = 3; +var ISBANGLEJS1 = process.env.HWVERSION==1; + var target_heartrate = 70; var heartrate_set; @@ -33,25 +35,39 @@ function btn2Pressed() { } function update_target_HR(){ - g.clear(); - g.setColor("#00ff7f"); - g.setFont("6x8", 4); - g.setFontAlign(0,0); // center font + if (process.env.HWVERSION==1) { + g.setColor("#00ff7f"); + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font - g.drawString(target_heartrate, 120,120); - g.setFont("6x8", 2); - g.setFontAlign(-1,-1); - g.drawString("-", 220, 200); - g.drawString("+", 220, 40); - g.drawString("GO", 210, 120); - - g.setColor("#ffffff"); - g.setFontAlign(0,0); // center font - g.drawString("target HR", 120,90); - - g.setFont("6x8", 1); - g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + g.drawString(target_heartrate, 120,120); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 220, 200); + g.drawString("+", 220, 40); + g.drawString("GO", 210, 120); + + g.setColor("#ffffff"); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 120,90); + + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 120,170); + } else { + g.setFont("6x8", 4); + g.setFontAlign(0,0); // center font + g.drawString(target_heartrate, 88,88); + g.setFont("6x8", 2); + g.setFontAlign(-1,-1); + g.drawString("-", 160, 160); + g.drawString("+", 160, 10); + g.drawString("GO", 150, 88); + g.setFontAlign(0,0); // center font + g.drawString("target HR", 88,65); + g.setFont("6x8", 1); + g.drawString("if unsure, start with 7-10%\n less than waking average and\n adjust as required", 88,150); + } g.setFont("6x8",3); g.flip(); @@ -105,8 +121,13 @@ function checkHR() { average_HR = average(HR_samples).toFixed(0); stdev_HR = getStandardDeviation (HR_samples).toFixed(1); - g.drawString("HR: " + average_HR, 120,100); - g.drawString("STDEV: " + stdev_HR, 120,160); + if (ISBANGLEJS1) { + g.drawString("HR: " + average_HR, 120,100); + g.drawString("STDEV: " + stdev_HR, 120,160); + } else { + g.drawString("HR: " + average_HR, 88,60); + g.drawString("STDEV: " + stdev_HR, 88,90); + } HR_samples = []; if(average_HR < target_heartrate && stdev_HR < deviation_threshold){ @@ -131,12 +152,26 @@ function checkHR() { update_target_HR(); -setWatch(btn1Pressed, BTN1, {repeat:true}); -setWatch(btn2Pressed, BTN2, {repeat:true}); -setWatch(btn3Pressed, BTN3, {repeat:true}); +if (ISBANGLEJS1) { + // Bangle 1 + setWatch(btn1Pressed, BTN1, {repeat:true}); + setWatch(btn2Pressed, BTN2, {repeat:true}); + setWatch(btn3Pressed, BTN3, {repeat:true}); +} else { + // Bangle 2 + setWatch(btn2Pressed, BTN1, { repeat: true }); + Bangle.on('touch', function(zone, e) { + if (e.y < g.getHeight() / 2) { + btn1Pressed(); + } + if (e.y > g.getHeight() / 2) { + btn3Pressed(); + } + }); +} + Bangle.on('HRM',function(hrm) { - if(trigger_count < 2){ if (firstBPM) firstBPM=false; // ignore the first one as it's usually rubbish diff --git a/apps/edisonsball/metadata.json b/apps/edisonsball/metadata.json index f429c7b67..dfeb4451e 100644 --- a/apps/edisonsball/metadata.json +++ b/apps/edisonsball/metadata.json @@ -2,11 +2,11 @@ "id": "edisonsball", "name": "Edison's Ball", "shortName": "Edison's Ball", - "version": "0.01", + "version": "0.02", "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", "icon": "app-icon.png", - "tags": "", - "supports": ["BANGLEJS"], + "tags": "sleep,hyponagogia,quick,nap", + "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"edisonsball.app.js","url":"app.js"}, diff --git a/apps/hardalarm/ChangeLog b/apps/hardalarm/ChangeLog index 7e9b17f2a..dac7d317e 100644 --- a/apps/hardalarm/ChangeLog +++ b/apps/hardalarm/ChangeLog @@ -1,2 +1,3 @@ 0.01: Add a number to match to turn off alarm 0.02: Respect Quiet Mode +0.03: Fix hour/minute wrapping code for new menu system diff --git a/apps/hardalarm/app.js b/apps/hardalarm/app.js index 61467b421..0c72a2c8f 100644 --- a/apps/hardalarm/app.js +++ b/apps/hardalarm/app.js @@ -56,25 +56,25 @@ function editAlarm(alarmIndex) { } const menu = { '': { 'title': 'Alarms' }, - 'Hours': { - value: hrs, - onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + /*LANG*/'Hours': { + value: hrs, min : 0, max : 23, wrap : true, + onchange: v => hrs=v }, - 'Minutes': { - value: mins, - onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + /*LANG*/'Minutes': { + value: mins, min : 0, max : 59, wrap : true, + onchange: v => mins=v }, - 'Enabled': { + /*LANG*/'Enabled': { value: en, format: v=>v?"On":"Off", onchange: v=>en=v }, - 'Repeat': { + /*LANG*/'Repeat': { value: en, format: v=>v?"Yes":"No", onchange: v=>repeat=v }, - 'Auto snooze': { + /*LANG*/'Auto snooze': { value: as, format: v=>v?"Yes":"No", onchange: v=>as=v diff --git a/apps/hardalarm/metadata.json b/apps/hardalarm/metadata.json index 13a8fb920..1dab4501d 100644 --- a/apps/hardalarm/metadata.json +++ b/apps/hardalarm/metadata.json @@ -2,7 +2,7 @@ "id": "hardalarm", "name": "Hard Alarm", "shortName": "HardAlarm", - "version": "0.02", + "version": "0.03", "description": "Make sure you wake up! Count to the right number to turn off the alarm", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/intclock/ChangeLog b/apps/intclock/ChangeLog new file mode 100644 index 000000000..2ad07562f --- /dev/null +++ b/apps/intclock/ChangeLog @@ -0,0 +1 @@ +0.01: Initial commit diff --git a/apps/intclock/README.md b/apps/intclock/README.md new file mode 100644 index 000000000..7d31cdae2 --- /dev/null +++ b/apps/intclock/README.md @@ -0,0 +1,4 @@ +# Analogue Clock + +![](screenshot_analog.png) + diff --git a/apps/intclock/app-icon.js b/apps/intclock/app-icon.js new file mode 100644 index 000000000..8c62a192e --- /dev/null +++ b/apps/intclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/AH4Aq+GEkPh4E/BZMAh4LGw/h8MPBZHggIXJ/EB4ALI//h/4LHwk/BagA/ACY=")) diff --git a/apps/intclock/app.js b/apps/intclock/app.js new file mode 100644 index 000000000..1324a422c --- /dev/null +++ b/apps/intclock/app.js @@ -0,0 +1,60 @@ +Graphics.prototype.setFontUndo = function(scale) { + // Actual height 19 (20 - 2) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqKAqooCqigKqKAAAACgAAKAAAoAACgAAAAAAAAACgAAKAAAoAACgAAAAAAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAAAAAKgoAqCgKqKAqooKiioqKKiooqKiioKKqAoqoCgqAKCoAAAACoCgKgKAiCoCIKgKioAqKgACoAAKgACoAAKgACoqAKioCoIgKgiAoCoCgKgAAAAKqgAqqAKqqAqqoKiioqKKiooqKiioKAKAoAoCgCgKAKAAAACgAAKAAAoAACgAAAAAAKqgAqqAKqqAqqoCgCgKAKAAAACgCgKAKAqqoCqqgCqoAKqgAAAACigAKKAAqoACqgAqoACqgAKqAAqoAAqoACqgAKKAAooAAAAAAoAACgAAKAAAoAAqqACqoAKqgAqqAAKAAAoAACgAAKAAAAAAACoAAKgAAqAACoAAAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAAAAACgAAKAAAoAACgAAAAAAoAACgAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAKAAAoAAAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoAAAAKCqAoKoCiqgKKqAoooCiigKKKAoooCqigKqKAKgoAqCgAAAAoAoCgCgKAKAoAoCiigKKKAoooCiigKqqAqqoAqqACqoAAAACqAAKoAAqoACqgAAKAAAoAACgAAKAAqqoCqqgKqqAqqoAAAAKoKAqgoCqigKqKAoooCiigKKKAoooCiqgKKqAoKgCgqAAAAAKqgAqqAKqqAqqoCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKAAAoAACgAAKAAAqqoCqqgCqqAKqoAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCqqgKqqAKqgAqqAAAAAKgAAqAAKqAAqoACigAKKAAooACigAKqqAqqoAqqgCqqAAAACgCgKAKAoAoCgCgAAAAoAqCgCoKAKgoAqAAAAAKAAAoAAKoAAqgAKqgAqqAKgqAqCoCgCgKAKAAAAAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAAAAKAKAoAoCoKgKgqAKqgAqqAAqgACqAACgAAKAAAAACgAAKAAAoAACgAAKKKAoooCiigKKKAqoACqgACoAAKgAAAAACqqAKqoCqqgKqqAoAACgAAKCoAoKgCiqgKKqAoooCiigKqqAqqoAqqACqoAAAAAqqgCqqAqqoCqqgKKAAooACigAKKAAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAoooCiigKKKAoooCqqgKqqAKCgAoKAAAAAKqgAqqAKqqAqqoCgCgKAKAoAoCgCgKAKAoAoCgCgKAKAAAACqqgKqqAqqoCqqgKAKAoAoCoKgKgqAKqgAqqAAqgACqAAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCgCgKAKAoAoCgCgAAAAKqoAqqgKqqAqqoCigAKKAAooACigAKAAAoAACgAAKAAAAAAAqqACqoAqqoCqqgKAKAoAoCiigKKKAoqoCiqgKKqAoqoAAAAKqqAqqoCqqgKqqAAoAACgAAKAAAoACqqgKqqAqqoCqqgAAAAoAoCgCgKAKAoAoCqqgKqqAqqoCqqgKAKAoAoCgCgKAKAAAAAAKAAAoAACoAAKgAAKAAAoAACgAAKAqqoCqqgKqoAqqgAAAAKqqAqqoCqqgKqqACqAAKoACqoAKqgCoKgKgqAoAoCgCgAAAAqqgCqqAKqqAqqoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAKgAAqAAKqqAqqoCqqgKqqAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAqqoCqqgKqqAqqoAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoCigAKKAAooACigAKqAAqoAAqAACoAAAAAAqqACqoAqqoCqqgKAKAoAoCgKgKAqAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAooACigAKKgAoqACqqgKqqAKioAqKgAAAAKgoAqCgKqKAqooCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKqqAqqoCqqgKqqAoAACgAAKAAAoAAAAAAKqoAqqgCqqgKqqAAAoAACgAAKAAAoCqqgKqqAqqgCqqAAAAAqqACqoAKqoAqqgAAKgAAqAACoAAKgKqoAqqgCqoAKqgAAAACqqgKqqAqqoCqqgACoAAKgACoAAKgAAKgAAqAKqqAqqoCqqgKqqAAAACoKgKgqAqqoCqqgAqgACqAAKoAAqgAqqoCqqgKgqAqCoAAAAKoAAqgACqgAKqAAAqoACqgAKqAAqoCqgAKqAAqgACqAAAAAAoCoCgKgKCqAoKoCiqgKKqAqooCqigKoKAqgoCoCgKgKAAAACqqgKqqAqqoCqqgKAKAoAoAAAAKAAAoAACoAAKgAAKgAAqAAAqAACoAACoAAKgAAKgAAqAAAqAACoAACgAAKAAAACgCgKAKAqqoCqqgKqqAqqoAAA"), 32, atob("DQULDw0RDQUHBw0NBQ0FEQ0FDQ0NDQ0NDQ0FBQsNCw0RDQ0NDQ0NDQ0NDQ0NDw0NDQ0NDQ0NDQ8NDQ0HEQc="), 22+(scale<<8)+(1<<16)); + return this; +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2 - 10; + g.reset(); + // work out locale-friendly date/time + var date = new Date(); + var timeStr = require("locale").time(date,1).trim(); + var dateStr = require("locale").date(date).toUpperCase(); + + + // draw time + g.setFontAlign(0,0).setFont("Undo:3"); + g.clearRect(0,y-30,g.getWidth(),y+30); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += 40; + g.setFontAlign(0,0).setFont("Undo"); + g.clearRect(0,y-10,g.getWidth(),y+20); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/intclock/app.png b/apps/intclock/app.png new file mode 100644 index 000000000..dacbceb73 Binary files /dev/null and b/apps/intclock/app.png differ diff --git a/apps/intclock/metadata.json b/apps/intclock/metadata.json new file mode 100644 index 000000000..fff4c58b0 --- /dev/null +++ b/apps/intclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "intclock", + "name": "Interlaced Clock", + "version": "0.01", + "description": "A lightweight digital clock using an 'interlaced' font", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"intclock.app.js","url":"app.js"}, + {"name":"intclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/intclock/screenshot.png b/apps/intclock/screenshot.png new file mode 100644 index 000000000..33a459f1f Binary files /dev/null and b/apps/intclock/screenshot.png differ diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog index 23819e26c..52f705301 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial Release 0.02: Support for Bangle 2 0.03: Keep the date from being overwritten, use correct colour from theme for clearing +0.04: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index e116280af..2e4ba1ac4 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -239,15 +239,6 @@ Bangle.on('lcdPower', (on) => { } }); -Bangle.on('faceUp',function(up){ - //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); - if (up && !Bangle.isLCDOn()) { - //console.log("faceUp and LCD off"); - clearTimers(); - Bangle.setLCDPower(true); - } -}); - g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/matrixclock/metadata.json b/apps/matrixclock/metadata.json index 5e05698a5..122cee3a1 100644 --- a/apps/matrixclock/metadata.json +++ b/apps/matrixclock/metadata.json @@ -1,7 +1,7 @@ { "id": "matrixclock", "name": "Matrix Clock", - "version": "0.03", + "version": "0.04", "description": "inspired by The Matrix, a clock of the same style", "icon": "matrixclock.png", "screenshots": [{"url":"screenshot_matrix.png"}], diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 07da48198..bd40868a5 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -32,5 +32,8 @@ 0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) 0.21: Improve list readability on dark theme 0.22: Add Home Assistant icon -0.22: Allow repeat to be switched Off, so there is no buzzing repetition. - Also gave the widget a pixel more room to the right \ No newline at end of file + Allow repeat to be switched Off, so there is no buzzing repetition. + Also gave the widget a pixel more room to the right +0.23: Change message colors to match current theme instead of using green + Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured +0.24: Remove left-over debug statement diff --git a/apps/messages/README.md b/apps/messages/README.md index a355a58ac..cce4391e7 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -16,6 +16,9 @@ and `Messages`: * `Unread Timer` - when a new message is received we go into the Messages app. If there is no user input for this amount of time then the app will exit and return to the clock where a ringing bell will be shown in the Widget bar. +* `Min Font` - the minimum font size used when displaying messages on the screen. A bigger font +is chosen if there isn't much message text, but this specifies the smallest the font should get before +it starts getting clipped. ## New Messages diff --git a/apps/messages/app.js b/apps/messages/app.js index 96c48f02f..3f3b80080 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -21,13 +21,11 @@ */ var Layout = require("Layout"); +var settings = require('Storage').readJSON("messages.settings.json", true) || {}; var fontSmall = "6x8"; var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; -var colBg = g.theme.dark ? "#141":"#4f4"; -var colSBg1 = g.theme.dark ? "#121":"#cFc"; -var colSBg2 = g.theme.dark ? "#000":"#9F9"; // hack for 2v10 firmware's lack of ':size' font handling try { g.setFont("6x8:2"); @@ -77,9 +75,13 @@ function getPosImage() { function getNegImage() { return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="); } +/* +* icons should be 24x24px with 1bpp colors and transparancy +*/ function getMessageImage(msg) { if (msg.img) return atob(msg.img); var s = (msg.src||"").toLowerCase(); + if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); if (s=="facebook") return getFBIcon(); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); @@ -94,6 +96,7 @@ function getMessageImage(msg) { if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); if (s=="sms message") return getNotificationImage(); + if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); @@ -105,6 +108,7 @@ function getMessageImage(msg) { function getMessageImageCol(msg,def) { return { // generic colors, using B2-safe colors + "alarm": "#fff", "calendar": "#f00", "mail": "#ff0", "music": "#f0f", @@ -122,6 +126,7 @@ function getMessageImageCol(msg,def) { "outlook mail": "#0072c6", "skype": "#00aff0", "slack": "#e51670", + "threema": "#000", "telegram": "#0088cc", "twitter": "#1da1f2", "whatsapp": "#4fce5d", @@ -143,8 +148,8 @@ function showMapMessage(msg) { eta = m[2]; } else target=msg.body; layout = new Layout({ type:"v", c: [ - {type:"txt", font:fontMedium, label:target, bgCol:colBg, fillx:1, pad:2 }, - {type:"h", bgCol:colBg, fillx:1, c: [ + {type:"txt", font:fontMedium, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }, + {type:"h", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, c: [ {type:"txt", font:"6x8", label:"Towards" }, {type:"txt", font:fontLarge, label:street } ]}, @@ -168,27 +173,56 @@ function showMapMessage(msg) { } function showMusicMessage(msg) { + var updateLabelsInterval; + var trackScrollOffset = 0; + var artistScrollOffset = 0; + var albumScrollOffset = 0; + var trackName = ''; + var artistName = ''; + var albumName = ''; + function fmtTime(s) { var m = Math.floor(s/60); s = (parseInt(s%60)).toString().padStart(2,0); return m+":"+s; } + function reduceStringAndPad(text, offset, maxLen) { + var sliceLength = offset + maxLen > text.length ? text.length - offset : maxLen; + return text.substr(offset, sliceLength).padEnd(maxLen, " "); + } + function back() { + clearInterval(updateLabelsInterval); msg.new = false; saveMessages(); layout = undefined; checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1}); } + function updateLabels() { + trackName = reduceStringAndPad(msg.track, trackScrollOffset, 13); + artistName = reduceStringAndPad(msg.artist, artistScrollOffset, 21); + albumName = reduceStringAndPad(msg.album, albumScrollOffset, 21); + + trackScrollOffset++; + artistScrollOffset++; + albumScrollOffset++; + + if ((trackScrollOffset + 13) > msg.track.length) trackScrollOffset = 0; + if ((artistScrollOffset + 21) > msg.artist.length) artistScrollOffset = 0; + if ((albumScrollOffset + 21) > msg.album.length) albumScrollOffset = 0; + } + updateLabels(); + layout = new Layout({ type:"v", c: [ - {type:"h", fillx:1, bgCol:colBg, c: [ + {type:"h", fillx:1, bgCol:g.theme.bg2, col: g.theme.fg2, c: [ { type:"btn", src:getBackImage, cb:back }, { type:"v", fillx:1, c: [ - { type:"txt", font:fontMedium, label:msg.artist, pad:2 }, - { type:"txt", font:fontMedium, label:msg.album, pad:2 } + { type:"txt", font:fontMedium, bgCol:g.theme.bg2, label:artistName, pad:2, id:"artist" }, + { type:"txt", font:fontMedium, bgCol:g.theme.bg2, label:albumName, pad:2, id:"album" } ]} ]}, - {type:"txt", font:fontLarge, label:msg.track, fillx:1, filly:1, pad:2 }, + {type:"txt", font:fontLarge, bgCol:g.theme.bg, label:trackName, fillx:1, filly:1, pad:2, id:"track" }, Bangle.musicControl?{type:"h",fillx:1, c: [ {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause @@ -198,6 +232,14 @@ function showMusicMessage(msg) { ]}); g.clearRect(Bangle.appRect); layout.render(); + + updateLabelsInterval = setInterval(function() { + updateLabels(); + layout.artist.label = artistName; + layout.album.label = albumName; + layout.track.label = trackName; + layout.render(); + }, 400); } function showMessageScroller(msg) { @@ -214,7 +256,9 @@ function showMessageScroller(msg) { // 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=lines.length-2) @@ -270,13 +314,31 @@ function showMessage(msgid) { var title=msg.title, titleFont = fontLarge, lines; if (title) { var w = g.getWidth()-48; - if (g.setFont(titleFont).stringWidth(title) > w) - titleFont = fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) { + titleFont = fontBig; + if (settings.fontSize!=1 && g.setFont(titleFont).stringWidth(title) > w) + titleFont = fontMedium; + } if (g.setFont(titleFont).stringWidth(title) > w) { lines = g.wrapString(title, w); title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n"); } } + // If body of message is only two lines long w/ large font, use large font. + var body=msg.body, bodyFont = fontLarge; + if (body) { + var w = g.getWidth()-10; + if (g.setFont(bodyFont).stringWidth(body) > w * 2) { + bodyFont = fontBig; + if (settings.fontSize!=1 && g.setFont(bodyFont).stringWidth(body) > w * 3) + bodyFont = fontMedium; + } + if (g.setFont(bodyFont).stringWidth(body) > w) { + lines = g.setFont(bodyFont).wrapString(msg.body, w); + var maxLines = Math.floor((g.getHeight()-110) / g.getFontHeight()); + body = (lines.length>maxLines) ? lines.slice(0,maxLines).join("\n")+"..." : lines.join("\n"); + } + } function goBack() { msg.new = false; saveMessages(); // read mail cancelReloadTimeout(); // don't auto-reload to clock now @@ -303,27 +365,17 @@ function showMessage(msgid) { checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1}); }}); } - // If body of message is only two lines long w/ large font, use large font. - var body=msg.body, bodyFont = fontLarge, lines; - if (body) { - var w = g.getWidth()-48; - if (g.setFont(bodyFont).stringWidth(body) > w * 2) - bodyFont = fontMedium; - if (g.setFont(bodyFont).stringWidth(body) > w) { - lines = g.setFont(bodyFont).wrapString(msg.body, g.getWidth()-10); - body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n"); - } - } + layout = new Layout({ type:"v", c: [ - {type:"h", fillx:1, bgCol:colBg, c: [ + {type:"h", fillx:1, bgCol:g.theme.bg2, col: g.theme.fg2, c: [ { type:"btn", src:getMessageImage(msg), col:getMessageImageCol(msg), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, { type:"v", fillx:1, c: [ - {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:colBg, fillx:1, pad:2, halign:1 }, - title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{}, + {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, + title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, ]}, {type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2, cb:()=>{ @@ -374,9 +426,8 @@ function checkMessages(options) { c : Math.max(MESSAGES.length+1,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) draw : function(idx, r) {"ram" var msg = MESSAGES[idx-1]; - if (msg && msg.new) g.setBgColor(colBg); - else g.setBgColor((idx&1) ? colSBg1 : colSBg2); - g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); + if (msg && msg.new) g.setBgColor(g.theme.bgH).setColor(g.theme.fgH); + else g.setColor(g.theme.fg); if (idx==0) msg = {id:"back", title:"< Back"}; if (!msg) return; var x = r.x+2, title = msg.title, body = msg.body; @@ -391,18 +442,20 @@ function checkMessages(options) { .setColor(fg); // only color the icon x += 50; } - var m = msg.title+"\n"+msg.body; - if (msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); + var m = msg.title+"\n"+msg.body, longBody=false; if (title) g.setFontAlign(-1,-1).setFont(fontBig).drawString(title, x,r.y+2); if (body) { g.setFontAlign(-1,-1).setFont("6x8"); - var l = g.wrapString(body, r.w-14); + var l = g.wrapString(body, r.w-(x+14)); if (l.length>3) { l = l.slice(0,3); l[l.length-1]+="..."; } + longBody = l.length>2; g.drawString(l.join("\n"), x+10,r.y+20); } + if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); + g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items }, select : idx => { if (idx==0) load(); @@ -422,7 +475,7 @@ g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); setTimeout(() => { - var unreadTimeoutSecs = (require('Storage').readJSON("messages.settings.json", true) || {}).unreadTimeout; + var unreadTimeoutSecs = settings.unreadTimeout; if (unreadTimeoutSecs===undefined) unreadTimeoutSecs=60; if (unreadTimeoutSecs) unreadTimeout = setTimeout(function() { diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 7488c792e..0387c1972 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.22", + "version": "0.24", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/messages/screenshot.png b/apps/messages/screenshot.png index a95045400..a64416f7d 100644 Binary files a/apps/messages/screenshot.png and b/apps/messages/screenshot.png differ diff --git a/apps/messages/settings.js b/apps/messages/settings.js index 39abfc131..622177440 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -37,6 +37,12 @@ format: v => v?v+"s":/*LANG*/"Off", onchange: v => updateSetting("unreadTimeout", v) }, + /*LANG*/'Min Font': { + value: 0|settings().fontSize, + min: 0, max: 1, + format: v => [/*LANG*/"Small",/*LANG*/"Medium"][v], + onchange: v => updateSetting("fontSize", v) + }, }; E.showMenu(mainmenu); }) diff --git a/apps/messages/widget.js b/apps/messages/widget.js index 4a247b917..d9363573a 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -6,7 +6,6 @@ draw:function() { g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth); g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y); let settings = require('Storage').readJSON("messages.settings.json", true) || {}; - console.log("dingen ", typeof(settings.repeat), settings.repeat) if (settings.repeat===undefined) settings.repeat = 4; if (c<120 && (Date.now()-this.l)>settings.repeat*1000) { this.l = Date.now(); @@ -47,4 +46,4 @@ want to buzz but should still show that there are unread messages. */ if (global.MESSAGES===undefined) (function() { var messages = require("Storage").readJSON("messages.json",1)||[]; if (messages.some(m=>m.new)) WIDGETS["messages"].show(true); -})(); \ No newline at end of file +})(); diff --git a/apps/pooqroman/app.js b/apps/pooqroman/app.js index bed8ef3d2..fcb2437e1 100644 --- a/apps/pooqroman/app.js +++ b/apps/pooqroman/app.js @@ -353,13 +353,15 @@ const events = { let c, p, i, l = from - o, h = to - o; for (i = 0; (c = this.wall[i]).time < l; i++) ; for (; (c = this.wall[i]).time < h; i++) { - if ((p = c.time < t) ? c.past : c.future) + p = c.time < t; + if (p ? c.past : c.future) result = Math.min(result, f(c, new Date(c.time + o), p)); } l += o; h += o; t += o; for (i = 0; (c = this.fixed[i]).time < l; i++) ; for (; (c = this.fixed[i]).time < h; i++) { - if ((p = c.time < t) ? c.past : c.future) + p = c.time < t; + if (p ? c.past : c.future) result = Math.min(f(c, new Date(c.time), p)); } return result; diff --git a/apps/pooqroman/resourcer.js b/apps/pooqroman/resourcer.js index 69365018e..8b95dc834 100644 --- a/apps/pooqroman/resourcer.js +++ b/apps/pooqroman/resourcer.js @@ -60,7 +60,8 @@ const prepFont = (name, data) => { let width = m[2] == '*' ? null : +m[2]; let c = null, o = 0; lines.forEach((line, l) => { - if (m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line)) { + m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line); + if (m) { const h = m[2] == '='; if (m[1].length > desc || h && m[1].length != desc) throw new Error('Invalid descender height at ' + l); diff --git a/apps/pooqround/resourcer.js b/apps/pooqround/resourcer.js index 17c35a40d..6b969a102 100644 --- a/apps/pooqround/resourcer.js +++ b/apps/pooqround/resourcer.js @@ -60,7 +60,8 @@ const prepFont = (name, data) => { let width = m[2] == '*' ? null : +m[2]; let c = null, o = 0; lines.forEach((line, l) => { - if (m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line)) { + m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line); + if (m) { const h = m[2] == '='; if (m[1].length > desc || h && m[1].length != desc) throw new Error('Invalid descender height at ' + l); diff --git a/apps/ptlaunch/ChangeLog b/apps/ptlaunch/ChangeLog index 68b7d3e1c..eec3610ed 100644 --- a/apps/ptlaunch/ChangeLog +++ b/apps/ptlaunch/ChangeLog @@ -4,4 +4,5 @@ 0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible. 0.11: Respect theme colors. Fix: Do not pollute global space with internal variables ans functions in boot.js 0.12: Improve pattern detection code readability by PaddeK http://forum.espruino.com/profiles/117930/ -0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/ \ No newline at end of file +0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/ +0.14: Update setUI to work with new Bangle.js 2v13 menu style diff --git a/apps/ptlaunch/boot.js b/apps/ptlaunch/boot.js index 19a8f16cb..748d564f3 100644 --- a/apps/ptlaunch/boot.js +++ b/apps/ptlaunch/boot.js @@ -76,6 +76,7 @@ var sui = Bangle.setUI; Bangle.setUI = function (mode, cb) { sui(mode, cb); + if ("object"==typeof mode) mode = mode.mode; if (!mode) { Bangle.removeListener("drag", dragHandler); storedPatterns = {}; diff --git a/apps/ptlaunch/metadata.json b/apps/ptlaunch/metadata.json index 6c3870d24..0b6dce3d1 100644 --- a/apps/ptlaunch/metadata.json +++ b/apps/ptlaunch/metadata.json @@ -2,7 +2,7 @@ "id": "ptlaunch", "name": "Pattern Launcher", "shortName": "Pattern Launcher", - "version": "0.13", + "version": "0.14", "description": "Directly launch apps from the clock screen with custom patterns.", "icon": "app.png", "screenshots": [{"url":"manage_patterns_light.png"}], diff --git a/apps/rolex/ChangeLog b/apps/rolex/ChangeLog index 3bfed4a4e..a1e2cee0a 100644 --- a/apps/rolex/ChangeLog +++ b/apps/rolex/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial Release 0.02: Minor tweaks for light theme -0.03: Made images 2 bit and fixed theme honoring \ No newline at end of file +0.03: Made images 2 bit and fixed theme honoring +0.04: Fixed date font alignment and changed date font to match a real Rolex diff --git a/apps/rolex/README.md b/apps/rolex/README.md index 4c24aad0c..5339f1d0e 100644 --- a/apps/rolex/README.md +++ b/apps/rolex/README.md @@ -1,11 +1,13 @@ # Rolex -![](screenshot.png) +![](screenshot.png) ![](screenshot1.png) Created with the aid of the Espruino documentation and looking through many of the wonderful exising watchfaces that have been made. This has not been tested on a watch yet as I haven't aquired one but has been tested in the emulator. The hands don't rotate dead on center but they're as close as I could get them to. +Colour switches based on watch theme so if you want a white on black change your watch theme to dark, and for black on white change it to light. + Special thanks to: * rozek (for his updated widget draw code for utilization with background images) * Gordon Williams (Bangle.js, watchapps for reference code and documentation) diff --git a/apps/rolex/app.js b/apps/rolex/app.js index e409704fc..124cb6fee 100644 --- a/apps/rolex/app.js +++ b/apps/rolex/app.js @@ -28,6 +28,14 @@ var imgSec = { buffer : E.toArrayBuffer(atob("v/q//r/+v/qv+q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qv+v/6/D/wD8PDw8PwD/w///6v+qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqr+r//v///X/1X/Vf9V/1X/1///+//q/qq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq+qr6qvqq6qrqg==")) }; +/* use font closer to Rolex */ + +Graphics.prototype.setFontRolexFont = function(scale) { + // Actual height 12 (12 - 1) + this.setFontCustom(atob("AAAABAACAAAAAYAHgA4AOABgAAAAA/gD/gMBgQBAwGA/4A/gAAAAAAIBAQCB/8D/4AAQAAAAAAAAAwEDAYEBQIEgYxA/CA4MAAAAAAAAgIBAhCBCEDOYHvgCOAAAAAAABgANAAyAHEAf/A/+AAgABAAAAAAADBAcCBsECYIEYgIeAAAAAAAHwAfwB5wGggZBAjGBH4CDgAAACAAYAAgABAMCDwE+APgAYAAAAAAABxwH3wJwgRhA3iB54AhgAAAAAAPhA/iBDMCCQGHgH+AHwAAAAAAAQIAgQAAAAAA="), 46, atob("BAUJCQkJCQkJCQkJBQ=="), 17+(scale<<8)+(1<<16)); + return this; +}; + /* Set variables to get screen width, height and center points */ let W = g.getWidth(); @@ -36,10 +44,6 @@ let cx = W/2; let cy = H/2; let Timeout; -/* set font */ - -require("Font4x5Numeric").add(Graphics); - Bangle.loadWidgets(); /* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */ @@ -106,9 +110,10 @@ function drawHands() { g.drawImage(imgHour,cx-22*hourSin,cy+22*hourCos,{rotate:hourAngle}); g.drawImage(imgMin,cx-34*minSin,cy+34*minCos,{rotate:minAngle}); g.drawImage(imgSec,cx-25*secSin,cy+25*secCos,{rotate:secAngle}); - g.setFont("4x5Numeric:3"); + g.setFontRolexFont(); g.setColor(g.theme.bg); - g.drawString(d.getDate(),157,81); + g.setFontAlign(0,0,0); + g.drawString(d.getDate(),165,89); } function drawBackground() { diff --git a/apps/rolex/metadata.json b/apps/rolex/metadata.json index e24344dad..f307e60c4 100644 --- a/apps/rolex/metadata.json +++ b/apps/rolex/metadata.json @@ -2,8 +2,8 @@ "name": "rolex", "shortName":"rolex", "icon": "rolex.png", - "screenshots": [{"url":"screenshot.png"}], - "version":"0.03", + "screenshots": [{"url":"screenshot1.png"}], + "version":"0.04", "description": "A rolex like watch face", "tags": "clock", "type": "clock", @@ -14,4 +14,4 @@ {"name":"rolex.app.js","url":"app.js"}, {"name":"rolex.img","url":"app-icon.js","evaluate":true} ] - } \ No newline at end of file + } diff --git a/apps/rolex/screenshot1.png b/apps/rolex/screenshot1.png new file mode 100644 index 000000000..f84b0ef5c Binary files /dev/null and b/apps/rolex/screenshot1.png differ diff --git a/apps/run/README.md b/apps/run/README.md index 17975f92c..5b3bb635a 100644 --- a/apps/run/README.md +++ b/apps/run/README.md @@ -46,8 +46,8 @@ record GPS/HRM/etc data every time you start a run? ## Development -This app uses the [`exstats` module](/modules/exstats.js). When uploaded via the +This app uses the [`exstats` module](https://github.com/espruino/BangleApps/blob/master/modules/exstats.js). When uploaded via the app loader, the module is automatically included in the app's source. However when developing via the IDE the module won't get pulled in by default. -There are some options to fix this easily - please check out the [modules README.md file](/modules/README.md) +There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 09d95934f..f74c94db0 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -226,7 +226,7 @@ function showThemeMenu() { /*LANG*/'Dark BW': ()=>{ upd({ fg:cl("#fff"), bg:cl("#000"), - fg2:cl("#0ff"), bg2:cl("#000"), + fg2:cl("#fff"), bg2:cl("#004"), fgH:cl("#fff"), bgH:cl("#00f"), dark:true }); diff --git a/apps/shortcuts/ChangeLog b/apps/shortcuts/ChangeLog index 2286a7f70..0e4a98065 100644 --- a/apps/shortcuts/ChangeLog +++ b/apps/shortcuts/ChangeLog @@ -1 +1,2 @@ -0.01: New App! \ No newline at end of file +0.01: New App! +0.02: Update setUI to work with new Bangle.js 2v13 menu style diff --git a/apps/shortcuts/boot.js b/apps/shortcuts/boot.js index f71f6d6ca..58ce2d067 100644 --- a/apps/shortcuts/boot.js +++ b/apps/shortcuts/boot.js @@ -1,6 +1,7 @@ (function() { var sui = Bangle.setUI; Bangle.setUI = function(mode, cb) { + if ("object"==typeof mode) mode = mode.mode; if (mode!="clock") return sui(mode,cb); return sui("clockupdown", (dir) => { let settings = require("Storage").readJSON("shortcuts.json", 1)||{}; @@ -12,4 +13,3 @@ }); }; })(); - \ No newline at end of file diff --git a/apps/shortcuts/metadata.json b/apps/shortcuts/metadata.json index 2351a102f..922d5ae11 100644 --- a/apps/shortcuts/metadata.json +++ b/apps/shortcuts/metadata.json @@ -2,7 +2,7 @@ "id": "shortcuts", "name": "Shortcuts", "shortName": "Shortcuts", - "version": "0.01", + "version": "0.02", "description": "Quickly load your favourite apps from (almost) any watch face.", "icon": "app.png", "type": "bootloader", diff --git a/apps/slidingtext/ChangeLog b/apps/slidingtext/ChangeLog index 3bd656cb0..0327ff387 100644 --- a/apps/slidingtext/ChangeLog +++ b/apps/slidingtext/ChangeLog @@ -5,3 +5,4 @@ 0.05: BUGFIX: pedometer widget interfered with the clock Font Alignment 0.06: Use Bangle.setUI for button/launcher handling 0.07: Support for Bangle.js 2 and themes +0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". diff --git a/apps/slidingtext/metadata.json b/apps/slidingtext/metadata.json index 0f380ec8f..2937a618b 100644 --- a/apps/slidingtext/metadata.json +++ b/apps/slidingtext/metadata.json @@ -1,7 +1,7 @@ { "id": "slidingtext", "name": "Sliding Clock", - "version": "0.07", + "version": "0.08", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", "icon": "slidingtext.png", "type": "clock", diff --git a/apps/slidingtext/slidingtext.js b/apps/slidingtext/slidingtext.js index 3be502cfe..7b56af1a1 100644 --- a/apps/slidingtext/slidingtext.js +++ b/apps/slidingtext/slidingtext.js @@ -623,15 +623,6 @@ Bangle.on('lcdPower', (on) => { } }); -Bangle.on('faceUp',function(up){ - //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); - if (up && !Bangle.isLCDOn()) { - //console.log("faceUp and LCD off"); - clearTimers(); - Bangle.setLCDPower(true); - } -}); - g.clear(); load_settings(); Bangle.loadWidgets(); diff --git a/apps/smclock/ChangeLog b/apps/smclock/ChangeLog new file mode 100644 index 000000000..b029d805d --- /dev/null +++ b/apps/smclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial version +0.02: Add battery level diff --git a/apps/smclock/README.md b/apps/smclock/README.md new file mode 100644 index 000000000..7b5613147 --- /dev/null +++ b/apps/smclock/README.md @@ -0,0 +1,5 @@ +# Monogram Watch Face + +Just a simple watch face for the Banglejs2. + +It shows battery level in the upper left corner, date information in the upper right, and time information in the bottom. diff --git a/apps/smclock/app-icon.js b/apps/smclock/app-icon.js new file mode 100644 index 000000000..91494a528 --- /dev/null +++ b/apps/smclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("l0uwkEogAgm4VUod3ugsUu93FigABFyQsCFyQsDFyQsEFyAsFFyAsGFxwsHFxwsIu9yCpVCmQWIkckCxMhkcnFg8yiQsJiEBFw9zkMBFxEhgEAiYuGmUQgAuHFgIWBFwwsBBQQuGBQQuHFgQABFwosDFwwsDFw4KEFwosEFwosFFwgsFFwoKGFwYsGFwYsHEYUzEJAuBoIKHBYMiEJEAilEBRESFhAABLYNAFiUERQQsUFw4sPFwwsPFwosRFwgsRFwYsTFwQsTAANBFigABFigABoQtIFhYuKCpguIFhouICpwuGFh4uGCqAuEFiIuECqQuCFiYuCCqgAMA")) diff --git a/apps/smclock/app.js b/apps/smclock/app.js new file mode 100644 index 000000000..6aff72a46 --- /dev/null +++ b/apps/smclock/app.js @@ -0,0 +1,108 @@ +const background = { + width : 176, height : 176, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("/4A/AH4ACUb8H9MkyVJAThB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INP/AH4A/AAX8Yz4Afn5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INI=")) +}; + +const weekday = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; +var level = -1; + +function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + return 1 + Math.ceil((firstThursday - tdt) / 604800000); +} + +function d02(value) { + return ('0' + value).substr(-2); +} + +function pollBattery() { + level = E.getBattery(); + return level; +} + +function getBatteryColor(level) { + var color; + if (level < 0) { + level = pollBattery(); + } + if(level>80) { + color = [0,0,1]; + } else if(level>60) { + color = [0,1,1]; + } else if(level>40) { + color = [0,1,0]; + } else if(level>20) { + color = [1,1,0]; + } else { + color = [1,0,0]; + } + return color; +} + +function draw() { + g.drawImage(background); + + const color = getBatteryColor(); + const bat = d02(E.getBattery()) + "%"; + const d = new Date(); + const day = d.getDate(); + const month = (d.getMonth() + 1); + const week = d02(ISO8601_week_no(d)); + const date1 = d02(day) + "/" + d02(month); + const date2 = weekday[d.getDay()] + " " + d02(week); + const h = d.getHours(); + const m = d.getMinutes(); + const time = d02(h) + ":" + d02(m); + + g.reset(); + + g.setColor(0, 0, 0); + g.setFont("Vector", 20); + g.drawString(date1, 105, 20, false); + g.setFont("Vector", 16); + g.drawString(date2, 105, 55, false); + + g.setColor(1, 1, 1); + g.setFont("Vector", 60); + g.drawString(time, 10, 108, false); + + g.setColor(1, 1, 1); + g.setFont("Vector", 16); + g.drawString("Bat:", 12, 22, false); + g.setColor(color[0], color[1], color[2]); + g.drawString(bat, 52, 22, false); +} + +g.clear(); + +pollBattery(); +draw(); + +var batInterval = setInterval(pollBattery, 60000); +var drawInterval = setInterval(draw, 10000); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (batInterval) clearInterval(batInterval); + batInterval = undefined; + if (drawInterval) clearInterval(drawInterval); + drawInterval = undefined; + if (on) { + batInterval = setInterval(pollBattery, 60000); + drawInterval = setInterval(draw, 10000); + + pollBattery(); + draw(); // draw immediately + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); diff --git a/apps/smclock/app.png b/apps/smclock/app.png new file mode 100644 index 000000000..16712c63b Binary files /dev/null and b/apps/smclock/app.png differ diff --git a/apps/smclock/metadata.json b/apps/smclock/metadata.json new file mode 100644 index 000000000..1783ca7bf --- /dev/null +++ b/apps/smclock/metadata.json @@ -0,0 +1,16 @@ +{ + "id":"smclock", + "name":"Monogram Watch Face", + "shortName":"MonoClock", + "icon":"app.png", + "version":"0.02", + "description": "A simple watchface based on my stylised monogram.", + "tags":"clock", + "readme":"README.md", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"smclock.app.js","url":"app.js"}, + {"name":"smclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index 602147856..73e9bfc40 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -12,4 +12,4 @@ 1.10: Adds Kalman filter. 1.14: Add VMG and coordinates screens 1.43: Adds mirroring of the watch face to an Android device. See README.md -1.48: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. +1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 73fa3bacb..ed16131a4 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -179,7 +179,8 @@ var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); let LED = // LED as minimal and only definition (as instance / singleton) { isOn: false // status on / off, not needed if you don't need to ask for it , set: function(v) { // turn on w/ no arg or truey, else off - g.setColor((this.isOn=(v===undefined||!!v))?1:0,0,0).fillCircle(120,10,10); } + this.isOn = v===undefined||!!v; + g.setColor(this.isOn?1:0,0,0).fillCircle(120,10,10); } , reset: function() { this.set(false); } // turn off , write: function(v) { this.set(v); } // turn on w/ no arg or truey, else off , toggle: function() { this.set( ! this.isOn); } // toggle the LED diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog index c1b4a5fbb..244b602b5 100644 --- a/apps/swiperclocklaunch/ChangeLog +++ b/apps/swiperclocklaunch/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Fix issue with mode being undefined +0.03: Update setUI to work with new Bangle.js 2v13 menu style diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index e9b203eee..bb285ea94 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -4,6 +4,7 @@ Bangle.setUI = function(mode, cb) { sui(mode,cb); if(!mode) return; + if ("object"==typeof mode) mode = mode.mode; if (!mode.startsWith("clock")) return; Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; Bangle.on("swipe", Bangle.swipeHandler); diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json index 733aaa032..5e4a0d648 100644 --- a/apps/swiperclocklaunch/metadata.json +++ b/apps/swiperclocklaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "swiperclocklaunch", "name": "Swiper Clock Launch", - "version": "0.02", + "version": "0.03", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", "type": "bootloader", diff --git a/apps/tabata/ChangeLog b/apps/tabata/ChangeLog index 5560f00bc..86faf17a5 100644 --- a/apps/tabata/ChangeLog +++ b/apps/tabata/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Fix settings wrapping code for new menu system diff --git a/apps/tabata/metadata.json b/apps/tabata/metadata.json index 14429090f..a840c9c8b 100644 --- a/apps/tabata/metadata.json +++ b/apps/tabata/metadata.json @@ -2,7 +2,7 @@ "id": "tabata", "name": "Tabata", "shortName": "Tabata - Control High-Intensity Interval Training", - "version": "0.01", + "version": "0.02", "description": "Control high-intensity interval training (according to tabata: https://en.wikipedia.org/wiki/Tabata_method).", "icon": "tabata.png", "tags": "workout,health", diff --git a/apps/tabata/tabata.js b/apps/tabata/tabata.js index 603cf96ee..f6addc35b 100644 --- a/apps/tabata/tabata.js +++ b/apps/tabata/tabata.js @@ -30,18 +30,17 @@ function showMainMenu() { }, 'Pause sec.': { value: settings.pause, - onchange: function(v){ - if (v<0)v=MAX_SECONDS; - if (v>MAX_SECONDS)v=0; + min : 0, max : MAX_SECONDS, wrap : true, + onchange: v => { settings.pause=v; - this.value=v; saveSettingsDebounce(); } }, 'Trainig sec.': { value: settings.training, - onchange: function(v){if (v<0)v=MAX_SECONDS;if (v>MAX_SECONDS)v=0;settings.training=v; - this.value=v; + min : 0, max : MAX_SECONDS, wrap : true, + onchange: v => { + settings.training=v; saveSettingsDebounce(); } }, @@ -61,8 +60,8 @@ function startTabata() { g.clear(); Bangle.setLCDMode("doublebuffered"); g.flip(); - var pause = settings.pause, - training = settings.training, + var pause = settings.pause, + training = settings.training, round = 1, active = true, clearBtn1, clearBtn2, clearBtn3, timer; @@ -86,7 +85,7 @@ function startTabata() { exitTraining(); return; } - + if (active) { drawCountDown(round, training, active); training--; @@ -117,7 +116,7 @@ function drawCountDown(round, count, active) { g.setFontAlign(0,0); g.setFont("6x8", 2); - g.drawString("Round " + round + "/" + settings.rounds,120,6); + g.drawString("Round " + round + "/" + settings.rounds,120,6); g.setFont("6x8", 6); g.drawString("" + count,120,80); diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog index 5560f00bc..6515ab627 100644 --- a/apps/terminalclock/ChangeLog +++ b/apps/terminalclock/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Rename "Activity" in "Motion" and display the true values for it diff --git a/apps/terminalclock/README.md b/apps/terminalclock/README.md index 2a2bc1204..5a54583d2 100644 --- a/apps/terminalclock/README.md +++ b/apps/terminalclock/README.md @@ -5,5 +5,5 @@ It can display : - time - date - hrm -- activity +- motion - steps diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js index fb7bd16cc..ab83a696f 100644 --- a/apps/terminalclock/app.js +++ b/apps/terminalclock/app.js @@ -1,6 +1,5 @@ var locale = require("locale"); var fontColor = g.theme.dark ? "#0f0" : "#000"; -var startY = 24; var paddingY = 2; var font6x8At4Size = 32; var font6x8At2Size = 18; @@ -15,25 +14,25 @@ function setFontSize(pos){ } function clearField(pos){ - var yStartPos = startY + + var yStartPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos-1) + font6x8At2Size * Math.max(0, pos-2); - var yEndPos = startY + + var yEndPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos) + font6x8At2Size * Math.max(0, pos-1); - g.clearRect(0, yStartPos, 240, yEndPos); + g.clearRect(Bangle.appRect.x, yStartPos, Bangle.appRect.x2, yEndPos); } function clearWatchIfNeeded(now){ if(now.getMinutes() % 10 == 0) - g.clearRect(0, startY, 240, 240); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2); } function drawLine(line, pos){ setFontSize(pos); - var yPos = startY + + var yPos = Bangle.appRect.y + paddingY * (pos - 1) + font6x8At4Size * Math.min(1, pos-1) + font6x8At2Size * Math.max(0, pos-2); @@ -76,11 +75,10 @@ function drawHRM(pos){ function drawActivity(pos){ clearField(pos); var health = Bangle.getHealthStatus('last'); - var steps_formated = ">Activity: " + parseInt(health.movement/10); + var steps_formated = ">Motion: " + parseInt(health.movement); drawLine(steps_formated, pos); } - function draw(){ var curPos = 1; g.reset(); @@ -109,7 +107,6 @@ function draw(){ drawInput(now, curPos); } - Bangle.on('HRM',function(hrmInfo) { if(hrmInfo.confidence >= settings.HRMinConfidence) heartRate = hrmInfo.bpm; @@ -127,12 +124,12 @@ var settings = Object.assign({ showActivity: true, showStepCount: true, }, require('Storage').readJSON("terminalclock.json", true) || {}); -// draw immediately at first -draw(); // Show launcher when middle button pressed Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); +// draw immediately at first +draw(); var secondInterval = setInterval(draw, 10000); diff --git a/apps/terminalclock/screenshot1.png b/apps/terminalclock/screenshot1.png index a0d41f495..c0f472102 100644 Binary files a/apps/terminalclock/screenshot1.png and b/apps/terminalclock/screenshot1.png differ diff --git a/apps/terminalclock/screenshot2.png b/apps/terminalclock/screenshot2.png index 21d7aadbf..8bd552a73 100644 Binary files a/apps/terminalclock/screenshot2.png and b/apps/terminalclock/screenshot2.png differ diff --git a/apps/thering/0p.png b/apps/thering/0p.png new file mode 100644 index 000000000..26b159a17 Binary files /dev/null and b/apps/thering/0p.png differ diff --git a/apps/thering/100p.png b/apps/thering/100p.png new file mode 100644 index 000000000..3e1464b65 Binary files /dev/null and b/apps/thering/100p.png differ diff --git a/apps/thering/10p.png b/apps/thering/10p.png new file mode 100644 index 000000000..e2a80aa23 Binary files /dev/null and b/apps/thering/10p.png differ diff --git a/apps/thering/1circle.png b/apps/thering/1circle.png new file mode 100644 index 000000000..fe50dcb3a Binary files /dev/null and b/apps/thering/1circle.png differ diff --git a/apps/thering/1ring.png b/apps/thering/1ring.png new file mode 100644 index 000000000..8a5e49063 Binary files /dev/null and b/apps/thering/1ring.png differ diff --git a/apps/thering/20p.png b/apps/thering/20p.png new file mode 100644 index 000000000..4b2ce214c Binary files /dev/null and b/apps/thering/20p.png differ diff --git a/apps/thering/2p.png b/apps/thering/2p.png new file mode 100644 index 000000000..aff255ce3 Binary files /dev/null and b/apps/thering/2p.png differ diff --git a/apps/thering/30p.png b/apps/thering/30p.png new file mode 100644 index 000000000..83de9f45d Binary files /dev/null and b/apps/thering/30p.png differ diff --git a/apps/thering/40p.png b/apps/thering/40p.png new file mode 100644 index 000000000..07470987a Binary files /dev/null and b/apps/thering/40p.png differ diff --git a/apps/thering/4p.png b/apps/thering/4p.png new file mode 100644 index 000000000..e8b26942d Binary files /dev/null and b/apps/thering/4p.png differ diff --git a/apps/thering/50p.png b/apps/thering/50p.png new file mode 100644 index 000000000..0c1865f57 Binary files /dev/null and b/apps/thering/50p.png differ diff --git a/apps/thering/60p.png b/apps/thering/60p.png new file mode 100644 index 000000000..18720530a Binary files /dev/null and b/apps/thering/60p.png differ diff --git a/apps/thering/70p.png b/apps/thering/70p.png new file mode 100644 index 000000000..a55a0ffaf Binary files /dev/null and b/apps/thering/70p.png differ diff --git a/apps/thering/7p.png b/apps/thering/7p.png new file mode 100644 index 000000000..5de4ae719 Binary files /dev/null and b/apps/thering/7p.png differ diff --git a/apps/thering/80p.png b/apps/thering/80p.png new file mode 100644 index 000000000..a183084eb Binary files /dev/null and b/apps/thering/80p.png differ diff --git a/apps/thering/90p.png b/apps/thering/90p.png new file mode 100644 index 000000000..ccaaff9ab Binary files /dev/null and b/apps/thering/90p.png differ diff --git a/apps/thering/README.md b/apps/thering/README.md new file mode 100644 index 000000000..382fe853f --- /dev/null +++ b/apps/thering/README.md @@ -0,0 +1,73 @@ +# The Ring + + *A proof of concept clock with large ring guage for steps using pre-set images, acts as a tutorial piece for discussion* + + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) + +* The ring is a proof of concept to establish a clean way to draw a +large ring guage with few aliasing issues and artifacts. +* Rather than use grahics commands to draw the ring a series of fixed images are used. +* This allows for better accuracy of the initial image and also does not suffer from performance issues. +* The downside is that more storage and memory is used to hold the + initial images. This is not an issue on a Bangle 2. +* The ring effect is constructed from 14 images that represent a range of different percentages +* The percentages of the images are 0,2,4,7,10,20,30,40,50,60,70,80,90,100% +* The app is not intended to be enhanced further (apart from bug fixes) but rather as code that can be reused in other apps +* The full set of original images are included in the source code to demonstrate the concept +* I will use this code to build a new clock similar to Pastel but + using this ring guage for steps. The new clock will use more + attractive fonts and provide a settings meu to change the primary + color of the ring. + + +## Screenshots + +![](screenshot_thering1.png) + +It is worth looking at a photograph of the clock in action as the +screenshot does not do the final effect justice. + +![](screenshot_thering2.jpg) + +## Production + +1. I first generated a circle on black background using [The +Gimp](https://www.gimp.org/) image editor. I used this [Youtube +video](https://www.youtube.com/watch?v=AoIAznSdLik) to get started. +The initial image is 178x178 pixels. + +![](1circle.png) + +2. I then drew another smaller black circle over the top of the original to make a ring + +![](1ring.png) + +3. From the empty ring image I coloured segments of the ring and saved new images at specific percentages + +4. I used the file `calc_percentages.js` to work out the x and y +coordinates of the end point of each percentage position along the +ring. + +5. The [Image +Converter](https://espruino.github.io/EspruinoWebTools/examples/imageconverter.html) +was used, set to 2-bit optimal, transparency Y, compression Y and +ImageObject Y, to convert each PNG file to code. + +6. NOTE that the generated image object pallete seemed to switch the +order of the colors from 50% onwards. + +7. The greying out of the unused part of the ring is acheived by +using a dithered color. So if the ring colour is green #0f0 then the +greyed out part is done in '#020'. + + +## Stages of The Ring + +Below are some examples of the different stages of the ring + +![](0p.png) +![](7p.png) +![](60p.png) diff --git a/apps/thering/app-icon.js b/apps/thering/app-icon.js new file mode 100644 index 000000000..7d6edf547 --- /dev/null +++ b/apps/thering/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///un/1XEh32DyX/+AKIn/6//ABQ0P/Wq1dcBY3+1QAB64KFgfqBYWVoAtFBQWq2tQBYgtBBYdUERALBq5FE1WuF4dVKoYiB3QLEHgQiC1eq1EAitVuAiEBoOABYsOD4PqCwMA6tVKgU6FwWgAwNVqqFC/WrEQI1CBYY6BFwIiCgILDHQO79QiCBYg6BFwJFBgEFBYc+1YLBOAU1BYf6326FwReCBYXq3wuDLwRfBIwIuEKQR3BgY6CCwRGCBYbsBBYUNBYVAh+r1w6DIwS/BhwLBHQ1VNQILBHQzjBn2q9wLCFwb7Bn2u9wuEEgKCCUoIuEZQaOBIwQiDfQQvBBYRRDKQILEIoQiBIwK+CBYM9BYN9IwTWC1EDNAY6CBYP61AWCBoQuBFYP//oVDFwYLBBQppCAAX/D4V1XgQLDn4YEq4uCHgQYDqtfBQY8C//dCwPwBYgwBAAYiEGAQKC/gKFEgYWHHoYtGABYA=")) diff --git a/apps/thering/app.js b/apps/thering/app.js new file mode 100644 index 000000000..f7cfaa015 --- /dev/null +++ b/apps/thering/app.js @@ -0,0 +1,224 @@ +const h = g.getHeight(); +const w = g.getWidth(); +// palette for 0-40% +const pal1 = new Uint16Array([g.theme.bg, g.toColor("#020"), g.toColor("#0f0"), g.toColor("#00f")]); +// palette for 50-100% +const pal2 = new Uint16Array([g.theme.bg, g.toColor("#0f0"), g.toColor("#020"), g.toColor("#00f")]); +const infoWidth = 50; +const infoHeight = 14; + +var drawingSteps = false; +//var fakeSteps = 0; + +var p0_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLLLzWEABLgbVgohEGopYaiofDBihWVHJpYYDgYPbKx1ACJhYZIwT4OcAZWYHyRYUIgQXQH4RqOThCXUYRpCHNyQVVQQTwVQiSZWIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) +}; + +var p2_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette: pal1, + buffer : require("heatshrink").decompress(atob("AH4A/ADNUFE8FqtVq2q1AqkFIIrDAAOAFMEBFQYrE1WgKsYrGLL4qFFY2pqDWeFZdUVkAhCAQMKFYdVLDUVFQYMHlWq0oMJKyoOJlQrCLDBWDB5clB5xWOoARMCARYWKwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) +}; + +var p4_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFY2loAqjFY1VqDWeFZdUVkAhEhQrDLDcVFQYMHlQrCBhBWVHJpYYDgYPbKx1ACJhYZIwT4OgpYXKwY+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) +}; + +var p7_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgWlKzVACJgrCqBWYawgAJcAOlNBhWMCZ8qFYJYUgoqBC6ECFYJqOAApWSS4jCNQQ5uSCqqCCeCqESFQKZUIQSEQNgSYSIYiEQQSyEUCQLDSOAyCnQiSCYQiSCYQiSCZDaDARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A==")) +}; + +var p10_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAOkQSdUFacK1WloCCSCaAAEFYKaQQSyEC0pvQirZTbomlIh6CYZAZFOQTBxDQhyCYOQhoPQS4bQHaBzaVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) +}; + +var p20_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4AWgNVoAEGAERSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A=")) +}; + +var p30_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccFawkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHI")) +}; + +var p40_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal1, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVqtW1WoFUgpBFYYABwApggIqDFYmq0BVjFYxZfFQorGLLrWCFZbgbVgtUBQcKLD8VFQYMHlQsDKzoOJFgZYYKwYPLFgZWaoARMLDJWCawgAJcAZWYCZ6FCLCkFFQNQCZ8CFYOoFaZWSLAmAQShWQLAiESQQRtTLAKESFQNUFacKQiSCCoArTgCESQSyEUirZTboyCnQiSCYQiSCYQiSCZQgeAVxwqYQgSwMVwNUFbMKWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbhdVFcTcHbT7cDFY0BbT7cD0ArxgtVoArfgGq1ArHFUDcBFY0VFceqFY1UFcMKFY1VFcmAFYtQFcMCFYsBFcugFYtAFcMAFYsFFcuoFYoqigEqFeEVFcuqFYlUFccKFYlVFc2AFYdQFccCFf4rbgNVoArjgGq0Ar/FbMFFc+oFYYqkgEqFf4r/FY0VqgrlhWqFf4r/Ff4rdqorowArBqArlgQr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlhQrCioroAYIr/Ff4r/FbcFqorllWoFf4r/FY9AFcmqFYUBFc+gFf4rZgFVqAqjgWqwAr/FbdUFccKFYkVFcwFDitVFccqFYkFFcuoFeNAFcWqFYkBFcugFYtQFUMCFYsAFcuAFYtUFcMKFY0VFcgHFitVFcMqFY0FFceoFY9AFcGqFY0BqtQFT8C1WgFeMAqtUFb8K1WAFY7cglQrIioriBI8FqtAFb2q1ArJbjzaBFZEBbj7aB0ALIFcLaHbkLaJFYbcd1QrKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjUBQR6EaiqCPQjVVQSATCqtUFSZvB1WACiSEUY4KCQQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) +}; + +var p50_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAiEXKwKuNQjSCQQjSCQQjSCRAAIrB1AqTgorBoAUQQiyCSQgjdSbISCRQgZYSKwKCSQghYQKwSCSQghYQKwSCTAAMqFYOoCJsFFQNVFShYEwARMFQRWVLAiFMQIRWWLAosKFQZWXLAosIFQZWYLAzgFawZWbAAMKFgmq1IoEAANUFTQABFZtAFbgsFFYwqeWQorFVjZZJFYhVfcAwrCazoA/AHI")) +}; + +var p60_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZgGq0AqjgNVoAr/FbeoFccFFYkqFcwFDlWqFccVFYkKFctUFeOAFcVVFYkCFctQFYugFUMBFYsAFctAFYuoFcMFFY0qFcgHFlWqFcMVFY0KFcdUFY+AFcFVFY0C1WgFT8BqtQFeMA1WoFb8FqtAFY7cgiorIlQriBI8K1WAFb1VqgrJbjzaBFZECbj7aBqALIFcLaHbkLaJFYbcdqorKbjzaKbkDaLbgSwcVwLaJWD6uLFYawaVwIrMbgKwaVwLaKbgawaVwLaLbgawZQQLaLWDiuOWAaEYQQKuMWAelNBqCLVxqEC0oRPQS6EC0oSQQSyECFYKEVQSIABFYI/QAAcFFYJDRCgSCmYYjdSCqqYCLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) +}; + +var p70_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AFYegFccBFf4rbgWqwArjgFVqAr/FbMKFc9UFYYqkgEVFf4r/FY0q1ArlgtVFf4r/Ff4rd1QrooArB0ArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rb1ArlgorClQroAYIr/Ff4r/FbcK1QrlitUFf4r/FY+AFclVFYUCFc9QFf4rZAgoAggNVoAr/FbdUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) +}; + +var p80_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AcIdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) +}; + +var p90_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESquq1ArTgqESNgOqwArTIYKERH4KCUQigSBbKTdGCKKCVQiTCCFSyERCALBQQjAPBoArXDZ7ARObKuBSZwcaVzR0QFYKuZWAYNZWCJJKMoKuaWAahKBhiwTJRSudURorBFTgfMVzqjDO5DaeZ5jaeJhhiKbi4rIbT4hLqoriPI7afUpS5BbTwiKFdZgIADSmHFYIqgbgIrGcgIriEYwzHADZ7HRY4rdaYrjHADcBFYoGBFcgkEGQwAeFYqKHFbzUEcQ4AdiorwiorlEogxFAD59FWoorhoArDqArjgIr/FbYwFAEJSDFf4rXgornqgrDFUkAior/Ff4rGAYYAjKYYr/Ff4r/FbdVFdFAFYNQFcsBFf4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/FbdUFcsFFYUVFdADBFf4r/Ff4rbAYYAjKYYr/Ff4rFoArkqorCgIrnqAr/FbIEFAEBSFFf4rYqgrjgorEiormAocVAogAfEooxFFcB9EFdq1DAD9VFYkBFctQFYoGEADokHFcp8FRQoAdag7iFFb4HFioHGADYjHGY4rcPYyLHADbTHcYNQFT4iIFdZgIADKmJqrcgiorIBIIrhMKIAXUpIrBbjzaBFZAKKbS5MJFcKkJbj4fLBYLcdqorKbjzPMbjxKNMhauTURawdJJorBWDShBFZiRBWDQcOHRyuPOhorBWDIbPWDRzQSYKEYIwLLOHgSEXDIJyPQjD2SQjCCQQjSCRCYY/QN4xDRQiyCSQgjdSCqqECLCRWBYyiECISBWCYqgXCLCBWCQSYYEIhxqCeChYFThoQCKypYEIxgPPLB4cKFQZWXDoosIBhhYWcArWDKzYhHABA1EADArNoArcFhgqeWQysgLJxVfcBLWdAH4A5A")) +}; + +var p100_img = { + width : 176, height : 176, bpp : 2, + transparent : -1, + palette : pal2, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ACgtVAAVUFUgpDAAdAFMEBFQ4ABqBVnLMQqLFjzWEABLgbVgohEGoqyaiofDBihWVHJpYYDgYPbKxz5NLDJGCfBzgDKzA+SLChECC6A/CNRycIS6jCNIQ5uSCqqCCeCqESTKxCCQiBsCTCRDEQiCCWQigSBYaRwGQU6ESQTCESQTCESQTIbQYCJzZVwKTODjSuaOiArBVzKwDBrKwRJJRlBVzSwDUJQMMWCZKKVzqiNFYIqcD5iudUYZ3IbTzPMbTxMMMRTcXFZDafEJdVFcR5HbT6lKXILaeERQrrMBAAaUw4rBFUDcBFYzkBFcQjGGY4AbPY6LHFbrTFcY4AbgIrFAwIrkEggyGADwrFRQ4reagjiHADsVFeEVFcolEGIoAfPoq1FFcNAFYdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) +}; + +function setLargeFont() { + g.setFont('Vector', 48); +} + +function setSmallFont() { + //g.setFont('6x8',1); + g.setFont('Vector', 16); +} + +function getSteps() { + //return fakeSteps; + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return 0; + } +} + +function getGaugeImage(p) { + console.log("p="+p); + if (p < 2) return p0_img; + if (p >= 2 && p < 4) return p2_img; + if (p >= 4 && p < 7) return p4_img; + if (p >= 7 && p < 10) return p7_img; + if (p >= 10 && p < 20) return p10_img; + if (p >= 20 && p < 30) return p20_img; + if (p >= 30 && p < 40) return p30_img; + if (p >= 40 && p < 50) return p40_img; + if (p >= 50 && p < 60) return p50_img; + if (p >= 60 && p < 70) return p60_img; + if (p >= 70 && p < 80) return p70_img; + if (p >= 80 && p < 90) return p80_img; + if (p >= 90 && p < 100) return p90_img; + if (p >= 100) return p100_img; +} + +function draw() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var da = date.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + var steps = getSteps(); + var p_steps = Math.round(100*(steps/10000)); + console.log("steps="+ steps + " p_steps=" + p_steps); + + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(0, 0, w, h); + g.drawImage(getGaugeImage(p_steps), 0, 0); + setLargeFont(); + + g.setColor('#0f0'); + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 1, h/2); + + g.setColor(g.theme.fg); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 1, h/2); + + drawSteps(); + g.drawString('Battery ' + E.getBattery() + '%', w/2, h/4); +} + +function drawSteps() { + if (drawingSteps) return; + drawingSteps = true; + setSmallFont(); + g.setFontAlign(0,0); + var steps = getSteps(); + g.setColor(g.theme.bg); + g.fillRect((w/2) - infoWidth, (3*h/4) - infoHeight, (w/2) + infoWidth, (3*h/4) + infoHeight); + g.setColor(g.theme.fg); + g.drawString('Steps ' + steps, w/2, (3*h/4) - 4); + drawingSteps = false; +} + +Bangle.on('step', s => { + drawSteps(); +}); + +// for testing the segments +/* +function nextSteps() { + fakeSteps += 100; +} + +function prevSteps() { + fakeSteps -= 100; +} + +Bangle.setUI("clockupdown", btn=> { + if (btn<0) prevSteps(); + if (btn>0) nextSteps(); + draw(); +}); +*/ + +g.clear(); +Bangle.setUI("clock"); +Bangle.loadWidgets(); +/* + * we are not drawing the widgets as we are taking over the whole screen + * so we will blank out the draw() functions of each widget and change the + * area to the top bar doesn't get cleared. + */ +for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + +draw(); +setInterval(draw, 60000); diff --git a/apps/thering/app.png b/apps/thering/app.png new file mode 100644 index 000000000..0c68d2246 Binary files /dev/null and b/apps/thering/app.png differ diff --git a/apps/thering/calc_precentages.js b/apps/thering/calc_precentages.js new file mode 100644 index 000000000..b94815c2e --- /dev/null +++ b/apps/thering/calc_precentages.js @@ -0,0 +1,58 @@ +/* + +Used to calculate the x,y coordinates (so that we can manuall create +images) of the outer circumference of a rotating ring guage where +cx,cy is the centre of the screen r1 is the radius of the circle. p +is the % completion or % measure for the guage to take we need to +generate images at percentages 0,2,4,7,10,20....100 + + +the output is below + +p=0 x=88 y=4 +p=2 x=99 y=5 +p=4 x=109 y=7 +p=7 x=124 y=12 +p=10 x=137 y=20 +p=20 x=168 y=62 +p=30 x=168 y=114 +p=40 x=137 y=156 +p=50 x=88 y=172 +p=60 x=39 y=156 +p=70 x=8 y=114 +p=80 x=8 y=62 +p=90 x=39 y=20 + +*/ + +const h = g.getHeight(); +const w = g.getWidth(); + +function radians(a) { + return a*Math.PI/180; +} + +function calc() { + var i = 0; + var r1 = (w/2) - 4; + var startrot = 0 - 180; + var endrot = -360 - 180; + var cx = w/2; + var cy = h/2; + var p = 0; + + // calc coords for each percentage point + for (i = startrot; i > endrot; i -= 3.6) { + x = cx + r1 * Math.sin(radians(i)); + y = cy + r1 * Math.cos(radians(i)); + x = Math.round(x); + y = Math.round(y); + + if (p==2 || p==4 || p==7 || p % 10 == 0) + print('p=' + p + ' x=' + x + ' y=' + y); + p++; + } +} + +g.clear(); +calc(); diff --git a/apps/thering/metadata.json b/apps/thering/metadata.json new file mode 100644 index 000000000..32b1dae4b --- /dev/null +++ b/apps/thering/metadata.json @@ -0,0 +1,14 @@ +{ "id": "thering", + "name": "The Ring", + "version":"0.01", + "description": "A proof of concept clock with large ring guage for steps using pre-set images, acts as a tutorial piece for discussion", + "icon": "app.png", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot_thering3.jpg"}], + "readme": "README.md", + "storage": [ + {"name":"thering.app.js","url":"app.js"}, + {"name":"thering.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/thering/screenshot_thering1.png b/apps/thering/screenshot_thering1.png new file mode 100644 index 000000000..bf7e6cebc Binary files /dev/null and b/apps/thering/screenshot_thering1.png differ diff --git a/apps/thering/screenshot_thering2.jpg b/apps/thering/screenshot_thering2.jpg new file mode 100644 index 000000000..521a61017 Binary files /dev/null and b/apps/thering/screenshot_thering2.jpg differ diff --git a/apps/thering/screenshot_thering3.jpg b/apps/thering/screenshot_thering3.jpg new file mode 100644 index 000000000..da8f140c0 Binary files /dev/null and b/apps/thering/screenshot_thering3.jpg differ diff --git a/apps/timeandlife/metadata.json b/apps/timeandlife/metadata.json index 86800f16f..acd6b0086 100644 --- a/apps/timeandlife/metadata.json +++ b/apps/timeandlife/metadata.json @@ -1,18 +1,26 @@ { "id": "timeandlife", "name": "Time and Life", - "shortName":"Time and Lfie", + "shortName": "Time and Life", "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "A simple watchface which displays the time when the screen is tapped and decay according to the rules of Conway's game of life.", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator":true, + "supports": [ + "BANGLEJS2" + ], + "allow_emulator": true, "readme": "README.md", "storage": [ - {"name":"timeandlife.app.js","url":"app.js"}, - {"name":"timeandlife.img","url":"app-icon.js","evaluate":true} + { + "name": "timeandlife.app.js", + "url": "app.js" + }, + { + "name": "timeandlife.img", + "url": "app-icon.js", + "evaluate": true + } ] -} - +} \ No newline at end of file diff --git a/apps/weatherClock/app.js b/apps/weatherClock/app.js index 1a7f53f05..91d0ab36f 100644 --- a/apps/weatherClock/app.js +++ b/apps/weatherClock/app.js @@ -71,7 +71,6 @@ function chooseIconByCode(code) { case 801: return partSunIcon; default: return cloudIcon; } - break; default: return cloudIcon; } } diff --git a/apps/widChargingStatus/ChangeLog b/apps/widChargingStatus/ChangeLog index 1033c0cd3..5a6db5cb7 100644 --- a/apps/widChargingStatus/ChangeLog +++ b/apps/widChargingStatus/ChangeLog @@ -1 +1,2 @@ 0.01: First release. +0.02: No functional changes, just moved codebase to Typescript. diff --git a/apps/widChargingStatus/metadata.json b/apps/widChargingStatus/metadata.json index f68ccf5b4..573c594e7 100644 --- a/apps/widChargingStatus/metadata.json +++ b/apps/widChargingStatus/metadata.json @@ -2,7 +2,7 @@ "name": "Charging Status", "shortName":"ChargingStatus", "icon": "widget.png", - "version":"0.01", + "version":"0.02", "type": "widget", "description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.", "tags": "widget", diff --git a/apps/widChargingStatus/widget.js b/apps/widChargingStatus/widget.js index 90f9199fa..5d9ea3837 100644 --- a/apps/widChargingStatus/widget.js +++ b/apps/widChargingStatus/widget.js @@ -1,31 +1,33 @@ -(() => { - const icon = require("heatshrink").decompress(atob("ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA")); - const iconWidth = 18; - - function draw() { - g.reset(); - if (Bangle.isCharging()) { - g.setColor("#FD0"); - g.drawImage(icon, this.x + 1, this.y + 1, { - scale: 0.6875 - }); - } - } - - WIDGETS.chargingStatus = { - area: 'tr', - width: Bangle.isCharging() ? iconWidth : 0, - draw: draw, - }; - - Bangle.on('charging', (charging) => { - if (charging) { - Bangle.buzz(); - WIDGETS.chargingStatus.width = iconWidth; - } else { - WIDGETS.chargingStatus.width = 0; - } - Bangle.drawWidgets(); // re-layout widgets - g.flip(); - }); -})(); \ No newline at end of file +"use strict"; +(() => { + const icon = require('heatshrink').decompress(atob('ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA')); + const iconWidth = 18; + function draw() { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw: draw, + }; + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } + else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/apps/widChargingStatus/widget.ts b/apps/widChargingStatus/widget.ts new file mode 100644 index 000000000..14b4df4a4 --- /dev/null +++ b/apps/widChargingStatus/widget.ts @@ -0,0 +1,38 @@ +(() => { + const icon = require('heatshrink').decompress( + atob( + 'ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA' + ) + ); + const iconWidth = 18; + + function draw(this: { x: number; y: number }) { + g.reset(); + if (Bangle.isCharging()) { + g.setColor('#FD0'); + g.drawImage(icon, this.x + 1, this.y + 1, { + scale: 0.6875, + }); + } + } + + WIDGETS.chargingStatus = { + area: 'tr', + width: Bangle.isCharging() ? iconWidth : 0, + draw: draw, + }; + + Bangle.on('charging', (charging) => { + const widget = WIDGETS.chargingStatus; + if (widget) { + if (charging) { + Bangle.buzz(); + widget.width = iconWidth; + } else { + widget.width = 0; + } + Bangle.drawWidgets(); // re-layout widgets + g.flip(); + } + }); +})(); diff --git a/apps/widadjust/README.md b/apps/widadjust/README.md new file mode 100644 index 000000000..4f89af54b --- /dev/null +++ b/apps/widadjust/README.md @@ -0,0 +1,57 @@ +# Adjust Clock + +Adjusts clock continually in the background to counter clock drift. + +## Usage + +First you need to determine the clock drift of your watch in PPM (parts per million). + +For example if you measure that your watch clock is too fast by 5 seconds in 24 hours, +then PPM is `5 / (24*60*60) * 1000000 = 57.9`. + +Then set PPM in settings and this widget will continually adjust the clock by that amount. + +## Settings + +See **Basic logic** below for more details. + +- **PPM x 10** - change PPM in steps of 10 +- **PPM x 1** - change PPM in steps of 1 +- **PPM x 0.1** - change PPM in steps of 0.1 +- **Update Interval** - How often to update widget and clock error. +- **Threshold** - Threshold for adjusting clock. + When clock error exceeds this threshold, clock is adjusted with `setTime`. +- **Save State** - If `On` clock error state is saved to file when widget exits, if needed. + That is recommended and default setting. + If `Off` clock error state is forgotten and reset to 0 whenever widget is restarted, + for example when going to Launcher. This can cause significant inaccuracy especially + with large **Update Interval** or **Threshold**. +- **Debug Log** - If `On` some debug information is logged to file `widadjust.log`. + +## Display + +Widget shows clock error in milliseconds and PPM. + +## Basic logic + +- When widget starts, clock error state is loaded from file `widadjust.state`. +- While widget is running, widget display and clock error is updated + periodically (**Update Interval**) according to **PPM**. +- When clock error exceeds **Threshold** clock is adjusted with `setTime`. +- When widget exists, clock error state is saved to file `widadjust.state` if needed. + +## Services + +Other apps/widgets can use `WIDGETS.adjust.now()` to request current adjusted time. +To support also case where this widget isn't present, the following code can be used: + +``` +function adjustedNow() { + return WIDGETS.adjust ? WIDGETS.adjust.now() : Date.now(); +} +``` + +## Acknowledgment + +Uses [Clock Settings](https://icons8.com/icon/tQvI71EfIWy3/clock-settings) +icon by [Icons8](https://icons8.com). diff --git a/apps/widadjust/icon.png b/apps/widadjust/icon.png new file mode 100644 index 000000000..f97394618 Binary files /dev/null and b/apps/widadjust/icon.png differ diff --git a/apps/widadjust/metadata.json b/apps/widadjust/metadata.json new file mode 100644 index 000000000..a308072f5 --- /dev/null +++ b/apps/widadjust/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "widadjust", + "name": "Adjust Clock", + "icon": "icon.png", + "version": "0.01", + "description": "Adjusts clock continually in the background to counter clock drift", + "type": "widget", + "tags": "widget", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "readme": "README.md", + "storage": [ + { "name": "widadjust.wid.js", "url": "widget.js" }, + { "name": "widadjust.settings.js", "url": "settings.js" } + ], + "data": [ + { "name": "widadjust.json" }, + { "name": "widadjust.state" } + ] +} diff --git a/apps/widadjust/settings.js b/apps/widadjust/settings.js new file mode 100644 index 000000000..5791d763b --- /dev/null +++ b/apps/widadjust/settings.js @@ -0,0 +1,120 @@ +(function(back) { + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + let thresholdV = [ 10, 25, 50, 100, 250, 500, 1000 ]; + + const DEFAULT_UPDATE_INTERVAL = 60000; + let intervalV = [ 10000, 30000, 60000, 180000, 600000, 1800000, 3600000 ]; + let intervalN = [ "10 s", "30 s", "1 m", "3 m", "10 m", "30 m", "1 h" ]; + + let stateFileErased = false; + + let settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (thresholdV.indexOf(settings.adjustThreshold) == -1) { + settings.adjustThreshold = DEFAULT_ADJUST_THRESHOLD; + } + + if (intervalV.indexOf(settings.updateInterval) == -1) { + settings.updateInterval = DEFAULT_UPDATE_INTERVAL; + } + + function onPpmChange(v) { + settings.ppm = v; + mainMenu['PPM x 10' ].value = v; + mainMenu['PPM x 1' ].value = v; + mainMenu['PPM x 0.1'].value = v; + } + + let mainMenu = { + '': { 'title' : 'Adjust Clock' }, + + '< Back': () => { + require('Storage').writeJSON(SETTINGS_FILE, settings); + back(); + }, + + /* + // NOT FULLY WORKING YET + 'Mode': { + value: settings.advanced, + format: v => v ? 'Advanced' : 'Basic', + onchange: () => { + settings.advanced = !settings.advanced; + } + }, + */ + + 'PPM x 10' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 10, + onchange : onPpmChange, + }, + + 'PPM x 1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 1, + onchange : onPpmChange, + }, + + 'PPM x 0.1' : { + value: settings.ppm, + format: v => v.toFixed(1), + step: 0.1, + onchange : onPpmChange, + }, + + 'Update Interval': { + value: intervalV.indexOf(settings.updateInterval), + min: 0, + max: intervalV.length - 1, + format: v => intervalN[v], + onchange: v => { + settings.updateInterval = intervalV[v]; + }, + }, + + 'Threshold': { + value: thresholdV.indexOf(settings.adjustThreshold), + min: 0, + max: thresholdV.length - 1, + format: v => thresholdV[v] + " ms", + onchange: v => { + settings.adjustThreshold = thresholdV[v]; + }, + }, + + 'Save State': { + value: settings.saveState, + format: v => v ? 'On' : 'Off', + onchange: () => { + settings.saveState = !settings.saveState; + if (!settings.saveState && !stateFileErased) { + stateFileErased = true; + require("Storage").erase(STATE_FILE); + } + }, + }, + + 'Debug Log': { + value: settings.debugLog, + format: v => v ? 'On' : 'Off', + onchange: () => { + settings.debugLog = !settings.debugLog; + }, + }, + }; + + E.showMenu(mainMenu); +}) diff --git a/apps/widadjust/widget.js b/apps/widadjust/widget.js new file mode 100644 index 000000000..138833783 --- /dev/null +++ b/apps/widadjust/widget.js @@ -0,0 +1,244 @@ +(() => { + // ====================================================================== + // CONST + + const DEBUG_LOG_FILE = 'widadjust.log'; + const SETTINGS_FILE = 'widadjust.json'; + const STATE_FILE = 'widadjust.state'; + + const DEFAULT_ADJUST_THRESHOLD = 100; + const DEFAULT_UPDATE_INTERVAL = 60 * 1000; + const MIN_INTERVAL = 10 * 1000; + + const MAX_CLOCK_ERROR_FROM_SAVED_STATE = 2000; + + const SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD = 1; + const SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD = 1; + const SAVE_STATE_PPM_DELTA_THRESHOLD = 1; + + // Widget width. + const WIDTH = 22; + + // ====================================================================== + // VARIABLES + + let settings; + let saved; + + let lastClockCheckTime = Date.now(); + let lastClockErrorUpdateTime; + + let clockError; + let currentUpdateInterval; + let lastPpm = null; + + let debugLogFile = null; + + // ====================================================================== + // FUNCTIONS + + function clockCheck() { + let now = Date.now(); + let elapsed = now - lastClockCheckTime; + lastClockCheckTime = now; + + let prevUpdateInterval = currentUpdateInterval; + currentUpdateInterval = settings.updateInterval; + setTimeout(clockCheck, lastClockCheckTime + currentUpdateInterval - Date.now()); + + // If elapsed time differs a lot from expected, + // some other app probably used setTime to change clock significantly. + // -> reset clock error since elapsed time can't be trusted + if (Math.abs(elapsed - prevUpdateInterval) > 10 * 1000) { + // RESET CLOCK ERROR + + clockError = 0; + lastClockErrorUpdateTime = now; + + debug( + 'Looks like some other app used setTime, so reset clockError. (elapsed = ' + + elapsed.toFixed(0) + ')' + ); + WIDGETS.adjust.draw(); + + } else if (!settings.advanced) { + // UPDATE CLOCK ERROR WITHOUT TEMPERATURE COMPENSATION + + updateClockError(settings.ppm); + } else { + // UPDATE CLOCK ERROR WITH TEMPERATURE COMPENSATION + + Bangle.getPressure().then(d => { + let temp = d.temperature; + updateClockError(settings.ppm0 + settings.ppm1 * temp + settings.ppm2 * temp * temp); + }).catch(e => { + WIDGETS.adjust.draw(); + }); + } + } + + function debug(line) { + console.log(line); + if (debugLogFile !== null) { + debugLogFile.write(line + '\n'); + } + } + + function draw() { + g.reset().setFont('6x8').setFontAlign(0, 0); + g.clearRect(this.x, this.y, this.x + WIDTH - 1, this.y + 23); + g.drawString(Math.round(clockError), this.x + WIDTH/2, this.y + 9); + + if (lastPpm !== null) { + g.setFont('4x6').setFontAlign(0, 1); + g.drawString(lastPpm.toFixed(1), this.x + WIDTH/2, this.y + 23); + } + } + + function loadSettings() { + settings = Object.assign({ + advanced: false, + saveState: true, + debugLog: false, + ppm: 0, + ppm0: 0, + ppm1: 0, + ppm2: 0, + adjustThreshold: DEFAULT_ADJUST_THRESHOLD, + updateInterval: DEFAULT_UPDATE_INTERVAL, + }, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + + if (settings.debugLog) { + if (debugLogFile === null) { + debugLogFile = require('Storage').open(DEBUG_LOG_FILE, 'a'); + } + } else { + debugLogFile = null; + } + + settings.updateInterval = Math.max(settings.updateInterval, MIN_INTERVAL); + } + + function onQuit() { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + let save = false; + + if (! settings.saveState) { + debug(new Date(now).toISOString() + ' QUIT'); + + } else if (saved === undefined) { + save = true; + debug(new Date(now).toISOString() + ' QUIT & SAVE STATE'); + + } else { + let elapsedSaved = now - saved.time; + let estimatedClockError = saved.clockError + elapsedSaved * saved.ppm / 1000000; + + let clockErrorDelta = updatedClockError - estimatedClockError; + let clockErrorDeltaInPpm = clockErrorDelta / elapsedSaved * 1000000; + let ppmDelta = ppm - saved.ppm; + + let debugA = new Date(now).toISOString() + ' QUIT'; + let debugB = + '\n> ' + updatedClockError.toFixed(2) + ' - ' + estimatedClockError.toFixed(2) + ' = ' + + clockErrorDelta.toFixed(2) + ' (' + + clockErrorDeltaInPpm.toFixed(1) + ' PPM) ; ' + + ppm.toFixed(1) + ' - ' + saved.ppm.toFixed(1) + ' = ' + ppmDelta.toFixed(1); + + if ((Math.abs(clockErrorDelta) >= SAVE_STATE_CLOCK_ERROR_DELTA_THRESHOLD + && Math.abs(clockErrorDeltaInPpm) >= SAVE_STATE_CLOCK_ERROR_DELTA_IN_PPM_THRESHOLD + ) || Math.abs(ppmDelta) >= SAVE_STATE_PPM_DELTA_THRESHOLD + ) + { + save = true; + debug(debugA + ' & SAVE STATE' + debugB); + } else { + debug(debugA + debugB); + } + } + + if (save) { + require('Storage').writeJSON(STATE_FILE, { + counter: (saved === undefined) ? 1 : saved.counter + 1, + time: Math.round(now), + clockError: Math.round(updatedClockError * 1000) / 1000, + ppm: Math.round(ppm * 1000) / 1000, + }); + } + } + + function updateClockError(ppm) { + let now = Date.now(); + let elapsed = now - lastClockErrorUpdateTime; + let drift = elapsed * ppm / 1000000; + clockError += drift; + lastClockErrorUpdateTime = now; + lastPpm = ppm; + + if (Math.abs(clockError) >= settings.adjustThreshold) { + let now = Date.now(); + // Shorter variables are faster to look up and this part is time sensitive. + let e = clockError / 1000; + setTime(getTime() - e); + debug( + new Date(now).toISOString() + ' -> ' + ((now / 1000 - e) % 60).toFixed(3) + + ' SET TIME (' + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + + WIDGETS.adjust.draw(); + } + + // ====================================================================== + // MAIN + + loadSettings(); + + WIDGETS.adjust = { + area: 'tr', + draw: draw, + now: () => { + let now = Date.now(); + // WIP + let ppm = (lastPpm !== null) ? lastPpm : settings.ppm; + let updatedClockError = clockError + (now - lastClockErrorUpdateTime) * ppm / 1000000; + return now - updatedClockError; + }, + width: WIDTH, + }; + + if (settings.saveState) { + saved = require('Storage').readJSON(STATE_FILE, true); + } + + let now = Date.now(); + lastClockErrorUpdateTime = now; + if (saved === undefined) { + clockError = 0; + debug(new Date().toISOString() + ' START'); + } else { + clockError = saved.clockError + (now - saved.time) * saved.ppm / 1000000; + + if (Math.abs(clockError) <= MAX_CLOCK_ERROR_FROM_SAVED_STATE) { + debug( + new Date().toISOString() + ' START & LOAD STATE (' + + clockError.toFixed(2) + ')' + ); + } else { + debug( + new Date().toISOString() + ' START & IGNORE STATE (' + + clockError.toFixed(2) + ')' + ); + clockError = 0; + } + } + + clockCheck(); + + E.on('kill', onQuit); + +})() diff --git a/apps/widcw/ChangeLog b/apps/widcw/ChangeLog new file mode 100644 index 000000000..a4bc24d1a --- /dev/null +++ b/apps/widcw/ChangeLog @@ -0,0 +1 @@ +0.01: First version \ No newline at end of file diff --git a/apps/widcw/logo.svg b/apps/widcw/logo.svg new file mode 100644 index 000000000..e093414d5 --- /dev/null +++ b/apps/widcw/logo.svg @@ -0,0 +1,62 @@ + + + + + + + + CW + + diff --git a/apps/widcw/metadata.json b/apps/widcw/metadata.json new file mode 100644 index 000000000..653b093ec --- /dev/null +++ b/apps/widcw/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widcw", + "name": "Calendar Week Widget", + "version": "0.01", + "description": "Widget which shows the current calendar week", + "icon": "widget.png", + "type": "widget", + "tags": "widget,calendar", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widcw.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widcw/widget.js b/apps/widcw/widget.js new file mode 100644 index 000000000..ef43a4551 --- /dev/null +++ b/apps/widcw/widget.js @@ -0,0 +1,48 @@ +(function() { + var width = 22; // width of the widget + + function draw() { + const x = this.x, y = this.y, x2 = x+21, y2 = y+23; + + var date = new Date(); + + // Calculate calendar week (https://stackoverflow.com/a/6117889) + getCW= function(date){ + var d=new Date(date.getFullYear(), date.getMonth(), date.getDate()); + var dayNum = d.getDay() || 7; + d.setDate(d.getDate() + 4 - dayNum); + var yearStart = new Date(d.getFullYear(),0,1); + return Math.ceil((((d - yearStart) / 86400000) + 1)/7); + }; + + g.reset().setFontAlign(0, 0) // center all text + // header + .setBgColor("#f00").setColor("#fff") + .clearRect(x, y, x2, y+8).setFont("4x6").drawString("CW", (x+x2)/2+1, y+5) + // date + .setBgColor("#fff").setColor("#000") + .clearRect(x, y+9, x2, y2).setFont("Vector:16").drawString(getCW(date), (x+x2)/2+2, y+17); + + if (!g.theme.dark) { + // black border around date for light themes + g.setColor("#000").drawPoly([ + x, y+9, + x, y2, + x2, y2, + x2, y+9 + ]); + } + + // redraw when date changes + setTimeout(()=>WIDGETS["widcw"].draw(), (86401 - Math.floor(date/1000) % 86400)*1000); + + } + + // add your widget + WIDGETS["widcw"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: width, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; + +})(); diff --git a/apps/widcw/widget.png b/apps/widcw/widget.png new file mode 100644 index 000000000..c73d40c5a Binary files /dev/null and b/apps/widcw/widget.png differ diff --git a/apps/widcw/widget.svg b/apps/widcw/widget.svg new file mode 100644 index 000000000..d3e567286 --- /dev/null +++ b/apps/widcw/widget.svg @@ -0,0 +1,55 @@ + + + + + + + + CW + + diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index 3d47b8a0c..f68fc701c 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -2,3 +2,4 @@ 0.02: Don't break if running on 2v08 firmware (just don't display anything) 0.03: Fix positioning 0.04: Show GPS fix status +0.05: Don't poll for GPS status, override setGPSPower handler (fix #1456) diff --git a/apps/widgps/metadata.json b/apps/widgps/metadata.json index 2f59aa82a..39bff2fad 100644 --- a/apps/widgps/metadata.json +++ b/apps/widgps/metadata.json @@ -1,7 +1,7 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.04", + "version": "0.05", "description": "Tiny widget to show the power and fix status of the GPS", "icon": "widget.png", "type": "widget", diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 25df2178a..bfdb89d33 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -1,7 +1,13 @@ (function(){ - if (!Bangle.isGPSOn) return; // old firmware + // override setGPSPower so we know if GPS is on or off + var oldSetGPSPower = Bangle.setGPSPower; + Bangle.setGPSPower = function(on,id) { + var isGPSon = oldSetGPSPower(on,id); + WIDGETS.gps.draw(); + return isGPSon; + } - function draw() { + WIDGETS.gps={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isGPSOn()) { const gpsObject = Bangle.getGPSFix(); @@ -14,20 +20,5 @@ g.setColor("#888"); // off = grey } g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.gps.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.gps.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.gps={area:"tr",width:24,draw:draw}; + }}; })(); diff --git a/modules/Layout.js b/modules/Layout.js index 1b0bbd47f..4223867a4 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -180,13 +180,6 @@ function Layout(layout, options) { if (l.id) ll[l.id] = l; // fix type up if (!l.type) l.type=""; - // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally - // fix fonts for pre-2v11 firmware - if (l.font && l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.c) l.c.forEach(recurser); } recurser(this._l); @@ -241,13 +234,13 @@ Layout.prototype.render = function (l) { "":function(){}, "txt":function(l){ if (l.wrap) { - g.setFont(l.font,l.fsz).setFontAlign(0,-1); + g.setFont(l.font).setFontAlign(0,-1); var lines = g.wrapString(l.label, l.w); var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); // TODO: on 2v11 we can just render in a single drawString call lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); } else { - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + g.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } }, "btn":function(l){ var x = l.x+(0|l.pad), y = l.y+(0|l.pad), @@ -365,7 +358,7 @@ Layout.prototype.update = function() { if (l.wrap) { l._h = l._w = 0; } else { - var m = g.setFont(l.font,l.fsz).stringMetrics(l.label); + var m = g.setFont(l.font).stringMetrics(l.label); l._w = m.width; l._h = m.height; } }, "btn": function(l) { diff --git a/modules/exstats.js b/modules/exstats.js index 8a726a5de..b72ee6584 100644 --- a/modules/exstats.js +++ b/modules/exstats.js @@ -119,6 +119,7 @@ Bangle.on("GPS", function(fix) { Bangle.on("step", function(steps) { if (!state.active) return; if (stats["step"]) stats["step"].emit("changed",stats["step"]); + state.stepHistory[0] += steps-state.lastStepCount; state.lastStepCount = steps; }); Bangle.on("HRM", function(h) { diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 000000000..630f61ee5 --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,2 @@ +./node_modules +!package-lock.json diff --git a/typescript/README.md b/typescript/README.md new file mode 100644 index 000000000..13800aeec --- /dev/null +++ b/typescript/README.md @@ -0,0 +1,29 @@ +# BangleTS + +A generic project setup for compiling apps from Typescript to Bangle.js ready, readable Javascript. +It includes types for _some_ of the modules and globals that are exposed for apps to use. +The goal is to have types for everything, but that will take some time. Feel free to help out by contributing! + +## Using the types + +All currently typed modules can be found in `/typescript/types.globals.d.ts`. +The typing is an ongoing process. If anything is still missing, you can add it! It will automatically be available in your TS files. + +## Compilation + +Install [npm](https://www.npmjs.com/get-npm) and node.js if you haven't already. We recommend using a version manager like nvm, which is also referenced in the linked documentation. +Make sure you are using node version 16 by running `nvm use 16` and npm version ^8 by running `npm -v`. If the latter version is incorrect, run `npm i -g npm@^8`. + +After having installed npm for your platform, open a terminal, and navigate into the `/typescript` folder. Then run: + +``` +npm ci +``` + +to install the project's build tools, and: + +``` +npm run build +``` + +To build all Typescript apps and widgets. The last command will generate the `app.js` files containing the transpiled code for the BangleJS. diff --git a/typescript/package-lock.json b/typescript/package-lock.json new file mode 100644 index 000000000..52be5f98a --- /dev/null +++ b/typescript/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "Bangle.ts", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "Bangle.ts", + "version": "0.0.1", + "devDependencies": { + "typescript": "4.5.2" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true + } + } +} diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 000000000..8cd38ce63 --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,13 @@ +{ + "name": "Bangle.ts", + "description": "Bangle.js Typescript Project Setup and Types", + "author": "Sebastian Di Luzio (https://diluz.io)", + "version": "0.0.1", + "devDependencies": { + "typescript": "4.5.2" + }, + "scripts": { + "build": "tsc", + "build:types": "tsc ./types/globals.d.ts" + } +} diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json new file mode 100644 index 000000000..d36465a01 --- /dev/null +++ b/typescript/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitAny": true, + "target": "es6", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true + }, + "include": ["../apps/**/*", "./**/*"], +} diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts new file mode 100644 index 000000000..2ef52dcdf --- /dev/null +++ b/typescript/types/globals.d.ts @@ -0,0 +1,184 @@ +// TODO all of these globals (copied from eslintrc) need to be typed at some point +/* The typing status is listed on the left of the attribute, e.g.: +status "Attribute" + + // Methods and Fields at https://banglejs.com/reference + "Array": "readonly", + "ArrayBuffer": "readonly", + "ArrayBufferView": "readonly", +started "Bangle": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "Boolean": "readonly", + "console": "readonly", + "DataView": "readonly", + "Date": "readonly", + "E": "readonly", + "Error": "readonly", + "Flash": "readonly", + "Float32Array": "readonly", + "Float64Array": "readonly", + "fs": "readonly", + "Function": "readonly", +started "Graphics": "readonly", +done "heatshrink": "readonly", + "I2C": "readonly", + "Int16Array": "readonly", + "Int32Array": "readonly", + "Int8Array": "readonly", + "InternalError": "readonly", + "JSON": "readonly", + "Math": "readonly", + "Modules": "readonly", + "NRF": "readonly", + "Number": "readonly", + "Object": "readonly", + "OneWire": "readonly", + "Pin": "readonly", + "process": "readonly", + "Promise": "readonly", + "ReferenceError": "readonly", + "RegExp": "readonly", + "Serial": "readonly", + "SPI": "readonly", + "Storage": "readonly", + "StorageFile": "readonly", + "String": "readonly", + "SyntaxError": "readonly", + "tensorflow": "readonly", + "TFMicroInterpreter": "readonly", + "TypeError": "readonly", + "Uint16Array": "readonly", + "Uint24Array": "readonly", + "Uint32Array": "readonly", + "Uint8Array": "readonly", + "Uint8ClampedArray": "readonly", + "Waveform": "readonly", + // Methods and Fields at https://banglejs.com/reference + "analogRead": "readonly", + "analogWrite": "readonly", + "arguments": "readonly", + "atob": "readonly", + "Bluetooth": "readonly", + "BTN": "readonly", + "BTN1": "readonly", + "BTN2": "readonly", + "BTN3": "readonly", + "BTN4": "readonly", + "BTN5": "readonly", + "btoa": "readonly", + "changeInterval": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clearWatch": "readonly", + "decodeURIComponent": "readonly", + "digitalPulse": "readonly", + "digitalRead": "readonly", + "digitalWrite": "readonly", + "dump": "readonly", + "echo": "readonly", + "edit": "readonly", + "encodeURIComponent": "readonly", + "eval": "readonly", + "getPinMode": "readonly", + "getSerial": "readonly", + "getTime": "readonly", + "global": "readonly", + "HIGH": "readonly", + "I2C1": "readonly", + "Infinity": "readonly", + "isFinite": "readonly", + "isNaN": "readonly", + "LED": "readonly", + "LED1": "readonly", + "LED2": "readonly", + "load": "readonly", + "LoopbackA": "readonly", + "LoopbackB": "readonly", + "LOW": "readonly", + "NaN": "readonly", + "parseFloat": "readonly", + "parseInt": "readonly", + "peek16": "readonly", + "peek32": "readonly", + "peek8": "readonly", + "pinMode": "readonly", + "poke16": "readonly", + "poke32": "readonly", + "poke8": "readonly", + "print": "readonly", +started "require": "readonly", + "reset": "readonly", + "save": "readonly", + "Serial1": "readonly", + "setBusyIndicator": "readonly", + "setInterval": "readonly", + "setSleepIndicator": "readonly", + "setTime": "readonly", + "setTimeout": "readonly", + "setWatch": "readonly", + "shiftOut": "readonly", + "SPI1": "readonly", + "Terminal": "readonly", + "trace": "readonly", + "VIBRATE": "readonly", + // Aliases and not defined at https://banglejs.com/reference +done "g": "readonly", +done "WIDGETS": "readonly" + */ + +// ambient JS definitions + +declare const require: ((module: 'heatshrink') => { + decompress: (compressedString: string) => string; +}) & // TODO add more + ((module: 'otherString') => {}); + +// ambient bangle.js definitions + +declare const Bangle: { + // functions + buzz: () => void; + drawWidgets: () => void; + isCharging: () => boolean; + // events + on(event: 'charging', listener: (charging: boolean) => void): void; + // TODO add more +}; + +declare type Image = { + width: number; + height: number; + bpp?: number; + buffer: ArrayBuffer | string; + transparent?: number; + palette?: Uint16Array; +}; + +declare type GraphicsApi = { + reset: () => void; + flip: () => void; + setColor: (color: string) => void; // TODO we can most likely type color more usefully than this + drawImage: ( + image: string | Image | ArrayBuffer, + xOffset: number, + yOffset: number, + options?: { + rotate?: number; + scale?: number; + } + ) => void; + // TODO add more +}; + +declare const Graphics: GraphicsApi; +declare const g: GraphicsApi; + +declare type Widget = { + area: 'tr' | 'tl'; + width: number; + draw: (this: { x: number; y: number }) => void; +}; +declare const WIDGETS: { [key: string]: Widget };