diff --git a/.eslintignore b/.eslintignore index e657b6260..7bbe41136 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ apps/animclk/V29.LBM.js apps/banglerun/rollup.config.js apps/schoolCalendar/fullcalendar/main.js apps/authentiwatch/qr_packed.js +*.test.js diff --git a/apps/7x7dotsclock/7x7dotsclock.app.js b/apps/7x7dotsclock/7x7dotsclock.app.js new file mode 100644 index 000000000..3f2e9b9b1 --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.app.js @@ -0,0 +1,356 @@ +/* +7x7DotsClock + +by Peter Kuppelwieser + +*/ + +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {}); + +// position on screen +var Xs = 0, Ys = 30,Xe = 175, Ye=175; +//const Xs = 0, Ys = 0,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]; + +const Font = [ + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,0,0,0,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0], + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,0,1,1,0,0], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,1,1,0,0], + [0,0,0,1,1,0,0] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,0,0,0,0,0], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1] + ], + [ + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [1,1,0,0,0,1,1], + [1,1,1,1,1,1,1], + [1,1,1,1,1,1,1], + [0,0,0,0,0,1,1], + [0,0,0,0,0,1,1] + ], + ]; + +// Global Vars +var dho = -1, eho = -1, dmo = -1, emo = -1; + + +function drawHSeg(x1,y1,x2,y2,Num,dColor,Size) { + g.setColor(0,0,0); + 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]); + 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.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1); + } + } + } +} + + +function drawSSeg(x1,y1,x2,y2,Num,dColor,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]); + g.fillCircle(x1+(i-1)*(x2-x1)/7,y1+(j-1)*(y2-y1)/7,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); + + + drawSSeg( (Xe-Xs) / 2 - 14 + Xs -1, + (Ye-Ys) / 2 - 7 + Ys , + (Xe-Xs) / 2 + Xs -1, + (Ye-Ys) / 2 + 7 + Ys, + ds,mColor,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); + +} + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); + + + dh = Math.floor(h/10); + eh = h - dh * 10; + + dm = Math.floor(m/10); + em = m - dm * 10; + + ds = Math.floor(s/10); + es = s - ds * 10; + + + // Reset the state of the graphics library + g.reset(); + if (dh != dho) { + g.setColor(1,1,1); + drawHSeg(Xs, Ys, Xs+SegW, Ys+SegH,dh,hColor,4); + dho = dh; + } + + if (eh != eho) { + g.setColor(1,1,1); + drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,hColor,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); + 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); + emo = em; + } + + if (!Bangle.isLocked()) ShowSecons(); + +} + + +function actions(v){ + if(BTN1.read() === true) { + print("BTN pressed"); + Bangle.showLauncher(); + } + + if(v==-1){ + print("up swipe event"); + if(settings.swupApp != "") load(settings.swupApp); + print(settings.swupApp); + } else if(v==1) { + print("down swipe event"); + if(settings.swdownApp != "") load(settings.swdownApp); + print(settings.swdownApp); + } else { + print("touch event"); + } +} + +// Get Messages status +var messages = require("Storage").readJSON("messages.json",1)||[]; + +//var BTconnected = NRF.getSecurityStatus().connected; +//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected); +//NRF.on('disconnect',BTconnected = NRF.getSecurityStatus().connected); + + +function drawWidgeds() { + + //Bluetooth + //print(BluetoothDevice.connected); + var x1Bt = 160; + var y1Bt = 0; + var x2Bt = x1Bt + 30; + var y2Bt = y2Bt; + + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt); + + + //Battery + //print(E.getBattery()); + //print(Bangle.isCharging()); + + var x1B = 130; + var y1B = 2; + var x2B = x1B + 20; + var y2B = y1B + 15; + + g.setColor(g.theme.bg); + g.clearRect(x1B,y1B,x2B,y2B); + + g.setColor(g.theme.fg); + g.drawRect(x1B,y1B,x2B,y2B); + g.fillRect(x1B,y1B,x1B+(E.getBattery()*(x2B-x1B)/100),y2B); + g.fillRect(x2B,y1B+(y2B-y1B)/2-3,x2B+4,y1B+(y2B-y1B)/2+3); + + + + //Messages + + var x1M = 100; + var y1M = y1B; + var x2M = x1M + 30; + var y2M = y2B; + + if (messages.some(m=>m.new)) { + g.setColor(g.theme.fg); + g.fillRect(x1M,y1M,x2M,y2M); + g.setColor(g.theme.bg); + g.drawLine(x1M,y1M,x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2); + g.drawLine(x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2,x2M,y1M); + } + + var strDow = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + var d = new Date(); + var dow = d.getDay(),day = d.getDate(), month = d.getMonth() + 1, year = d.getFullYear(); + + print(strDow[dow] + ' ' + day + '.' + month + ' ' + year); + + g.setFontAlign(-1, -1,0); + g.setFont("Vector", 20); + g.drawString(strDow[dow] + ' ' + day, 0, 0, true); + +} + + + + +function SetFull(on) { + dho = -1; eho = -1; dmo = -1; emo = -1; + g.clear(); + + if (on === true) { + Ys = 0; + Bangle.setUI("clock"); + Bangle.on('swipe', function(direction) { }); + + } else { + Ys = 30; + Bangle.setUI("updown",actions); + Bangle.on('swipe', function(direction) { + switch (direction) { + case 1: + print("swipe left event"); + if(settings.swleftApp != "") load(settings.swleftApp); + print(settings.swleftApp); + break; + case -1: + print("swipe right event"); + if(settings.swrightApp != "") load(settings.swrightApp); + print(settings.swrightApp); + break; + default: + print("swipe undefined event"); + } + }); + } + + SegH = (Ye-Ys)/2; + Dy = SegH/16; + + draw(); + + if (on != true) { + //Bangle.loadWidgets(); + //Bangle.drawWidgets(); + drawWidgeds(); + } +} + +Bangle.on('lock', function(on) { + SetFull(on); +}); + + +SetFull(Bangle.isLocked()); + +var secondInterval = setInterval(draw, 1000); diff --git a/apps/7x7dotsclock/7x7dotsclock.img.js b/apps/7x7dotsclock/7x7dotsclock.img.js new file mode 100644 index 000000000..b1f91c0bb --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.img.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEBAkTmEzkAHDmcjmQBBmcTmICCgMAiMAkE/+P/mEQgMQgH/n/zAIP/l/yA4QvXC4kDkEjFgIACkcSmMTkMyBoQHBI4kvI6wXBn8wA4c/mfzl8y+cfEoIaBVa5HBAAMQF4UgIoIBBBgJNBAwQ3BkfygSnJSQIUBkECiBoCL48DmCPFAA6PCX40jX4hYEU4LNBX4JHIkBHCBgJHBianKj8wO4IvHgSnBmJ3CHYqGCABcRcYTXLAA5KCFAJfCC4KnDX4anNgUgiSnMkQQBO5hvCl8yO4pHEd4oyBH4QBBU5TXHkcimUTkLXFL44HEiTbBO4MhBoQHBI4KECR45HGBoIFBU4y/BC4c/mYXGMQJHFiBHLEAIHCf5gAKhWg1UB0IEBjUA0MB0EAjQKCiANCCQOg0cxmcSmWjU4MqmcDmSnDBASkBmejCQIXFmYXEmYXHicyhRLC0AEBAIJFBAIIFCBAYHDF65fXR66vImUCnS8IkeinUBgERgEgcIMBgRHDBgLvCBYMQmcjBYIAHfwL7JiQLBichkcSnUSO4MhI4MxI5MSmMjPgMinCnCkRHGIgJHFiUgkUalUCAgMRkUCkIvIkUSkMC0EiBxAAI0UKkBHCkCPDgA+CI5Z3BmYPBAB53CV4MSEgcSiCnOR4cyR5JQEgBHCC4I0BC4UjC4MCxQXGF4IlBxRHB0UAlUK0BMBkIEBI5ILB0ZHBF4czlTXHI4mjCQIXOH4KnDC4MKgGqgGgAgIBBIoJHJBoQ=")) diff --git a/apps/7x7dotsclock/7x7dotsclock.settings.js b/apps/7x7dotsclock/7x7dotsclock.settings.js new file mode 100644 index 000000000..42473ec17 --- /dev/null +++ b/apps/7x7dotsclock/7x7dotsclock.settings.js @@ -0,0 +1,65 @@ +(function(back) { + +let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, 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); + settings[key] = value; + + print("storing settings 7x7dotsclock.json"); + storage.write('7x7dotsclock.json', settings); +} + + +function showSelAppMenu(key) { + var Apps = require("Storage").list(/\.info$/) + .map(app => {var a=storage.readJSON(app, 1);return ( + a&&a.name != "Launcher" + && a&&a.name != "Bootloader" + && a&&a.type != "clock" + && a&&a.type !="widget" + )?a:undefined}) + .filter(app => app) // filter out any undefined apps + .sort((a, b) => a.sortorder - b.sortorder); + const SelAppMenu = { + '': { + 'title': /*LANG*/'Select App', + }, + '< Back': ()=>showMainMenu(), + }; + Apps.forEach((app, index) => { + var label = app.name; + if (settings[key] === app.src) { + label = "* " + label; + } + SelAppMenu[label] = () => { + if (settings[key] !== app.src) { + setSetting(key,app.src); + showMainMenu(); + } + }; + }); + if (Apps.length === 0) { + SelAppMenu[/*LANG*/"No Apps Found"] = () => { }; + } + return E.showMenu(SelAppMenu); +} + +showMainMenu(); + +}) diff --git a/apps/7x7dotsclock/ChangeLog b/apps/7x7dotsclock/ChangeLog new file mode 100644 index 000000000..50c5eca4a --- /dev/null +++ b/apps/7x7dotsclock/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version for upload diff --git a/apps/7x7dotsclock/README.md b/apps/7x7dotsclock/README.md new file mode 100644 index 000000000..7f899ff0f --- /dev/null +++ b/apps/7x7dotsclock/README.md @@ -0,0 +1,17 @@ +# 7x7 dots clock + +![](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 + +![](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 +* button press opens launcher + diff --git a/apps/7x7dotsclock/dotsfontclock-scr1.png b/apps/7x7dotsclock/dotsfontclock-scr1.png new file mode 100644 index 000000000..dc86396c0 Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock-scr1.png differ diff --git a/apps/7x7dotsclock/dotsfontclock.png b/apps/7x7dotsclock/dotsfontclock.png new file mode 100644 index 000000000..af8fa61ba Binary files /dev/null and b/apps/7x7dotsclock/dotsfontclock.png differ diff --git a/apps/7x7dotsclock/metadata.json b/apps/7x7dotsclock/metadata.json new file mode 100644 index 000000000..7b2c52512 --- /dev/null +++ b/apps/7x7dotsclock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "7x7dotsclock", + "name": "7x7 Dots Clock", + "shortName":"7x7 Dots Clock", + "version":"0.01", + "description": "A clock with a big 7x7 dots Font", + "icon": "dotsfontclock.png", + "tags": "clock", + "type": "clock", + "supports" : ["BANGLEJS2"], + "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"}] +} diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 4dca8053e..ac49dcbd7 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -7,4 +7,5 @@ when weekday name "On": weekday name is cut at 6th position and .# is added 0.06: fixes #1271 - wrong settings name when weekday name and calendar weeknumber are on then display is # - week is buffered until date or timezone changes \ No newline at end of file + week is buffered until date or timezone changes +0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users) \ No newline at end of file diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json index def5d3b48..4d26dd0c7 100644 --- a/apps/antonclk/metadata.json +++ b/apps/antonclk/metadata.json @@ -1,7 +1,7 @@ { "id": "antonclk", "name": "Anton Clock", - "version": "0.06", + "version": "0.07", "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", "readme":"README.md", "icon": "app.png", diff --git a/apps/antonclk/settings.js b/apps/antonclk/settings.js index e452b02c7..6882cbd0f 100644 --- a/apps/antonclk/settings.js +++ b/apps/antonclk/settings.js @@ -38,7 +38,7 @@ }, "< Back": () => back(), "Seconds...": () => E.showMenu(secmenu), - "Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]), + "Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]), "Show Weekday": { value: (settings.weekDay !== undefined ? settings.weekDay : true), format: v => v ? "On" : "Off", @@ -56,7 +56,7 @@ } }, "Uppercase": { - value: (settings.upperCase !== undefined ? settings.upperCase : false), + value: (settings.upperCase !== undefined ? settings.upperCase : true), format: v => v ? "On" : "Off", onchange: v => { settings.upperCase = v; @@ -81,7 +81,7 @@ "< Back": () => E.showMenu(mainmenu), "Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]), "With \":\"": { - value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false), + value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true), format: v => v ? "On" : "Off", onchange: v => { settings.secondsWithColon = v; @@ -89,14 +89,14 @@ } }, "Color": { - value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false), + value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true), format: v => v ? "On" : "Off", onchange: v => { settings.secondsColoured = v; writeSettings(); } }, - "Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"]) + "Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"]) }; // Actually display the menu diff --git a/apps/blackjack/ChangeLog b/apps/blackjack/ChangeLog index 25b5f9195..8e468e9ad 100644 --- a/apps/blackjack/ChangeLog +++ b/apps/blackjack/ChangeLog @@ -1,2 +1,3 @@ 0.01: New game! BTN4- Hit card, BTN5- Stand -0.02: ignore buttons on pauses \ No newline at end of file +0.02: Ignore buttons on pauses +0.03: Support Bangle.js 2 diff --git a/apps/blackjack/appb2.js b/apps/blackjack/appb2.js new file mode 100644 index 000000000..c9907487d --- /dev/null +++ b/apps/blackjack/appb2.js @@ -0,0 +1,207 @@ +var Clubs = require("heatshrink").decompress(atob("j0ewcBkmSpICipEAiQLHwA3BBY8gBQMEEA1AJwQgGyAKChILGBQUCFgxwDJpEAO5AVCII44CAQI1GAAg1GAAZQCWxCDEAAqJBQYQAFRIJWCAApcCR4YADPoRWCgQdBPopfCwAdBTw47BcBAvBU44vDfBDUIRIbUHATuQ")); + +var Spades = require("heatshrink").decompress(atob("j0ewcBkmSpICuoALJIQILHpAKBJQ+QLIUJBYsgMoY1GBQcCBYmAPgkSEBEAgggIKApBDIg4KFHAZiCAAgsDBQw4DFitJFhQ4FTwplBgRoCSQoRBBYJ6EF4jgUwDUHAVOQA==")); + +var Hearts = require("heatshrink").decompress(atob("j0ewY96gMkyAEByVIBQcSpILBhMkBYkEyQLBAQYKCCIQLEEwQgCBYuAEBFJkBBCBYw4CEA44CgQLHIYQsHLJsAEBJEHSQhxENwQADMQoAEKAdAWowLCYJESXggAFGowA/AAQ")); + +var Diamonds = require("heatshrink").decompress(atob("j0ewY1ykgKJhIKJiVIEBOSoAKHpILBBQ+SBYOQBIsBCgILBwAKEgQgCAQIKEggICAQMgKwgUDAQI1GBY4IFLgoLGJpGSPoo4EMoxNIMoqSHiR6HLgizIPoLgfAFA")); + +var deck = []; +var player = {Hand:[]}; +var computer = {Hand:[]}; +var ctx = {ready:true}; + +function createDeck() { + var suits = ["Spades", "Hearts", "Diamonds", "Clubs"]; + var values = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]; + + var dck = []; + for (var i = 0 ; i < values.length; i++) { + for(var x = 0; x < suits.length; x++) { + dck.push({ Value: values[i], Suit: suits[x] }); + } + } + return dck; +} + +function shuffle(a) { + var j, x, i; + for (i = a.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = a[i]; + a[i] = a[j]; + a[j] = x; + } + return a; +} + +function EndGameMessdage(msg){ + ctx.ready = false; + g.clearRect(0,160,176,176); + g.setColor(255,255,255); + g.fillRect(0,160,176,176); + g.setColor(0,0,0); + g.drawString(msg, 12, 155); + setTimeout(function(){ + startGame(); + }, 2500); + +} + +function hitMe() { + if (!ctx.ready) return; + player.Hand.push(deck.pop()); + renderOnScreen(1); + var playerWeight = calcWeight(player.Hand, 0); + + if(playerWeight == 21) + EndGameMessdage('WINNER'); + else if(playerWeight > 21) + EndGameMessdage('LOSER'); +} + +function calcWeight(hand, hideCard) { + if(hideCard === 1) { + if (hand[0].Value == "J" || hand[0].Value == "Q" || hand[0].Value == "K") + return "10 +"; + else if (hand[0].Value == "A") + return "11 +"; + else + return parseInt(hand[0].Value) +" +"; + } + else { + var weight = 0; + for(i=0; i 21 || bangleWeight < playerWeight) + EndGameMessdage('WINNER'); + else if(bangleWeight > playerWeight) + EndGameMessdage('LOOSER'); +} + +function renderOnScreen(HideCard) { + const fontName = "6x8"; + + g.clear(); // clear screen + g.reset(); // default draw styles + g.setFont(fontName, 1); + + g.setColor(255,255,255); + g.fillRect(Bangle.appRect); + g.setColor(0,0,0); + + g.drawString('Hit', 176/4-10, 160); + g.drawString('Stand', 176/4+176/2-10, 160); + + g.setFont(fontName, 3); + for(i=0; i right; + + if(is_left){ + hitMe(); + + } else if(is_right){ + stand(); + } +}); +setWatch(startGame, BTN1, {repeat:true, edge:"falling"}); + +startGame(); diff --git a/apps/blackjack/metadata.json b/apps/blackjack/metadata.json index 331c64040..837b891bd 100644 --- a/apps/blackjack/metadata.json +++ b/apps/blackjack/metadata.json @@ -2,15 +2,16 @@ "id": "blackjack", "name": "Black Jack game", "shortName": "Black Jack game", - "version": "0.02", + "version": "0.03", "description": "Simple implementation of card game Black Jack", "icon": "blackjack.png", "tags": "game", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}], "allow_emulator": true, "storage": [ - {"name":"blackjack.app.js","url":"blackjack.app.js"}, + {"name":"blackjack.app.js","url":"blackjack.app.js","supports": ["BANGLEJS"]}, + {"name":"blackjack.app.js","url":"appb2.js","supports": ["BANGLEJS2"]}, {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} ] } diff --git a/apps/boldclk/ChangeLog b/apps/boldclk/ChangeLog index c7a4ba7b4..30ac31c61 100644 --- a/apps/boldclk/ChangeLog +++ b/apps/boldclk/ChangeLog @@ -2,3 +2,4 @@ 0.03: Tweak for more efficient rendering, and firmware 2v06 0.04: Work with themes, smaller screens 0.05: Adjust hand lengths to be within 'tick' points +0.06: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up". diff --git a/apps/boldclk/bold_clock.js b/apps/boldclk/bold_clock.js index 4358b2e29..9d3ea0756 100644 --- a/apps/boldclk/bold_clock.js +++ b/apps/boldclk/bold_clock.js @@ -129,14 +129,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(); Bangle.loadWidgets(); diff --git a/apps/boldclk/metadata.json b/apps/boldclk/metadata.json index 7e3941cb3..cf961347d 100644 --- a/apps/boldclk/metadata.json +++ b/apps/boldclk/metadata.json @@ -1,7 +1,7 @@ { "id": "boldclk", "name": "Bold Clock", - "version": "0.05", + "version": "0.06", "description": "Simple, readable and practical clock", "icon": "bold_clock.png", "screenshots": [{"url":"screenshot_bold.png"}], diff --git a/apps/contourclock/ChangeLog b/apps/contourclock/ChangeLog index 0b6709d24..9c62e637b 100644 --- a/apps/contourclock/ChangeLog +++ b/apps/contourclock/ChangeLog @@ -4,3 +4,4 @@ 0.22: Changed timing code, original "Nunito" Font is back! 0.23: Customizer! Unused fonts no longer take up precious memory. 0.24: Added previews to the customizer. +0.25: Fixed a bug that would let widgets change the color of the clock. diff --git a/apps/contourclock/app.js b/apps/contourclock/app.js index a5440845d..cdfadd217 100644 --- a/apps/contourclock/app.js +++ b/apps/contourclock/app.js @@ -10,6 +10,7 @@ if (settings.fontIndex==undefined) { function draw() { var date = new Date(); // Draw day of the week + g.reset(); g.setFont("Teletext10x18Ascii"); g.clearRect(0,138,g.getWidth()-1,176); g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18); diff --git a/apps/contourclock/metadata.json b/apps/contourclock/metadata.json index a5d764f2d..54d799127 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.24", + "version":"0.25", "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/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 8b1a3e4aa..e2ee53ede 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -7,3 +7,5 @@ 0.07: Fix "previous" button image 0.08: Fix scrolling title background color 0.09: Move event listener from widget to boot code, stops music from showing up in messages +0.10: Simplify touch events + Remove date+time diff --git a/apps/gbmusic/README.md b/apps/gbmusic/README.md index 5d06164c2..f1f9c4378 100644 --- a/apps/gbmusic/README.md +++ b/apps/gbmusic/README.md @@ -3,9 +3,10 @@ If you have an Android phone with Gadgetbridge, this app allows you to view and control music playback. -| Bangle.js 1 | Bangle.js 2 | -|:-------------------------------------------|:-------------------------------------------| -| ![Screenshot: Bangle 1](screenshot_v1.png) | ![Screenshot: Bangle 2](screenshot_v2.png) | +| Bangle.js 1 | Bangle.js 2 | +|:---------------------------------------------------------|:---------------------------------------------------------| +| ![Screenshot: Bangle 1 Dark theme](screenshot_v1_d.png) | ![Screenshot: Bangle 2 Darm theme](screenshot_v2_d.png) | +| ![Screenshot: Bangle 1 Light theme](screenshot_v1_l.png) | ![Screenshot: Bangle 2 Light theme](screenshot_v2_l.png) | Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/). @@ -14,7 +15,6 @@ Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages * Dynamic colors based on Track/Artist/Album name * Scrolling display for long titles * Automatic start when music plays -* Time and date display ## Settings @@ -40,9 +40,7 @@ Disable double/triple pressing Middle Button: always simply toggle play/pause. * Button 3 (*Bangle.js 1*): Volume down ### Touch -* Left: Pause/previous song -* Right: Next song/resume -* Center: Toggle play/pause +* Touch: Toggle play/pause * Swipe left/right: Next/previous song * Swipe up/down (*Bangle.js 2*): Volume up/down diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index c8395f745..5252ac0ac 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -10,15 +10,15 @@ const BANGLE2 = process.env.HWVERSION===2; /** * @param {string} text - * @return {number} Maximum font size to make text fit on screen + * @param {number} w Width to fit text in + * @return {number} Maximum font size to make text fit */ -function fitText(text) { +function fitText(text, w) { if (!text.length) { return Infinity; } // make a guess, then shrink/grow until it fits - const w = Bangle.appRect.w, - test = (s) => g.setFont("Vector", s).stringWidth(text); + const test = (s) => g.setFont("Vector", s).stringWidth(text); let best = Math.floor(100*w/test(100)); if (test(best)===w) { // good guess! return best; @@ -106,7 +106,7 @@ function rTitle(l) { rScroller(l); // already scrolling return; } - let size = fitText(l.label); + let size = fitText(l.label, l.w); if (sizel.h) { size = l.h; } @@ -182,23 +182,17 @@ function makeUI() { type: "v", c: [ { type: "h", fillx: 1, c: [ - {id: "time", type: "txt", label: "88:88", valign: -1, halign: -1, font: "8%", bgCol: g.theme.bg}, {fillx: 1}, - {id: "num", type: "txt", label: "88:88", valign: -1, halign: 1, font: "12%", bgCol: g.theme.bg}, - BANGLE2 ? {} : {id: "up", type: "txt", label: " +", font: "6x8:2"}, + {id: "num", type: "txt", label: "", valign: -1, halign: -1, font: "12%", bgCol: g.theme.bg}, + BANGLE2 ? {} : {id: "up", type: "txt", label: " +", halign: 1, font: "6x8:2"}, ], }, {id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg}, {id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg}, - {id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg}, - {height: 10}, { type: "h", c: [ - {width: 3}, - {id: "prev", type: "custom", height: 15, width: 15, icon: "previous", render: rIcon, bgCol: g.theme.bg}, - {id: "date", type: "txt", halign: 0, valign: 1, label: "", font: "8%", fillx: 1, bgCol: g.theme.bg}, - {id: "next", type: "custom", height: 15, width: 15, icon: "next", render: rIcon, bgCol: g.theme.bg}, - BANGLE2 ? {width: 3} : {id: "down", type: "txt", label: " -", font: "6x8:2"}, + {id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg}, + BANGLE2 ? {} : {id: "down", type: "txt", label: " -", font: "6x8:2"}, ], }, {height: 10}, @@ -211,20 +205,6 @@ function makeUI() { // Self-repeating timeouts /////////////////////// -// Clock -let tock = -1; -function tick() { - if (!BANGLE2 && !Bangle.isLCDOn()) { - return; - } - const now = new Date(); - if (now.getHours()*60+now.getMinutes()!==tock) { - drawDateTime(); - tock = now.getHours()*60+now.getMinutes(); - } - setTimeout(tick, 1000); // we only show minute precision anyway -} - // Fade out while paused and auto closing let fade = null; function fadeOut() { @@ -271,40 +251,12 @@ function scrollStop() { //////////////////// // Drawing functions //////////////////// -/** - * Draw date and time - */ -function drawDateTime() { - const now = new Date(); - const l = require("locale"); - const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; - if (is12) { - const d12 = new Date(now.getTime()); - const hour = d12.getHours(); - if (hour===0) { - d12.setHours(12); - } else if (hour>12) { - d12.setHours(hour-12); - } - layout.time.label = l.time(d12, true)+l.meridian(now); - } else { - layout.time.label = l.time(now, true); - } - layout.date.label = require("locale").date(now, true); - layout.render(); -} function drawControls() { - let l = layout; + if (BANGLE2) return; const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive - if (!BANGLE2) { - l.up.col = cc("volumeup" in tCommand); - l.down.col = cc("volumedown" in tCommand); - } - l.prev.icon = (stat==="play") ? "pause" : "previous"; - l.prev.col = cc("prev" in tCommand || "pause" in tCommand); - l.next.icon = (stat==="play") ? "next" : "play"; - l.next.col = cc("next" in tCommand || "play" in tCommand); + layout.up.col = cc("volumeup" in tCommand); + layout.down.col = cc("volumedown" in tCommand); layout.render(); } @@ -339,6 +291,7 @@ function info(info) { layout.album.col = infoColor("album"); layout.artist.col = infoColor("artist"); layout.num.label = formatNum(info); + layout.update(); layout.render(); rTitle(layout.title); // force redraw of title, or scroller might break // reset auto exit interval @@ -473,37 +426,16 @@ function sendCommand(command) { drawControls(); } -// touch/swipe: navigation function togglePlay() { sendCommand(stat==="play" ? "pause" : "play"); } -function pausePrev() { - sendCommand(stat==="play" ? "pause" : "previous"); -} -function nextPlay() { - sendCommand(stat==="play" ? "next" : "play"); -} /** * Setup touch+swipe for Bangle.js 1 */ function touch1() { - Bangle.on("touch", side => { - if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware - switch(side) { - case 1: - pausePrev(); - break; - case 2: - nextPlay(); - break; - default: - togglePlay(); - break; - } - }); + Bangle.on("touch", togglePlay); Bangle.on("swipe", dir => { - if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware sendCommand(dir===1 ? "previous" : "next"); }); } @@ -511,16 +443,7 @@ function touch1() { * Setup touch+swipe for Bangle.js 2 */ function touch2() { - Bangle.on("touch", (side, xy) => { - const ar = Bangle.appRect; - if (xy.xar.x+ar.w*2/3) { - nextPlay(); - } else { - togglePlay(); - } - }); + Bangle.on("touch", togglePlay); // swiping let drag; Bangle.on("drag", e => { @@ -595,7 +518,6 @@ function startWatches() { function start() { makeUI(); startWatches(); - tick(); startEmulator(); } diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json index f578f1f48..0ded80452 100644 --- a/apps/gbmusic/metadata.json +++ b/apps/gbmusic/metadata.json @@ -2,10 +2,11 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.09", + "version": "0.10", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", - "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], + "screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"}, + {"url":"screenshot_v2_d.png"},{"url":"screenshot_v2_l.png"}], "type": "app", "tags": "tools,bluetooth,gadgetbridge,music", "supports": ["BANGLEJS","BANGLEJS2"], diff --git a/apps/gbmusic/screenshot_v1.png b/apps/gbmusic/screenshot_v1.png deleted file mode 100644 index 3b290e459..000000000 Binary files a/apps/gbmusic/screenshot_v1.png and /dev/null differ diff --git a/apps/gbmusic/screenshot_v1_d.png b/apps/gbmusic/screenshot_v1_d.png new file mode 100644 index 000000000..3a6d84cff Binary files /dev/null and b/apps/gbmusic/screenshot_v1_d.png differ diff --git a/apps/gbmusic/screenshot_v1_l.png b/apps/gbmusic/screenshot_v1_l.png new file mode 100644 index 000000000..1b6b57809 Binary files /dev/null and b/apps/gbmusic/screenshot_v1_l.png differ diff --git a/apps/gbmusic/screenshot_v2.png b/apps/gbmusic/screenshot_v2.png deleted file mode 100644 index b89b5022e..000000000 Binary files a/apps/gbmusic/screenshot_v2.png and /dev/null differ diff --git a/apps/gbmusic/screenshot_v2_d.png b/apps/gbmusic/screenshot_v2_d.png new file mode 100644 index 000000000..761e1d14a Binary files /dev/null and b/apps/gbmusic/screenshot_v2_d.png differ diff --git a/apps/gbmusic/screenshot_v2_l.png b/apps/gbmusic/screenshot_v2_l.png new file mode 100644 index 000000000..5cf05c7cb Binary files /dev/null and b/apps/gbmusic/screenshot_v2_l.png differ diff --git a/apps/info/ChangeLog b/apps/info/ChangeLog new file mode 100644 index 000000000..07afedd21 --- /dev/null +++ b/apps/info/ChangeLog @@ -0,0 +1 @@ +0.01: Release \ No newline at end of file diff --git a/apps/info/README.md b/apps/info/README.md new file mode 100644 index 000000000..007a9794e --- /dev/null +++ b/apps/info/README.md @@ -0,0 +1,17 @@ +# Info + +A very simple app that shows information on 3 different screens. +Go to the next screen via tab right, go to the previous screen +via tab left and reload the data via tab in the middle of the +screen. Very useful if combined with pattern launcher ;) + +![](screenshot_1.png) +![](screenshot_2.png) +![](screenshot_2.png) + + +## Contributors +- [David Peer](https://github.com/peerdavid). + +## Thanks To +Info icons created by Freepik - Flaticon diff --git a/apps/info/info.app.js b/apps/info/info.app.js new file mode 100644 index 000000000..c61a88045 --- /dev/null +++ b/apps/info/info.app.js @@ -0,0 +1,108 @@ +var s = require("Storage"); +const locale = require('locale'); +var ENV = process.env; +var W = g.getWidth(), H = g.getHeight(); +var screen = 0; +const maxScreen = 2; + +function getVersion(file) { + var j = s.readJSON(file,1); + var v = ("object"==typeof j)?j.version:false; + return v?((v?"v"+v:"Unknown")):"NO "; +} + + +function drawData(name, value, y){ + g.drawString(name, 5, y); + g.drawString(value, 100, y); +} + +function getSteps(){ + try{ + return Bangle.getHealthStatus("day").steps; + } catch(e) { + return ">= 2v12"; + } +} + +function getBpm(){ + try{ + return Math.round(Bangle.getHealthStatus("day").bpm) + "bpm"; + } catch(e) { + return ">= 2v12"; + } +} + +function drawInfo() { + g.reset().clearRect(Bangle.appRect); + var h=18, y = h;//-h; + + // Header + g.setFont("Vector", h+2).setFontAlign(0,-1); + g.drawString("--==|| INFO ||==--", W/2, 0); + g.setFont("Vector",h).setFontAlign(-1,-1); + + // Dynamic data + if(screen == 0){ + drawData("Steps", getSteps(), y+=h); + drawData("HRM", getBpm(), y+=h); + drawData("Battery", E.getBattery() + "%", y+=h); + drawData("Voltage", E.getAnalogVRef().toFixed(2) + "V", y+=h); + drawData("IntTemp.", locale.temp(parseInt(E.getTemperature())), y+=h); + } + + if(screen == 1){ + drawData("Charging?", Bangle.isCharging() ? "Yes" : "No", y+=h); + drawData("Bluetooth", NRF.getSecurityStatus().connected ? "Conn." : "Disconn.", y+=h); + drawData("GPS", Bangle.isGPSOn() ? "On" : "Off", y+=h); + drawData("Compass", Bangle.isCompassOn() ? "On" : "Off", y+=h); + drawData("HRM", Bangle.isHRMOn() ? "On" : "Off", y+=h); + } + + // Static data + if(screen == 2){ + drawData("Firmw.", ENV.VERSION, y+=h); + drawData("Boot.", getVersion("boot.info"), y+=h); + drawData("Settings", getVersion("setting.info"), y+=h); + drawData("Storage", "", y+=h); + drawData(" Total", ENV.STORAGE>>10, y+=h); + drawData(" Free", require("Storage").getFree()>>10, y+=h); + } + + if(Bangle.isLocked()){ + g.setFont("Vector",h-2).setFontAlign(-1,-1); + g.drawString("Locked", 0, H-h+2); + } + + g.setFont("Vector",h-2).setFontAlign(1,-1); + g.drawString((screen+1) + "/3", W, H-h+2); +} + +drawInfo(); +setWatch(_=>load(), BTN1); + +Bangle.on('touch', function(btn, e){ + var left = parseInt(g.getWidth() * 0.3); + var right = g.getWidth() - left; + var isLeft = e.x < left; + var isRight = e.x > right; + + if(isRight){ + screen = (screen + 1) % (maxScreen+1); + } + + if(isLeft){ + screen -= 1; + screen = screen < 0 ? maxScreen : screen; + } + + drawInfo(); +}); + +Bangle.on('lock', function(isLocked) { + drawInfo(); +}); + +Bangle.loadWidgets(); +for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +// Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/info/info.icon.js b/apps/info/info.icon.js new file mode 100644 index 000000000..8dbab8357 --- /dev/null +++ b/apps/info/info.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcBkmSpICDBwcJBYwCDpAhFggRJGg8SCI+ABgU//gSDCI4JBj//AAX4JRAIBg4QDAAPgBIJWGgIQFAAI+BLglAgEPCI/wEgJoEgYQHAAPANwhWFAApcBCIWQgAQJAAMAgSMDCJiSCwB6GQA6eCn5TFL4q5BUgIRF/wuBv4RGkCeGO4IREUgMBCJCVGCISwIWw0BYRLIICLBHHCJRrGCIQIFR44I5LIoRaPpARcdIwRJfYMBCJuACKUkgE/a5f8gEJCJD7FCIeAg78FAAvggFJCIMACJZOBCIOQCJsCCIOSgEfCBP4gESCIZTFOIwRDoDIGaguSCIVIgCkFTwcAggRDpIYBQAx6BgAOCAQYIBLghWBTwQRFFgIABXIIFDBwgCDBYQAENAYCFLgIAEKwpKIIhA=")) diff --git a/apps/info/info.png b/apps/info/info.png new file mode 100644 index 000000000..c73813025 Binary files /dev/null and b/apps/info/info.png differ diff --git a/apps/info/metadata.json b/apps/info/metadata.json new file mode 100644 index 000000000..f05f0e134 --- /dev/null +++ b/apps/info/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "info", + "name": "Info", + "version": "0.01", + "description": "An application that displays information such as battery level, steps etc.", + "icon": "info.png", + "type": "app", + "tags": "tool", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "screenshots": [ + {"url":"screenshot_1.png"}, + {"url":"screenshot_2.png"}, + {"url":"screenshot_3.png"}], + "storage": [ + {"name":"info.app.js","url":"info.app.js"}, + {"name":"info.img","url":"info.icon.js","evaluate":true} + ] +} diff --git a/apps/info/screenshot_1.png b/apps/info/screenshot_1.png new file mode 100644 index 000000000..97d42a896 Binary files /dev/null and b/apps/info/screenshot_1.png differ diff --git a/apps/info/screenshot_2.png b/apps/info/screenshot_2.png new file mode 100644 index 000000000..2d25dd4e6 Binary files /dev/null and b/apps/info/screenshot_2.png differ diff --git a/apps/info/screenshot_3.png b/apps/info/screenshot_3.png new file mode 100644 index 000000000..782e4a195 Binary files /dev/null and b/apps/info/screenshot_3.png differ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 4811cd19b..07da48198 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -15,7 +15,7 @@ 0.10: Respect the 'new' attribute if it was set from iOS integrations 0.11: Open app when touching the widget (Bangle.js 2 only) 0.12: Extra app-specific notification icons - New animated notifcationicon (instead of large blinking 'MESSAGES') + New animated notification icon (instead of large blinking 'MESSAGES') Added screenshots 0.13: Add /*LANG*/ comments for internationalisation Add 'Delete All' option to message options @@ -31,3 +31,6 @@ 0.19: Use a larger font for message text if it'll fit 0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) 0.21: Improve list readability on dark theme +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 diff --git a/apps/messages/app.js b/apps/messages/app.js index 4aaf97369..96c48f02f 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -83,6 +83,7 @@ function getMessageImage(msg) { 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="); + if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); if (s=="gmail") return getNotificationImage(); if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); @@ -114,6 +115,7 @@ function getMessageImageCol(msg,def) { "facebook": "#4267b2", "gmail": "#ea4335", "google home": "#fbbc05", + "home assistant": "#fff", // ha-blue is #41bdf5, but that's the background "hangouts": "#1ba261", "instagram": "#dd2a7b", "messenger": "#0078ff", @@ -123,7 +125,7 @@ function getMessageImageCol(msg,def) { "telegram": "#0088cc", "twitter": "#1da1f2", "whatsapp": "#4fce5d", - "wordfeud": "#dcc8bd", + "wordfeud": "#e7d3c7", }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); } diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 6834693ae..7488c792e 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,8 +1,8 @@ { "id": "messages", "name": "Messages", - "version": "0.21", - "description": "App to display notifications from iOS and Gadgetbridge", + "version": "0.22", + "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", "tags": "tool,system", diff --git a/apps/messages/settings.js b/apps/messages/settings.js index c865a37fb..39abfc131 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -4,6 +4,7 @@ if (settings.vibrate===undefined) settings.vibrate="."; if (settings.repeat===undefined) settings.repeat=4; if (settings.unreadTimeout===undefined) settings.unreadTimeout=60; + settings.maxUnreadTimeout=240; return settings; } function updateSetting(setting, value) { @@ -13,7 +14,6 @@ } var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"]; - var currentVib = settings().vibrate; var mainmenu = { "" : { "title" : /*LANG*/"Messages" }, "< Back" : back, @@ -27,13 +27,13 @@ }, /*LANG*/'Repeat': { value: settings().repeat, - min: 2, max: 10, - format: v => v+"s", + min: 0, max: 10, + format: v => v?v+"s":/*LANG*/"Off", onchange: v => updateSetting("repeat", v) }, /*LANG*/'Unread timer': { value: settings().unreadTimeout, - min: 0, max: 240, step : 10, + min: 0, max: settings().maxUnreadTimeout, step : 10, format: v => v?v+"s":/*LANG*/"Off", onchange: v => updateSetting("unreadTimeout", v) }, diff --git a/apps/messages/widget.js b/apps/messages/widget.js index 1239ef262..4a247b917 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -1,12 +1,12 @@ -WIDGETS["messages"]={area:"tl", width:0, iconwidth:23, +WIDGETS["messages"]={area:"tl", width:0, iconwidth:24, draw:function() { Bangle.removeListener('touch', this.touch); if (!this.width) return; var c = (Date.now()-this.t)/1000; 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); - //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute 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(); diff --git a/apps/neonx/ChangeLog b/apps/neonx/ChangeLog index af7f83942..2e815a449 100644 --- a/apps/neonx/ChangeLog +++ b/apps/neonx/ChangeLog @@ -1 +1,4 @@ 0.01: Initial release +0.02: Optional fullscreen mode +0.03: Optional show lock status via color +0.04: Ensure that widgets are always hidden in fullscreen mode \ No newline at end of file diff --git a/apps/neonx/README.md b/apps/neonx/README.md index d836dfab3..ffb3c3f2c 100644 --- a/apps/neonx/README.md +++ b/apps/neonx/README.md @@ -4,8 +4,8 @@ |---------------------------------|--------------------------------------| |
Neon X
|
Neon IO X
| -This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow. -Can be switched between in the Settings menu, which can be accessed through +This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow. +Can be switched between in the Settings menu, which can be accessed through the app/widget settings menu of the Bangle.js ## Settings @@ -14,7 +14,14 @@ the app/widget settings menu of the Bangle.js Activate the Neon IO X clock look, a bit hard to read until one gets used to it. ### Thickness -The thickness of watch lines, from 1 to 5. +The thickness of watch lines, from 1 to 6. ### Date on touch Shows the current date as DD MM on touch and reverts back to time after 5 seconds or with another touch. + +### Fullscreen +Shows the watchface in fullscreen mode. +Note: In fullscreen mode, widgets are hidden, but still loaded. + +### Show lock status +If enabled, color changes when unlocked to detect the lock state easily. \ No newline at end of file diff --git a/apps/neonx/metadata.json b/apps/neonx/metadata.json index 41b16d11b..840e5b82e 100644 --- a/apps/neonx/metadata.json +++ b/apps/neonx/metadata.json @@ -2,7 +2,7 @@ "id": "neonx", "name": "Neon X & IO X Clock", "shortName": "Neon X Clock", - "version": "0.01", + "version": "0.04", "description": "Pebble Neon X & Neon IO X for Bangle.js", "icon": "neonx.png", "type": "clock", diff --git a/apps/neonx/neonx.app.js b/apps/neonx/neonx.app.js index 967fc8582..4b9231b0e 100644 --- a/apps/neonx/neonx.app.js +++ b/apps/neonx/neonx.app.js @@ -8,6 +8,19 @@ * Created: February 2022 */ +let settings = { + thickness: 4, + io: 0, + showDate: 1, + fullscreen: false, + showLock: false, +}; +let saved_settings = require('Storage').readJSON('neonx.json', 1) || settings; +for (const key in saved_settings) { + settings[key] = saved_settings[key] +} + + const digits = { 0:[[15,15,85,15,85,85,15,85,15,15]], 1:[[85,15,85,85]], @@ -21,6 +34,7 @@ const digits = { 9:[[15,50,15,15,85,15,85,85,15,85]], }; + const colors = { x: [ ["#FF00FF", "#00FFFF"], @@ -31,16 +45,14 @@ const colors = { ["#00FF00", "#00FFFF"] ] }; - const is12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; const screenWidth = g.getWidth(); +const screenHeight = g.getHeight(); const halfWidth = screenWidth / 2; const scale = screenWidth / 240; -const REFRESH_RATE = 10E3; - -let interval = 0; let showingDate = false; + function drawLine(poly, thickness){ for (let i = 0; i < poly.length; i = i + 2){ if (poly[i + 2] === undefined) { @@ -58,34 +70,35 @@ function drawLine(poly, thickness){ } } -let settings = require('Storage').readJSON('neonx.json', 1); - -if (!settings) { - settings = { - thickness: 4, - io: 0, - showDate: 1 - }; -} function drawClock(num){ let tx, ty; + if(settings.fullscreen){ + g.clearRect(0,0,screenWidth,screenHeight); + } else { + g.clearRect(0,24,240,240); + } + for (let x = 0; x <= 1; x++) { for (let y = 0; y <= 1; y++) { const current = ((y + 1) * 2 + x - 1); let newScale = scale; - g.setColor(colors[settings.io ? 'io' : 'x'][y][x]); + let xc = settings.showLock && !Bangle.isLocked() ? Math.abs(x-1) : x; + let c = colors[settings.io ? 'io' : 'x'][y][xc]; + g.setColor(c); if (!settings.io) { - tx = (x * 100 + 18) * newScale; - ty = (y * 100 + 32) * newScale; + newScale *= settings.fullscreen ? 1.20 : 1.0; + let dx = settings.fullscreen ? 0 : 18 + tx = (x * 100 + dx) * newScale; + ty = (y * 100 + dx*2) * newScale; } else { - newScale = 0.33 + current * 0.4; + newScale = 0.33 + current * (settings.fullscreen ? 0.48 : 0.4); - tx = (halfWidth - 139) * newScale + halfWidth; - ty = (halfWidth - 139) * newScale + halfWidth + 12; + tx = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 0); + ty = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 12); } for (let i = 0; i < digits[num[y][x]].length; i++) { @@ -95,59 +108,81 @@ function drawClock(num){ } } + function draw(date){ + queueDraw(); + + // Depending on the settings, we clear all widgets or draw those. + if(settings.fullscreen){ + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + } else { + Bangle.drawWidgets(); + } + + // Now lets draw the time/date let d = new Date(); let l1, l2; showingDate = date; if (date) { - setUpdateInt(0); - l1 = ('0' + (new Date()).getDate()).substr(-2); l2 = ('0' + ((new Date()).getMonth() + 1)).substr(-2); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + setTimeout(_ => { draw(); - setUpdateInt(1); }, 5000); } else { l1 = ('0' + (d.getHours() % (is12hour ? 12 : 24))).substr(-2); l2 = ('0' + d.getMinutes()).substr(-2); } - g.clearRect(0,24,240,240); - drawClock([l1, l2]); } -function setUpdateInt(set){ - if (interval) { - clearInterval(interval); - } - if (set) { - interval = setInterval(draw, REFRESH_RATE); - } +/* + * Draw watch face + */ +var drawTimeout; +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); } -g.clear(1); - -Bangle.setUI("clock"); - -setUpdateInt(1); -draw(); +/* + * Event handlers + */ if (settings.showDate) { Bangle.on('touch', () => draw(!showingDate)); } Bangle.on('lcdPower', function(on){ - if (on){ + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + if (on) { draw(); - setUpdateInt(1); - } else setUpdateInt(0); + } }); +Bangle.on('lock', function(isLocked) { + draw(); +}); + + +/* + * Draw first time + */ +g.clear(1); +Bangle.setUI("clock"); Bangle.loadWidgets(); -Bangle.drawWidgets(); + +draw(); \ No newline at end of file diff --git a/apps/neonx/neonx.settings.js b/apps/neonx/neonx.settings.js index 0e205e03b..e01ceb4d3 100644 --- a/apps/neonx/neonx.settings.js +++ b/apps/neonx/neonx.settings.js @@ -7,7 +7,9 @@ neonXSettings = { thickness: 4, io: 0, - showDate: 1 + showDate: 1, + fullscreen: false, + showLock: false, }; updateSettings(); @@ -17,7 +19,7 @@ if (!neonXSettings) resetSettings(); - let thicknesses = [1, 2, 3, 4, 5]; + let thicknesses = [1, 2, 3, 4, 5, 6]; const menu = { "" : { "title":"Neon X & IO"}, @@ -48,7 +50,23 @@ neonXSettings.showDate = v; updateSettings(); } - } + }, + 'Fullscreen': { + value: false | neonXSettings.fullscreen, + format: () => (neonXSettings.fullscreen ? 'Yes' : 'No'), + onchange: () => { + neonXSettings.fullscreen = !neonXSettings.fullscreen; + updateSettings(); + }, + }, + 'Show lock': { + value: false | neonXSettings.showLock, + format: () => (neonXSettings.showLock ? 'Yes' : 'No'), + onchange: () => { + neonXSettings.showLock = !neonXSettings.showLock; + updateSettings(); + }, + }, }; E.showMenu(menu); }) diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index d133697b3..a77fa758f 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -16,3 +16,4 @@ 0.14: incorporated lazybones idle timer, configuration settings to come 0.15: fixed tendancy for mylocation to default to London added setting to enable/disable idle timer warning +0.16: make check_idle boolean setting work properly with new B2 menu diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index da3c18eae..f04a7ae54 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -2,7 +2,7 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.15", + "version": "0.16", "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", "icon": "pastel.png", "dependencies": {"mylocation":"app","weather":"app"}, diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index 26dafd271..afe461f15 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -38,38 +38,28 @@ }, }, 'Show Grid': { - value: s.grid, - format: () => (s.grid ? 'Yes' : 'No'), - onchange: () => { - s.grid = !s.grid; + value: !!s.grid, + format: v => v ? /*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + s.grid = v; save(); }, }, 'Show Weather': { - value: s.weather, - format: () => (s.weather ? 'Yes' : 'No'), - onchange: () => { - s.weather = !s.weather; + value: !!s.weather, + format: v => v ? /*LANG*/"Yes":/*LANG*/"No", + onchange: v => { + s.weather = v; save(); }, }, - // for use when the new menu system goes live - /* 'Idle Warning': { - value: s.idle_check, - onchange : v => { + value: !!s.idle_check, + format: v => v ? /*LANG*/"Yes":/*LANG*/"No", + onchange: v => { s.idle_check = v; save(); }, - }, - */ - 'Idle Warning': { - value: s.idle_check, - format: () => (s.idle_check ? 'Yes' : 'No'), - onchange: () => { - s.idle_check = !s.idle_check; - save(); - }, } }) }) diff --git a/apps/pie/app-icon.js b/apps/pie/app-icon.js new file mode 100644 index 000000000..34a58225c --- /dev/null +++ b/apps/pie/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/ABl3ABQVJg4WLC/QWMC/4X/U7NVqIXTgtVC4MXC6QWBAAQYLCxQAGqByJIoQAKC8IWMJBIuNGBIXvCxxgIC/4XH1QAO0AX/C/4X/C/4X/C6Q")) diff --git a/apps/pie/app.js b/apps/pie/app.js new file mode 100644 index 000000000..69b67d3bd --- /dev/null +++ b/apps/pie/app.js @@ -0,0 +1,56 @@ +function end(){ + clearInterval(m); + clearWatch(w); + gfx.clear(); + gfx.setColor(1,0,0); + gfx.setFont("Vector30"); + gfx.drawString('Game over!\n Score: '+score+'\nPress BTN1', gfx.getWidth()*0.15,gfx.getHeight()*0.4); + setWatch(function(){init();}, BTN1); +} +function scrollX(){ + gfx.clearRect(0,gfx.getHeight()*(1/4),gfx.getWidth(),0); + gfx.scroll(0,gfx.getHeight()/4); + score++; + if(typeof(m) != undefined && score>0){ + clearInterval(m); + m = setInterval(scrollY,Math.abs(100/score+15-0.1*score));} + gfx.setColor(1,1,1); + gfx.drawString(score,gfx.getWidth()*(4.2/5),gfx.getHeight()*(0.5/5)); + gfx.setColor(Math.random(),Math.random(),Math.random()); + gfx.setColor(col[0],col[1],col[2]); + gfx.fillRect(colm[0],colm[1],colm[2],colm[3]); + col = [Math.random(),Math.random(),Math.random()]; + gfx.setColor(col[0],col[1],col[2]); + block[0] = gfx.getWidth(); +} +function scrollY(){ + block[0] -= 2; + block[2] = block[0]+colm[2]-colm[0]; + gfx.clearRect(block[2], block[1], gfx.getWidth(), block[3]); + gfx.fillRect(block[0],block[1],block[2],block[3]); + if(block[2]block[2] && colm[0]layout[e.id].label = e.getString()); - sb.on('changed', e=>layout[e.id].label = e.getString()); + if (sa) sa.on('changed', e=>layout[e.id].label = e.getString()); + if (sb) sb.on('changed', e=>layout[e.id].label = e.getString()); } // At the bottom put time/GPS state/etc lc.push({ type:"h", filly:1, c:[ diff --git a/apps/run/metadata.json b/apps/run/metadata.json index ea68f4734..7aabf8b53 100644 --- a/apps/run/metadata.json +++ b/apps/run/metadata.json @@ -1,6 +1,6 @@ { "id": "run", "name": "Run", - "version":"0.06", + "version":"0.07", "description": "Displays distance, time, steps, cadence, pace and more for runners.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps", diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog index 9db0e26c5..104fce19d 100644 --- a/apps/stopwatch/ChangeLog +++ b/apps/stopwatch/ChangeLog @@ -1 +1,2 @@ 0.01: first release +0.02: Adjust for touch events outside of screen g dimensions diff --git a/apps/stopwatch/metadata.json b/apps/stopwatch/metadata.json index e72d85af1..cc13ec92f 100644 --- a/apps/stopwatch/metadata.json +++ b/apps/stopwatch/metadata.json @@ -1,7 +1,7 @@ { "id": "stopwatch", "name": "Stopwatch Touch", - "version": "0.01", + "version": "0.02", "description": "A touch based stop watch for Bangle JS 2", "icon": "stopwatch.png", "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], diff --git a/apps/stopwatch/stopwatch.app.js b/apps/stopwatch/stopwatch.app.js index 48d4f26ea..e2be95451 100644 --- a/apps/stopwatch/stopwatch.app.js +++ b/apps/stopwatch/stopwatch.app.js @@ -185,17 +185,27 @@ resetBtn.setImage(pause_img); 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; + // not running, and reset - if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return; // paused and hit play - if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return; // paused and press reset - if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return; // must be running - if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; + if (running && bigPlayPauseBtn.check(x, y)) return; }); // Stop updates when LCD is off, restart when on diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/terminalclock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/terminalclock/README.md b/apps/terminalclock/README.md new file mode 100644 index 000000000..2a2bc1204 --- /dev/null +++ b/apps/terminalclock/README.md @@ -0,0 +1,9 @@ +# Terminal clock + +A clock displayed as a terminal cli. +It can display : +- time +- date +- hrm +- activity +- steps diff --git a/apps/terminalclock/app-icon.js b/apps/terminalclock/app-icon.js new file mode 100644 index 000000000..5e57a4b7c --- /dev/null +++ b/apps/terminalclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AHsHu93uAX/C+wACsAaTC/4Xiu1mswXRCYNnDQQxTgwX/C8QABuAaTg4X/C7Z3Xa/4Xds1ms4XUCgV2DYIXUsBdTC/4AEg4VCC61wIiYX/C74A/AGIA==")) diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js new file mode 100644 index 000000000..fb7bd16cc --- /dev/null +++ b/apps/terminalclock/app.js @@ -0,0 +1,138 @@ +var locale = require("locale"); +var fontColor = g.theme.dark ? "#0f0" : "#000"; +var startY = 24; +var paddingY = 2; +var font6x8At4Size = 32; +var font6x8At2Size = 18; +var heartRate = 0; + + +function setFontSize(pos){ + if(pos == 1) + g.setFont("6x8", 4); + else + g.setFont("6x8", 2); +} + +function clearField(pos){ + var yStartPos = startY + + paddingY * (pos - 1) + + font6x8At4Size * Math.min(1, pos-1) + + font6x8At2Size * Math.max(0, pos-2); + var yEndPos = startY + + paddingY * (pos - 1) + + font6x8At4Size * Math.min(1, pos) + + font6x8At2Size * Math.max(0, pos-1); + g.clearRect(0, yStartPos, 240, yEndPos); +} + +function clearWatchIfNeeded(now){ + if(now.getMinutes() % 10 == 0) + g.clearRect(0, startY, 240, 240); +} + +function drawLine(line, pos){ + setFontSize(pos); + var yPos = startY + + paddingY * (pos - 1) + + font6x8At4Size * Math.min(1, pos-1) + + font6x8At2Size * Math.max(0, pos-2); + g.drawString(line, 5, yPos, true); +} + +function drawTime(now, pos){ + var h = now.getHours(); + var m = now.getMinutes(); + var time = ">" + (""+h).substr(-2) + ":" + ("0"+m).substr(-2); + drawLine(time, pos); +} + +function drawDate(now, pos){ + var dow = locale.dow(now, 1); + var date = locale.date(now, 1).substr(0,6) + locale.date(now, 1).substr(-2); + var locale_date = ">" + dow + " " + date; + drawLine(locale_date, pos); +} + +function drawInput(now, pos){ + clearField(pos); + drawLine(">", pos); +} + +function drawStepCount(pos){ + var health = Bangle.getHealthStatus("day"); + var steps_formated = ">Steps: " + health.steps; + drawLine(steps_formated, pos); +} + +function drawHRM(pos){ + clearField(pos); + if(heartRate != 0) + drawLine(">HR: " + parseInt(heartRate), pos); + else + drawLine(">HR: unknown", pos); +} + +function drawActivity(pos){ + clearField(pos); + var health = Bangle.getHealthStatus('last'); + var steps_formated = ">Activity: " + parseInt(health.movement/10); + drawLine(steps_formated, pos); +} + + +function draw(){ + var curPos = 1; + g.reset(); + g.setFontAlign(-1, -1); + g.setColor(fontColor); + var now = new Date(); + clearWatchIfNeeded(now); // mostly to not have issues when changing days + drawTime(now, curPos); + curPos++; + if(settings.showDate){ + drawDate(now, curPos); + curPos++; + } + if(settings.showHRM){ + drawHRM(curPos); + curPos++; + } + if(settings.showActivity){ + drawActivity(curPos); + curPos++; + } + if(settings.showStepCount){ + drawStepCount(curPos); + curPos++; + } + drawInput(now, curPos); +} + + +Bangle.on('HRM',function(hrmInfo) { + if(hrmInfo.confidence >= settings.HRMinConfidence) + heartRate = hrmInfo.bpm; +}); + + +// Clear the screen once, at startup +g.clear(); +// load the settings +var settings = Object.assign({ + // default values + HRMinConfidence: 50, + showDate: true, + showHRM: true, + 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(); + +var secondInterval = setInterval(draw, 10000); diff --git a/apps/terminalclock/app.png b/apps/terminalclock/app.png new file mode 100644 index 000000000..5d4507ead Binary files /dev/null and b/apps/terminalclock/app.png differ diff --git a/apps/terminalclock/metadata.json b/apps/terminalclock/metadata.json new file mode 100644 index 000000000..6907da84d --- /dev/null +++ b/apps/terminalclock/metadata.json @@ -0,0 +1,24 @@ +{ + "id": "terminalclock", + "name": "Terminal Clock", + "shortName":"Terminal Clock", + "description": "A terminal cli like clock displaying multiple sensor data", + "version":"0.01", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name": "terminalclock.app.js","url": "app.js"}, + {"name": "terminalclock.settings.js","url": "settings.js"}, + {"name": "terminalclock.img","url": "app-icon.js","evaluate": true} + ], + "data": [ + {"name": "terminalclock.json"} + ], + "screenshots": [ + {"url": "screenshot1.png"}, + {"url": "screenshot2.png"} + ] +} diff --git a/apps/terminalclock/screenshot1.png b/apps/terminalclock/screenshot1.png new file mode 100644 index 000000000..a0d41f495 Binary files /dev/null and b/apps/terminalclock/screenshot1.png differ diff --git a/apps/terminalclock/screenshot2.png b/apps/terminalclock/screenshot2.png new file mode 100644 index 000000000..21d7aadbf Binary files /dev/null and b/apps/terminalclock/screenshot2.png differ diff --git a/apps/terminalclock/settings.js b/apps/terminalclock/settings.js new file mode 100644 index 000000000..77df69b12 --- /dev/null +++ b/apps/terminalclock/settings.js @@ -0,0 +1,61 @@ +(function(back) { + var FILE = "terminalclock.json"; + // Load settings + var settings = Object.assign({ + HRMinConfidence: 50, + showDate: true, + showHRM: true, + showActivity: true, + showStepCount: true, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Terminal Clock" }, + "< Back" : () => back(), + 'HR confidence': { + value: 50|settings.HRMinConfidence, // 0| converts undefined to 0 + min: 0, max: 100, + onchange: v => { + settings.HRMinConfidence = v; + writeSettings(); + } + }, + 'Show date': { + value: !!settings.showDate, + format: v => v?"Yes":"No", + onchange: v => { + settings.showDate = v; + writeSettings(); + } + }, + 'Show HRM': { + value: !!settings.showHRM, + format: v => v?"Yes":"No", + onchange: v => { + settings.showHRM = v; + writeSettings(); + } + }, + 'Show Activity': { + value: !!settings.showActivity, + format: v => v?"Yes":"No", + onchange: v => { + settings.showActivity = v; + writeSettings(); + } + }, + 'Show Steps': { + value: !!settings.showStepCount, + format: v => v?"Yes":"No", + onchange: v => { + settings.showStepCount = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/timecal/ChangeLog b/apps/timecal/ChangeLog new file mode 100644 index 000000000..e48145b4b --- /dev/null +++ b/apps/timecal/ChangeLog @@ -0,0 +1,10 @@ +0.01: Initial creation of the clock face time and calendar +0.02: Feature Request #1154 and some findings... + -> get rendered time from optimisations + -> *BATT SAFE* only update once a minute instead of once a second + -> *RAM optimized* clean code, corrected minute update (timout, no intervall) + -> locale: weekday name (first two characters) from locale + -> added settings to render cal view begin day (-1: today, 0:sunday, 1:monday [default]) +0.03: a lot of more settings for outline, colors and highlights +0.04: finalized README, fixed settings cancel, fixed border-setting +0.05: bugfix: default settings \ No newline at end of file diff --git a/apps/timecal/README.md b/apps/timecal/README.md new file mode 100644 index 000000000..d26f9ba4d --- /dev/null +++ b/apps/timecal/README.md @@ -0,0 +1,22 @@ +# Calendar Clock + +## Features +Shows the +* Date +* Time (hh:mm) - respecting 12/24 (uses locale string) +* 3 weeks calendar view (last,current and next week) + +### The settings menu +Calendar View can be customized +* < Save: Exist and save the current settings +* Show date: Choose if and how the date is displayed: none, locale (default), monthfull or monthshort.yearshort #weeknum with 0 prefixed +* Start wday: Set day of week start. Values: 0=Sunday, 1=Monday,...,6=Saturday or -1=Relative to today (default 0: Sunday) +* Su color: Set Sundays color. Values: none (default), red, green or blue +* Border: show or none (default) +* Submenu Today settings - choose how today is highlighted + * < Back: + * Color: none, red (default), green or blue + * Marker: Outline today graphically. Values: none (default), circle, rect(angle) + * Mrk.Color: Circle/rectangle color: red (default), green or blue + * Mrk.Size: Circle/rectangle thickness in pixel: min:1, max: 10, default:3 +* < Cancel: Exit and no change. Nevertheless missing default settings and superflous settings will be removed and saved. diff --git a/apps/timecal/metadata.json b/apps/timecal/metadata.json index 3237dd08a..f439f4e9c 100644 --- a/apps/timecal/metadata.json +++ b/apps/timecal/metadata.json @@ -1,13 +1,16 @@ { "id": "timecal", "name": "TimeCal", "shortName":"TimeCal", + "version":"0.05", + "description": "TimeCal shows the date/time along with a 3 week calendar", "icon": "icon.png", - "version":"0.01", - "description": "TimeCal shows the Time along with a 3 week calendar", - "tags": "clock", "type": "clock", + "tags": "clock,calendar", "supports":["BANGLEJS2"], + "readme": "README.md", + "allow_emulator":true, "storage": [ - {"name":"timecal.app.js","url":"timecal.app.js"} + {"name":"timecal.app.js","url":"timecal.app.js"}, + {"name":"timecal.settings.js","url":"timecal.settings.js"} ] } diff --git a/apps/timecal/testing/timecal.app.test._js b/apps/timecal/testing/timecal.app.test._js new file mode 100644 index 000000000..e41f3d848 --- /dev/null +++ b/apps/timecal/testing/timecal.app.test._js @@ -0,0 +1,798 @@ +//Clock renders date, time and pre,current,next week calender view +class TimeCalClock{ + DATE_FONT_SIZE(){ return 20; } + TIME_FONT_SIZE(){ return 40; } + + /** + * @param{Date} date optional the date (e.g. for testing) + * @param{Settings} settings optional settings to use e.g. for testing + */ + constructor(date, settings){ + if (date) + this.date=date; + + if (settings) + this._settings = settings; + else + this._settings = require("Storage").readJSON("timecal.settings.json", 1) || {}; + + const defaults = { + shwDate:1, //0:none, 1:locale, 2:month, 3:monthshort.year #week + + wdStrt:0, //identical to getDay() 0->Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings + for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults + + g.clear(); + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + this.centerX = Bangle.appRect.w/2; + this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b + + this.ABR_DAY=[]; + if (require("locale") && require("locale").dow) + for (let d=0; d<=6; d++) { + var refDay=new Date(); + refDay.setFullYear(1972); + refDay.setMonth(0); + refDay.setDate(2+d); + this.ABR_DAY.push(require("locale").dow(refDay)); + + } + else + this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + /** + * @returns {Object} current settings object + */ + settings(){ + return this._settings; + } + + + /* + * Run forest run + **/ + draw(){ + this.drawTime(); + + if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset()) + this.drawDateAndCal(); + } + + /** + * draw given or current time from date + * overwatch timezone changes + * schedules itself to update + */ + drawTime(){ + d=this.date ? this.date : new Date(); + const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10; + + d=d?d :new Date(); + + g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg) + .clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7) + .drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true); + //.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option + + setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds()); + } + + /** + * draws given date and cal + * @param{Date} d provide date or uses today + */ + drawDateAndCal(){ + d=this.date ? this.date : new Date(); + + this.TZOffset=d.getTimezoneOffset(); + this.drawDate(); + this.drawCal(); + + if (this.tOutD) //abort exisiting + clearTimeout(this.tOutD); + this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds()); + } + + /** + * draws given date as defiend in settings + */ + drawDate(){ + d=this.date ? this.date : new Date(); + + const FONT_SIZE=20; + const Y=Bangle.appRect.y; + var render=false; + var dateStr = ""; + if (this.settings().shwDate>0) { //skip if exactly -none + const dateSttngs = ["","l","M","m.Y #W"]; + for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured + switch (c){ + case "l":{ //locale + render=true; + dateStr+=require("locale").date(d,1); + break; + } + case "m":{ //month e.g. Jan. + render=true; + dateStr+=require("locale").month(d,1); + break; + } + case "M":{ //month e.g. January + render=true; + dateStr+=require("locale").month(d,0); + break; + } + case "y":{ //year e.g. 22 + render=true; + dateStr+=d.getFullYear().slice(-2); + break; + } + case "Y":{ //year e.g. 2022 + render=true; + dateStr+=d.getFullYear(); + break; + } + case "w":{ //week e.g. #2 + dateStr+=(this.ISO8601calWeek(d)); + break; + } + case "W":{ //week e.g. #02 + dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2); + break; + } + default: //append c + dateStr+=c; + render=dateStr.length>0; + break; //noop + } + } + } + if (render){ + g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y); + } + //g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option + } + + /** + * draws calender week view (-1,0,1) for given date + */ + drawCal(){ + d=this.date ? this.date : new Date(); + + const DAY_NAME_FONT_SIZE=10; + const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3; + const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only + const CELL_W=Bangle.appRect.w/7; //cell width + const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth + const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15 + + g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H); + + //draw grid & Headline + const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2 + for(var dNo=0; dNo=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7; + const dName=dNames[dIdx]; + if(dNo>0) + g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1); + + if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt + g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg); + } + + var nextY=CAL_Y+DAY_NAME_FONT_SIZE; + + for(i=0; i<3; i++){ + const y=nextY+i*CELL_H; + g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); + } + + g.setFont("Vector", DAY_NUM_FONT_SIZE); + + //write days + const tdyDate=d.getDate(); + const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days + var rD=new Date(d.getTime()); + rD.setDate(rD.getDate()-days); + var rDate=rD.getDate(); + for(var y=0; y<3; y++){ + for(var x=0; x", + cases: [ + { + value: "required,", + beforeTxt: "optional,", + beforeExpression: "optional,", + afterText: "optional,", + afterExpression: "optional," + } + ], + constructorParams: ["optional: ","|TEST_SETTINGS|","..."], //TEST_SETTINGS will be replcaed with each current {setting: case} + functionNames: ["required, ", "..."], + functionParams: ["optional: ","|TEST_SETTINGS|","..."] + }; + } + + constructor(data){ + + this._validate(data); + + this.setting = data.setting; + this.cases = data.cases.map((entry) => { + return { + value: entry.value, + beforeTxt: entry.beforeTxt||"", + beforeExpression: entry.beforeExpression||true, + afterTxt: entry.afterTxt||"", + afterExpression: entry.afterExpression||true + }; + }); + this.constructorParams = data.constructorParams; + this.functionNames = data.functionNames; + this.functionParams = data.functionParams; + } + + /** + * validates the given data config + */ + _validate(data){ + //validate given config + if (!data.setting) throw new EmptyMandatoryError("setting", data, this.TEST_SETTING_SAMPLE()); + if (!(data.cases instanceof Array) || data.cases.length==0) throw new EmptyMandatoryError("cases", data, this.TEST_SETTING_SAMPLE()); + if (!(data.functionNames instanceof Array) || data.functionNames==0) throw new EmptyMandatoryError("functionNames", data, this.TEST_SETTING_SAMPLE()); + + data.cases.forEach((entry,idx) => { + if (entry.value === undefined) throw new EmptyMandatoryError("cases["+idx+"].value", entry, this.TEST_SETTING_SAMPLE()); + }); + } +} + +/*************************************************************************/ + +/** + * Testing a Bangle object + */ +class BangleTestRunner{ + /** + * create for ObjClass + * @param {Class} objClass + * @param {LogSeverity} minSeverity to Log + */ + constructor(objClass, minSeverity){ + this.TESTCASE_MSG_BEFORE_TIMEOUT = 1000; //5s + this.TESTCASE_RUN_TIMEOUT = 1000; //5s + this.TESTCASE_MSG_AFTER_TIMEOUT = 1000; //5s + + this.oClass = objClass; + this.minSvrty = minSeverity; + this.tests = []; + + this.currentCaseNum = this.currentTestNum = this.currentTest = this.currentCase = undefined; + } + + /** + * add a Setting Test, return instance for chaining + * @param {TestSetting} + */ + addTestSettings(sttngs) { + this.tests.push(new TestSetting(sttngs)); + return this; + } + + /** + * Test execution of all tests + */ + execute() { + this._init(); + while (this._nextTest()) { + this._beforeTest(); + while (this._nextCase()) { + this._beforeCase(); + this._runCase(); + this._afterCase(); + } + this._afterTest(); + this._firstCase(); + } + this._exit(); + } + + /** + * global prepare - before all test + */ + _init() { + console.log(this._nowTime(), ">>init"); + this.currentTestNum=-1; + this.currentCaseNum=-1; + } + + /** + * before each test + */ + _beforeTest() { + console.log(this._nowTime(), ">>test #" + this.currentTestNum); + } + + /** + * befor each testcase + */ + _beforeCase() { + console.log(this.currentTest); + console.log(this._nowTime(), ">>case #" + this.currentTestNum + "." + this.currentCaseNum + "/" + (this.currentTest.cases.length-1)); + if (this.currentTest instanceof TestSetting) + console.log(this.currentTest.setting+"="+this.currentCase.value+"/n"+(this.currentCase.beforeTxt ? "#"+this.currentCase.beforeTxt : "")); + } + + /** + * testcase runner + */ + _runCase() { + console.log(this._nowTime(), ">>running..."); + var returns = []; + this.currentTest.functionNames.forEach((fName) => { + var settings={}; settings[this.currentTest.setting] = this.currentCase.value; + var cParams = this.currentTest.constructorParams||[]; + cParams = cParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params + var fParams = this.currentTest.functionParams||[]; + fParams = fParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params + + var creatorFunc = new Function("console.log('Constructor params:', arguments); return new " + this.oClass + "(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9])"); //prepare spwan arguments[0],arguments[1] + let instance = creatorFunc.call(this.oClass, cParams[0], cParams[1], cParams[2], cParams[3], cParams[4], cParams[5], cParams[6], cParams[7], cParams[8], cParams[9]); //spwan + + console.log(">>"+this.oClass+"["+fName+"]()"); + + console.log('Instance:', instance); + console.log('Function params:', fParams); + returns.push(instance[fName](fParams[0], fParams[1], fParams[2], fParams[3], fParams[4], fParams[5], fParams[6], fParams[7], fParams[8], fParams[9])); //run method and store result + g.dump(); + console.log("<<"+this.oClass+"["+fName+"]()"); + }); + console.log(this._nowTime(), "<<...running"); + } + + /** + * after each testcase + */ + _afterCase() { + if (this.currentTest instanceof TestSetting) + if (this.currentCase.afterTxt.length>0) + console.log("++EXPECTED:" + this.currentCase.afterTxt + "EXPECTED++"); + console.log(this._nowTime(), "< setTimeout(resolve, sec)); + } + + _waits(sec) { + this._delay(1).then(); + } + + _log() { + + } + + _nextTest() { + if (this.currentTestNum>=-1 && (this.currentTestNum+1)=-1 && (this.currentCaseNum+1)Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings + for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults + + g.clear(); + Bangle.setUI("clock"); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + this.centerX = Bangle.appRect.w/2; + this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b + + this.ABR_DAY=[]; + if (require("locale") && require("locale").dow) + for (let d=0; d<=6; d++) { + var refDay=new Date(); + refDay.setFullYear(1972); + refDay.setMonth(0); + refDay.setDate(2+d); + this.ABR_DAY.push(require("locale").dow(refDay)); -function drawCal(d){ - var calStart = 101; - var cellSize = g.getWidth() / 7; - var halfSize = cellSize / 2; - g.clearRect(0,calStart,g.getWidth(),g.getHeight()); - g.drawLine(0,calStart,g.getWidth(),calStart); - var days = ["Mo","Tu","We","Th","Fr","Sa","Su"]; - g.setFont("Vector",10); - g.setColor(fontColor); - g.setFontAlign(-1,-1,0); - for(var i = 0; i < days.length;i++){ - g.drawString(days[i],i*cellSize+5,calStart -11); - if(i!=0){ - g.drawLine(i*cellSize,calStart,i*cellSize,g.getHeight()); - } - } - var cellHeight = (g.getHeight() -calStart ) / 3; - for(var i = 0;i < 3;i++){ - var starty = calStart + i * cellHeight; - g.drawLine(0,starty,g.getWidth(),starty); - } - - g.setFont("Vector",15); - - var dayOfWeek = d.getDay(); - var dayRem = d.getDay() - 1; - if(dayRem <0){ - dayRem = 0; - } - - var start = new Date(); - start.setDate(start.getDate()-(7+dayRem)); - g.setFontAlign(0,-1,0); - for (var y = 0;y < 3; y++){ - for(var x = 0;x < 7; x++){ - if(start.getDate() === d.getDate()){ - g.setColor(accentColor); - }else{ - g.setColor(fontColor); } - g.drawString(start.getDate(),x*cellSize +(cellSize / 2) + 2,calStart+(cellHeight*y) + 5); - start.setDate(start.getDate()+1); + else + this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + /** + * @returns {Object} current settings object + */ + settings(){ + return this._settings; + } + + + /* + * Run forest run + **/ + draw(){ + this.drawTime(); + + if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset()) + this.drawDateAndCal(); } + + /** + * draw given or current time from date + * overwatch timezone changes + * schedules itself to update + */ + drawTime(){ + d=this.date ? this.date : new Date(); + const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10; + + d=d?d :new Date(); + + g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg) + .clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7) + .drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true); + //.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option + + setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds()); + } + + /** + * draws given date and cal + * @param{Date} d provide date or uses today + */ + drawDateAndCal(){ + d=this.date ? this.date : new Date(); + + this.TZOffset=d.getTimezoneOffset(); + this.drawDate(); + this.drawCal(); + + if (this.tOutD) //abort exisiting + clearTimeout(this.tOutD); + this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds()); + } + + /** + * draws given date as defiend in settings + */ + drawDate(){ + d=this.date ? this.date : new Date(); + + const FONT_SIZE=20; + const Y=Bangle.appRect.y; + var render=false; + var dateStr = ""; + if (this.settings().shwDate>0) { //skip if exactly -none + const dateSttngs = ["","l","M","m.Y #W"]; + for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured + switch (c){ + case "l":{ //locale + render=true; + dateStr+=require("locale").date(d,1); + break; + } + case "m":{ //month e.g. Jan. + render=true; + dateStr+=require("locale").month(d,1); + break; + } + case "M":{ //month e.g. January + render=true; + dateStr+=require("locale").month(d,0); + break; + } + case "y":{ //year e.g. 22 + render=true; + dateStr+=d.getFullYear().slice(-2); + break; + } + case "Y":{ //year e.g. 2022 + render=true; + dateStr+=d.getFullYear(); + break; + } + case "w":{ //week e.g. #2 + dateStr+=(this.ISO8601calWeek(d)); + break; + } + case "W":{ //week e.g. #02 + dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2); + break; + } + default: //append c + dateStr+=c; + render=dateStr.length>0; + break; //noop + } + } + } + if (render){ + g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y); + } + //g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option + } + + /** + * draws calender week view (-1,0,1) for given date + */ + drawCal(){ + d=this.date ? this.date : new Date(); + + const DAY_NAME_FONT_SIZE=10; + const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3; + const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only + const CELL_W=Bangle.appRect.w/7; //cell width + const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth + const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15 + + g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H); + + //draw grid & Headline + const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2 + for(var dNo=0; dNo=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7; + const dName=dNames[dIdx]; + if(dNo>0) + g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1); + + if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt + g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg); + } + + var nextY=CAL_Y+DAY_NAME_FONT_SIZE; + + for(i=0; i<3; i++){ + const y=nextY+i*CELL_H; + g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); + } + + g.setFont("Vector", DAY_NUM_FONT_SIZE); + + //write days + const tdyDate=d.getDate(); + const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days + var rD=new Date(d.getTime()); + rD.setDate(rD.getDate()-days); + var rDate=rD.getDate(); + for(var y=0; y<3; y++){ + for(var x=0; xSu, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today + + tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled + tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E + tdyMrkPxl:3, //px + + suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E + //phColor:"#E00", //public holiday + + calBrdr:false + }; + validSttngs = require("Storage").readJSON(FILE, 1) || {}; + for (const k in validSttngs) if (!DEFAULTS.hasOwnProperty(k)) delete this.validSttngs[k]; //remove invalid settings + for (const k in DEFAULTS) if(!validSttngs.hasOwnProperty(k)) validSttngs[k] = DEFAULTS[k]; //assign missing defaults fixed + + var chngdSttngs = Object.assign({}, validSttngs); + + var saveExitSettings = () => { + require('Storage').writeJSON(FILE, chngdSttngs); + exit(); + }; + + var cancelExitSettings = () => { + require('Storage').writeJSON(FILE, validSttngs); + exit(); + }; + + var showMainMenu = () => { + E.showMenu({ + "": { + "title": "TimeCal "+ /*LANG*/"settings" + }, + /*LANG*/"< Save": () => saveExitSettings(), + /*LANG*/"Show date": { + value: chngdSttngs.shwDate, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"locale", /*LANG*/"M", /*LANG*/"m.Y #W"][v], + onchange: v => chngdSttngs.shwDate = v + }, + /*LANG*/"Start wday": { + value: chngdSttngs.wdStrt, + min: -1, max: 6, + format: v => v>=0 ? ABR_DAY[v] : /*LANG*/"today", + onchange: v => chngdSttngs.wdStrt = v + }, + /*LANG*/"Su color": { + value: chngdSttngs.suClr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => chngdSttngs.suClr = v + }, + /*LANG*/"Border": { + value: chngdSttngs.calBrdr, + format: v => v ? /*LANG*/"show" : /*LANG*/"none", + onchange: v => chngdSttngs.calBrdr = v + }, + /*LANG*/"Today settings": () => { + showTodayMenu(); + }, + /*LANG*/"< Cancel": () => cancelExitSettings() + }); + }; + + var showTodayMenu = () => { + E.showMenu({ + "": { + "title": /*LANG*/"Today settings" + }, + "< Back": () => showMainMenu(), + /*LANG*/"Color": { + value: chngdSttngs.tdyNumClr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => chngdSttngs.tdyNumClr = v + }, + /*LANG*/"Marker": { + value: chngdSttngs.tdyMrkr, + min: 0, max: 3, + format: v => [/*LANG*/"none", /*LANG*/"circle", /*LANG*/"rectangle", /*LANG*/"filled"][v], + onchange: v => chngdSttngs.tdyMrkr = v + }, + /*LANG*/"Mrk.Color": { + value: chngdSttngs.tdyMrkClr, + min: 0, max: 2, + format: v => [/*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v], + onchange: v => chngdSttngs.tdyMrkClr = v + }, + /*LANG*/"Mrk.Size": { + value: chngdSttngs.tdyMrkPxl, + min: 1, max: 10, + format: v => v+"px", + onchange: v => chngdSttngs.tdyMrkPxl = v + }, + /*LANG*/"< Cancel": () => cancelExitSettings() + }); + }; + + showMainMenu(); +}); diff --git a/apps/widbars/ChangeLog b/apps/widbars/ChangeLog index 4c21f3ace..61e28e6e4 100644 --- a/apps/widbars/ChangeLog +++ b/apps/widbars/ChangeLog @@ -1 +1,3 @@ 0.01: New Widget! +0.02: Battery bar turns yellow on charge + Memory status bar does not trigger garbage collect diff --git a/apps/widbars/metadata.json b/apps/widbars/metadata.json index e8d52c90a..a9981305c 100644 --- a/apps/widbars/metadata.json +++ b/apps/widbars/metadata.json @@ -1,7 +1,7 @@ { "id": "widbars", "name": "Bars Widget", - "version": "0.01", + "version": "0.02", "description": "Display several measurements as vertical bars.", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/widbars/widget.js b/apps/widbars/widget.js index a1134f31f..cceeb0897 100644 --- a/apps/widbars/widget.js +++ b/apps/widbars/widget.js @@ -42,19 +42,25 @@ if (top) g .clearRect(x,y, x+w-1,y+top-1); // erase above bar if (f) g.setColor(col).fillRect(x,y+top, x+w-1,y+h-1); // even for f=0.001 this is still 1 pixel high } + let batColor='#0f0'; function draw() { g.reset(); const x = this.x, y = this.y, - m = process.memory(); + m = process.memory(false); let b=0; // ==HRM== bar(x+(w*b++),y,'#f00'/*red */,bpm/200); // >200 seems very unhealthy; if we have no valid bpm this will just be empty space // ==Temperature== bar(x+(w*b++),y,'#ff0'/*yellow */,E.getTemperature()/50); // you really don't want to wear a watch that's hotter than 50°C bar(x+(w*b++),y,g.theme.dark?'#0ff':'#00f'/*cyan/blue*/,1-(require('Storage').getFree() / process.env.STORAGE)); bar(x+(w*b++),y,'#f0f'/*magenta*/,m.usage/m.total); - bar(x+(w*b++),y,'#0f0'/*green */,E.getBattery()/100); + bar(x+(w*b++),y,batColor,E.getBattery()/100); } let redraw; + Bangle.on('charging', function(charging) { + batColor=charging?'#ff0':'#0f0'; + WIDGETS["bars"].draw(); + }); + Bangle.on('lcdPower', on => { if (redraw) clearInterval(redraw) redraw = undefined; diff --git a/core b/core index bd894bfdc..a7a80a13f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bd894bfdcee8293c97763de2b4d105a6b6e5415e +Subproject commit a7a80a13fa187a4ff5f89669992babca2d95812c diff --git a/loader.js b/loader.js index 6b27736ae..82d6172cb 100644 --- a/loader.js +++ b/loader.js @@ -202,7 +202,6 @@ window.addEventListener('load', (event) => { } var selectLang = document.getElementById("settings-lang"); - console.log(languages); languages.forEach(lang => { selectLang.innerHTML += ``; }); diff --git a/modules/Layout.js b/modules/Layout.js index 134cc8103..1b0bbd47f 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -232,7 +232,7 @@ Layout.prototype.render = function (l) { function render(l) {"ram" g.reset(); - if (l.col) g.setColor(l.col); + if (l.col!==undefined) g.setColor(l.col); if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); cb[l.type](l); } @@ -264,7 +264,7 @@ Layout.prototype.render = function (l) { x,y+4 ], bg = l.selected?g.theme.bgH:g.theme.bg2; g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); - if (l.col) g.setColor(l.col); + if (l.col!==undefined) g.setColor(l.col); if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ diff --git a/modules/exstats.js b/modules/exstats.js index 8890ae9db..8a726a5de 100644 --- a/modules/exstats.js +++ b/modules/exstats.js @@ -207,7 +207,6 @@ exports.getStats = function(statIDs, options) { }; } if (statIDs.includes("caden")) { - needGPS = true; stats["caden"]={ title : "Cadence", getValue : function() { return state.stepsPerMin; },