From c54a6dbaaa503f0768c3006c6d241143326d1b2c Mon Sep 17 00:00:00 2001 From: "Marko.Kl.Berkenbusch@gmail.com" Date: Sat, 28 May 2022 11:26:12 -0400 Subject: [PATCH 01/46] Add lightning mode --- apps/f9lander/ChangeLog | 1 + apps/f9lander/app.js | 34 +++++++++++++++++++++++++++++++--- apps/f9lander/metadata.json | 3 ++- apps/f9lander/settings.js | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 apps/f9lander/settings.js diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog index 5560f00bc..a13f2a313 100644 --- a/apps/f9lander/ChangeLog +++ b/apps/f9lander/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Add lightning diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js index 7e52104c0..2f17a5bd5 100644 --- a/apps/f9lander/app.js +++ b/apps/f9lander/app.js @@ -46,6 +46,9 @@ var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2, var exploded = false; var nExplosions = 0; var landed = false; +var lightning = 0; + +var settings = require("Storage").readJSON('f9settings.json', 1) || {}; const gravity = 4; const dt = 0.1; @@ -61,18 +64,40 @@ function flameImageGen (throttle) { function drawFalcon(x, y, throttle, angle) { g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle}); - if (throttle>0) { + if (throttle>0 || lightning>0) { var flameImg = flameImageGen(throttle); var r = falcon9.height/2 + flameImg.height/2-1; var xoffs = -Math.sin(angle)*r; var yoffs = Math.cos(angle)*r; if (Math.random()>0.7) g.setColor(1, 0.5, 0); else g.setColor(1, 1, 0); - g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + if (throttle>0) g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + if (lightning>1 && lightning<30) { + for (var i=0; i<6; ++i) { + var r = Math.random()*6; + var x = Math.random()*5 - xoffs; + var y = Math.random()*5 - yoffs; + g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r); + } + } } } +function drawLightning() { + var c = {x:cloudOffs+50, y:30}; + var dx = c.x-booster.x; + var dy = c.y-booster.y; + var m1 = {x:booster.x+0.6*dx+Math.random()*20, y:booster.y+0.6*dy+Math.random()*10}; + var m2 = {x:booster.x+0.4*dx+Math.random()*20, y:booster.y+0.4*dy+Math.random()*10}; + g.setColor(1, 1, 1).drawLine(c.x, c.y, m1.x, m1.y).drawLine(m1.x, m1.y, m2.x, m2.y).drawLine(m2.x, m2.y, booster.x, booster.y); +} + function drawBG() { + if (lightning==1) { + g.setBgColor(1, 1, 1).clear(); + Bangle.buzz(200); + return; + } g.setBgColor(0.2, 0.2, 1).clear(); g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1); g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10); @@ -88,6 +113,7 @@ function renderScreen(input) { drawBG(); showFuel(); drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle); + if (lightning>1 && lightning<6) drawLightning(); } function getInputs() { @@ -97,6 +123,7 @@ function getInputs() { if (t > 1) t = 1; if (t < 0) t = 0; if (booster.fuel<=0) t = 0; + if (lightning>0 && lightning<20) t = 0; return {throttle: t, angle: a}; } @@ -121,7 +148,6 @@ function gameStep() { else { var input = getInputs(); if (booster.y >= targetY) { -// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle); if (Math.abs(booster.x-droneX-droneShip.width/2)40) && Math.random()>0.98) lightning = 1; booster.x += booster.vx*dt; booster.y += booster.vy*dt; booster.vy += gravity*dt; diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json index 75c6a0164..5e7031b45 100644 --- a/apps/f9lander/metadata.json +++ b/apps/f9lander/metadata.json @@ -1,7 +1,7 @@ { "id": "f9lander", "name": "Falcon9 Lander", "shortName":"F9lander", - "version":"0.01", + "version":"0.02", "description": "Land a rocket booster", "icon": "f9lander.png", "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }], @@ -11,5 +11,6 @@ "storage": [ {"name":"f9lander.app.js","url":"app.js"}, {"name":"f9lander.img","url":"app-icon.js","evaluate":true} + {"name":"f9lander.settings.js", "url":"settings.js"} ] } diff --git a/apps/f9lander/settings.js b/apps/f9lander/settings.js new file mode 100644 index 000000000..0f9fba302 --- /dev/null +++ b/apps/f9lander/settings.js @@ -0,0 +1,36 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; +(function(back) { + const SETTINGS_FILE = 'f9settings.json' + // initialize with default settings... + let settings = { + 'lightning': false, + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + settings[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + } + const menu = { + '': { 'title': 'OpenWind' }, + '< Back': back, + 'Lightning': { + value: settings.lightning, + format: boolFormat, + onchange: save('lightning'), + } + } + E.showMenu(menu); +}) From 3c121a808f845906ce4732662bb381fdeb09060f Mon Sep 17 00:00:00 2001 From: "Marko.Kl.Berkenbusch@gmail.com" Date: Sat, 28 May 2022 11:29:51 -0400 Subject: [PATCH 02/46] Fix missing comma --- apps/f9lander/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json index 5e7031b45..1db777099 100644 --- a/apps/f9lander/metadata.json +++ b/apps/f9lander/metadata.json @@ -10,7 +10,7 @@ "supports" : ["BANGLEJS", "BANGLEJS2"], "storage": [ {"name":"f9lander.app.js","url":"app.js"}, - {"name":"f9lander.img","url":"app-icon.js","evaluate":true} + {"name":"f9lander.img","url":"app-icon.js","evaluate":true}, {"name":"f9lander.settings.js", "url":"settings.js"} ] } From d5e68798979d54b9ad1c8cdd6d33ea21d49021b9 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 6 Nov 2022 12:31:13 +0100 Subject: [PATCH 03/46] add handling of gps events --- apps/android/boot.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/android/boot.js b/apps/android/boot.js index 0d1edae99..8bcc1ba0d 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -130,6 +130,13 @@ request.j(event.err); //r = reJect function else request.r(event); //r = resolve function + }, + "gps": function() { + delete event.t; + event.satellites = NaN; + event.course = NaN; + event.fix = 1; + Bangle.emit('gps', event); } }; var h = HANDLERS[event.t]; From 31bed21581242731de263bad8b5f43becbaccb3a Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 6 Nov 2022 13:41:49 +0100 Subject: [PATCH 04/46] add setting to set overwrite of internal gps --- apps/android/boot.js | 28 ++++++++++++++++++++++++++++ apps/android/settings.js | 13 +++++++++++++ 2 files changed, 41 insertions(+) diff --git a/apps/android/boot.js b/apps/android/boot.js index 8bcc1ba0d..a8455cf35 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -197,6 +197,34 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; + + // GPS overwrite logic + // Save current logic + const originalSetGpsPower = Bangle.setGPSPower; + const originalIsGpsOn = Bangle.isGPSOn; + + // Replace set GPS power logic to suppress activation of gps, if the overwrite option is active + Bangle.setGPSPower = (isOn, appID) => { + const currentSettings = require("Storage").readJSON("android.settings.json",1)||{}; + if (!currentSettings.overwriteGps) { + originalSetGpsPower(isOn, appID); + } else { + const logMessage = 'Ignore gps power change due to the gps overwrite from android integration app'; + console.log(logMessage); + Bluetooth.println(logMessage); + } + } + + // Replace check if the GPS is on, to show it as always active, if the overwrite option is set + Bangle.isGPSOn = () => { + const currentSettings = require("Storage").readJSON("android.settings.json",1)||{}; + if (!currentSettings.overwriteGps) { + return originalIsGpsOn(); + } else { + return true; + } + } + // remove settings object so it's not taking up RAM delete settings; })(); diff --git a/apps/android/settings.js b/apps/android/settings.js index c7c34a76f..94a1eba0b 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -1,4 +1,13 @@ (function(back) { + + function onGpsOverwriteChange(newValue) { + if (newValue) { + Bangle.setGPSPower(false, 'android'); + } + settings.overwriteGps = newValue; + updateSettings(); + } + function gb(j) { Bluetooth.println(JSON.stringify(j)); } @@ -23,6 +32,10 @@ updateSettings(); } }, + /*LANG*/"Overwrite GPS" : { + value : !!settings.overwriteGps, + onchange: onGpsOverwriteChange + }, /*LANG*/"Messages" : ()=>load("messages.app.js"), }; E.showMenu(mainmenu); From c68c14a9ed82502d906c2e2274a02441b7878cf6 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 6 Nov 2022 19:25:25 +0100 Subject: [PATCH 05/46] add check if gps data should be overwritten, before the gps data is emitted --- apps/android/boot.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/android/boot.js b/apps/android/boot.js index a8455cf35..5cdc1f044 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -132,6 +132,8 @@ request.r(event); //r = resolve function }, "gps": function() { + const settings = require("Storage").readJSON("android.settings.json",1)||{}; + if (!settings.overwriteGps) return; delete event.t; event.satellites = NaN; event.course = NaN; From 7561f8d7c245ac68dc222e2c87355693ce8e7f26 Mon Sep 17 00:00:00 2001 From: Lukas Date: Mon, 21 Nov 2022 20:39:41 +0100 Subject: [PATCH 06/46] add gps request handling --- apps/android/boot.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/android/boot.js b/apps/android/boot.js index 5cdc1f044..7973456cd 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -139,6 +139,10 @@ event.course = NaN; event.fix = 1; Bangle.emit('gps', event); + }, + "is_gps_active": function() { + const gpsActive = originalIsGpsOn(); + sendGPSPowerStatus(gpsActive); } }; var h = HANDLERS[event.t]; @@ -205,12 +209,15 @@ const originalSetGpsPower = Bangle.setGPSPower; const originalIsGpsOn = Bangle.isGPSOn; + function sendGPSPowerStatus(status) { gbSend({ t: "gps_power", status: status }); } + // Replace set GPS power logic to suppress activation of gps, if the overwrite option is active Bangle.setGPSPower = (isOn, appID) => { const currentSettings = require("Storage").readJSON("android.settings.json",1)||{}; if (!currentSettings.overwriteGps) { originalSetGpsPower(isOn, appID); } else { + sendGPSPowerStatus(Bangle.isGPSOn()); const logMessage = 'Ignore gps power change due to the gps overwrite from android integration app'; console.log(logMessage); Bluetooth.println(logMessage); From a7ad62a030c46162b8e0c44e7597dccdb9028177 Mon Sep 17 00:00:00 2001 From: Kedlub Date: Tue, 22 Nov 2022 13:52:21 +0100 Subject: [PATCH 07/46] qcenter: New app --- apps/qcenter/ChangeLog | 1 + apps/qcenter/README.md | 20 +++++++ apps/qcenter/app-icon.js | 1 + apps/qcenter/app.js | 114 +++++++++++++++++++++++++++++++++++++ apps/qcenter/app.png | Bin 0 -> 265 bytes apps/qcenter/metadata.json | 14 +++++ apps/qcenter/settings.js | 114 +++++++++++++++++++++++++++++++++++++ 7 files changed, 264 insertions(+) create mode 100644 apps/qcenter/ChangeLog create mode 100644 apps/qcenter/README.md create mode 100644 apps/qcenter/app-icon.js create mode 100644 apps/qcenter/app.js create mode 100644 apps/qcenter/app.png create mode 100644 apps/qcenter/metadata.json create mode 100644 apps/qcenter/settings.js diff --git a/apps/qcenter/ChangeLog b/apps/qcenter/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/qcenter/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/qcenter/README.md b/apps/qcenter/README.md new file mode 100644 index 000000000..eb40e63ed --- /dev/null +++ b/apps/qcenter/README.md @@ -0,0 +1,20 @@ +# Quick Center + +App with status bar showing various info, and up to six shortcuts for your favorite apps! +Meant to be used with any kind of quick launcher, such as Quick Launch or Pattern Launcher + +Add screen shots (if possible) to the app folder and link then into this file with ![](.png) + +## Usage + +Pin apps using settings, and then run this using your favorite quick launcher to access them quickly +If you don't have any pinned apps, it shows setting and about app as an example + +## Features + +Showing battery and temperature (for now) +Up to six shortcuts to your favorite apps + +## Upcoming features +- Quick toggles for toggleable functions, such as Bluetooth, or it's HID mode +- Customizable status info \ No newline at end of file diff --git a/apps/qcenter/app-icon.js b/apps/qcenter/app-icon.js new file mode 100644 index 000000000..bfc94d10a --- /dev/null +++ b/apps/qcenter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UB6cA/4ACBYNVAElQHAsFBYZFHCxIYEoALHgILNOxILChWqAAmgBYNUBZMVBYIAIBc0C1WAlWoAgQL/O96D/Qf4LZqoLJqoLMoAKHgILNqALHgoLBGBAKCDA4WDAEQA=")) \ No newline at end of file diff --git a/apps/qcenter/app.js b/apps/qcenter/app.js new file mode 100644 index 000000000..0493805ac --- /dev/null +++ b/apps/qcenter/app.js @@ -0,0 +1,114 @@ +require("Font8x12").add(Graphics); + +// load pinned apps from config +var settings = require("Storage").readJSON("qcenter.json", 1) || {}; +var pinnedApps = settings.pinnedApps || []; +var exitGesture = settings.exitGesture || "swipeup"; + +// if empty load a default set of apps as an example +if (pinnedApps.length == 0) { + pinnedApps = [ + { src: "setting.app.js", icon: "setting.img" }, + { src: "about.app.js", icon: "about.img" }, + ]; +} + +// button drawing from Layout.js, edited to have completely custom button size with icon +function drawButton(l) { + var x = l.x + (0 | l.pad), + y = l.y + (0 | l.pad), + w = l.w - (l.pad << 1), + h = l.h - (l.pad << 1); + var poly = [ + x, + y + 4, + x + 4, + y, + x + w - 5, + y, + x + w - 1, + y + 4, + x + w - 1, + y + h - 5, + x + w - 5, + y + h - 1, + x + 4, + y + h - 1, + x, + y + h - 5, + 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.src) + g.setBgColor(bg).drawImage( + "function" == typeof l.src ? l.src() : l.src, + l.x + l.w / 2, + l.y + l.h / 2, + { scale: l.scale || undefined, rotate: Math.PI * 0.5 * (l.r || 0) } + ); +} + +// function to split array into group of 3, for button placement +function groupBy3(data) { + var result = []; + for (var i = 0; i < data.length; i += 3) result.push(data.slice(i, i + 3)); + return result; +} + +// generate object with buttons for apps by group of 3 +var appButtons = groupBy3(pinnedApps).map((appGroup, i) => { + return appGroup.map((app, j) => { + return { + type: "custom", + render: drawButton, + width: 50, + height: 50, + pad: 5, + src: require("Storage").read(app.icon), + scale: 0.75, + cb: (l) => Bangle.load(app.src), + }; + }); +}); + +// create basic layout content with status info on top +var layoutContent = [ + { + type: "h", + pad: 5, + c: [ + { type: "txt", font: "8x12", label: E.getBattery() + "%" }, + { type: "txt", font: "8x12", label: " " + E.getTemperature() + "°C" }, + ], + }, +]; + +// create rows for buttons and add them to layoutContent +appButtons.forEach((appGroup) => { + layoutContent.push({ + type: "h", + pad: 2, + c: appGroup, + }); +}); + +var Layout = require("Layout"); +var layout = new Layout({ + type: "v", + c: layoutContent, +}); +g.clear(); +layout.render(); + +// add swipe event listener for exit gesture +Bangle.on("swipe", function (lr, ud) { + if(exitGesture == "swipeup" && ud == -1) Bangle.showClock(); + if(exitGesture == "swipedown" && ud == 1) Bangle.showClock(); + if(exitGesture == "swipeleft" && lr == -1) Bangle.showClock(); + if(exitGesture == "swiperight" && lr == 1) Bangle.showClock(); +}); \ No newline at end of file diff --git a/apps/qcenter/app.png b/apps/qcenter/app.png new file mode 100644 index 0000000000000000000000000000000000000000..27ec75f1c09f4503465e233ec2f3183a072d9476 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$1AIbUnWg6a|NkGzlQ=%#3`jAT1o;I61+Jgs1*&D>EbxddW? z8eR=R!75J|#}E(iw-X%s8Wea~HvX^QazJky^R=rk$x|$^bQ!bzludGP7I9GBy!Ou~ z?yX`UOj^{Iv4r?=uGrPThVYYmuN?b(Y0 z?|L^zI2@hQ5Vbty!jZ=o3!gd82{>7@`q { + var a = require("Storage").readJSON(app, 1); + return ( + a && { name: a.name, type: a.type, sortorder: a.sortorder, src: a.src, icon: a.icon } + ); + }) + .filter( + (app) => + app && + (app.type == "app" || + app.type == "launch" || + app.type == "clock" || + !app.type) + ); + apps.sort((a, b) => { + var n = (0 | a.sortorder) - (0 | b.sortorder); + if (n) return n; // do sortorder first + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + + function save(key, value) { + settings[key] = value; + require("Storage").write("qcenter.json", settings); + } + + var pinnedApps = settings.pinnedApps || []; + var exitGesture = settings.exitGesture || "swipeup"; + + function showMainMenu() { + var mainmenu = { + "" : { "title" : "Quick Center" }, + "< Back" : ()=>{load();} + }; + + // Set exit gesture + mainmenu["Exit Gesture: " + exitGesture] = function() { + E.showMenu(exitGestureMenu); + }; + + //List all pinned apps + for (let i = 0; i < pinnedApps.length; i++) { + mainmenu[pinnedApps[i].name] = function() { + E.showMenu({ + "" : { "title" : pinnedApps[i].name }, + "< Back" : showMainMenu, + "Unpin" : function() { + pinnedApps.splice(i, 1); + save("pinnedApps", pinnedApps); + showMainMenu(); + } + }); + }; + } + + // Show pin app button only if there is less than 6 pinned apps, else show the button that shows alert that max apps has been pinned + if (pinnedApps.length < 6) { + mainmenu["Pin App"] = pinAppMenu; + } + else { + mainmenu["Pin App"] = function() { + E.showAlert("You can only pin 6 apps"); + }; + } + + return E.showMenu(mainmenu); + } + + // menu for adding apps to the quick launch menu, listing all apps + var pinAppMenu = { + "" : { "title" : "Add App" }, + "< Back" : showMainMenu + }; + apps.forEach((a)=>{ + pinAppMenu[a.name] = function() { + // strip unncecessary properties + delete a.type; + delete a.sortorder; + delete a.name; + pinnedApps.push(a); + save("pinnedApps", pinnedApps); + showMainMenu(); + }; + }); + + // menu for setting exit gesture + var exitGestureMenu = { + "" : { "title" : "Exit Gesture" }, + "< Back" : showMainMenu + }; + exitGestureMenu["Swipe Up"] = function() { + save("exitGesture", "swipeup"); + showMainMenu(); + } + exitGestureMenu["Swipe Down"] = function() { + save("exitGesture", "swipedown"); + showMainMenu(); + } + exitGestureMenu["Swipe Left"] = function() { + save("exitGesture", "swipeleft"); + showMainMenu(); + } + exitGestureMenu["Swipe Right"] = function() { + save("exitGesture", "swiperight"); + showMainMenu(); + } + +}); From 1c38505c9fc331c073b782e6fdeb3eac7fbb4cfe Mon Sep 17 00:00:00 2001 From: Kedlub Date: Tue, 22 Nov 2022 13:57:13 +0100 Subject: [PATCH 08/46] qcenter: Fix metadata --- apps/qcenter/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/qcenter/metadata.json b/apps/qcenter/metadata.json index b5e580bf0..736db2024 100644 --- a/apps/qcenter/metadata.json +++ b/apps/qcenter/metadata.json @@ -9,6 +9,7 @@ "readme": "README.md", "storage": [ {"name":"qcenter.app.js","url":"app.js"}, + {"name":"qcenter.settings.js","url":"settings.js"}, {"name":"qcenter.img","url":"app-icon.js","evaluate":true} ] } From db6b143c65e6b50e9d33f4d5e80c197d8b06e9c3 Mon Sep 17 00:00:00 2001 From: Kedlub Date: Tue, 22 Nov 2022 16:33:32 +0100 Subject: [PATCH 09/46] qcenter: Fix settings --- apps/qcenter/settings.js | 163 +++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/apps/qcenter/settings.js b/apps/qcenter/settings.js index 1bda89036..5550fd149 100644 --- a/apps/qcenter/settings.js +++ b/apps/qcenter/settings.js @@ -6,7 +6,13 @@ .map((app) => { var a = require("Storage").readJSON(app, 1); return ( - a && { name: a.name, type: a.type, sortorder: a.sortorder, src: a.src, icon: a.icon } + a && { + name: a.name, + type: a.type, + sortorder: a.sortorder, + src: a.src, + icon: a.icon, + } ); }) .filter( @@ -30,85 +36,90 @@ require("Storage").write("qcenter.json", settings); } - var pinnedApps = settings.pinnedApps || []; - var exitGesture = settings.exitGesture || "swipeup"; + var pinnedApps = settings.pinnedApps || []; + var exitGesture = settings.exitGesture || "swipeup"; - function showMainMenu() { - var mainmenu = { - "" : { "title" : "Quick Center" }, - "< Back" : ()=>{load();} - }; + function showMainMenu() { + var mainmenu = { + "": { title: "Quick Center" }, + "< Back": () => { + load(); + }, + }; - // Set exit gesture - mainmenu["Exit Gesture: " + exitGesture] = function() { - E.showMenu(exitGestureMenu); - }; - - //List all pinned apps - for (let i = 0; i < pinnedApps.length; i++) { - mainmenu[pinnedApps[i].name] = function() { - E.showMenu({ - "" : { "title" : pinnedApps[i].name }, - "< Back" : showMainMenu, - "Unpin" : function() { - pinnedApps.splice(i, 1); - save("pinnedApps", pinnedApps); - showMainMenu(); - } - }); - }; - } + // Set exit gesture + mainmenu["Exit Gesture: " + exitGesture] = function () { + E.showMenu(exitGestureMenu); + }; - // Show pin app button only if there is less than 6 pinned apps, else show the button that shows alert that max apps has been pinned - if (pinnedApps.length < 6) { - mainmenu["Pin App"] = pinAppMenu; - } - else { - mainmenu["Pin App"] = function() { - E.showAlert("You can only pin 6 apps"); - }; - } - - return E.showMenu(mainmenu); - } + //List all pinned apps + for (let i = 0; i < pinnedApps.length; i++) { + mainmenu[pinnedApps[i].name] = function () { + E.showMenu({ + "": { title: pinnedApps[i].name }, + "< Back": showMainMenu, + Unpin: function () { + pinnedApps.splice(i, 1); + save("pinnedApps", pinnedApps); + showMainMenu(); + }, + }); + }; + } - // menu for adding apps to the quick launch menu, listing all apps - var pinAppMenu = { - "" : { "title" : "Add App" }, - "< Back" : showMainMenu - }; - apps.forEach((a)=>{ - pinAppMenu[a.name] = function() { - // strip unncecessary properties - delete a.type; - delete a.sortorder; - delete a.name; - pinnedApps.push(a); - save("pinnedApps", pinnedApps); - showMainMenu(); - }; - }); + // Show pin app menu, or show alert if max amount of apps are pinned + mainmenu["Pin App"] = function () { + if (pinnedApps.length < 6) { + E.showMenu(pinAppMenu); + } else { + E.showAlert("Max apps pinned").then(showMainMenu); + } + }; - // menu for setting exit gesture - var exitGestureMenu = { - "" : { "title" : "Exit Gesture" }, - "< Back" : showMainMenu - }; - exitGestureMenu["Swipe Up"] = function() { - save("exitGesture", "swipeup"); - showMainMenu(); - } - exitGestureMenu["Swipe Down"] = function() { - save("exitGesture", "swipedown"); - showMainMenu(); - } - exitGestureMenu["Swipe Left"] = function() { - save("exitGesture", "swipeleft"); - showMainMenu(); - } - exitGestureMenu["Swipe Right"] = function() { - save("exitGesture", "swiperight"); - showMainMenu(); - } + return E.showMenu(mainmenu); + } + // menu for adding apps to the quick launch menu, listing all apps + var pinAppMenu = { + "": { title: "Add App" }, + "< Back": showMainMenu, + }; + apps.forEach((a) => { + pinAppMenu[a.name] = function () { + // strip unncecessary properties + delete a.type; + delete a.sortorder; + pinnedApps.push(a); + save("pinnedApps", pinnedApps); + showMainMenu(); + }; + }); + + // menu for setting exit gesture + var exitGestureMenu = { + "": { title: "Exit Gesture" }, + "< Back": showMainMenu, + }; + exitGestureMenu["Swipe Up"] = function () { + exitGesture = "swipeup"; + save("exitGesture", "swipeup"); + showMainMenu(); + }; + exitGestureMenu["Swipe Down"] = function () { + exitGesture = "swipedown"; + save("exitGesture", "swipedown"); + showMainMenu(); + }; + exitGestureMenu["Swipe Left"] = function () { + exitGesture = "swipeleft"; + save("exitGesture", "swipeleft"); + showMainMenu(); + }; + exitGestureMenu["Swipe Right"] = function () { + exitGesture = "swiperight"; + save("exitGesture", "swiperight"); + showMainMenu(); + }; + + showMainMenu(); }); From 8cf2f9957a278e6639902b96482cea3a43024cd8 Mon Sep 17 00:00:00 2001 From: Kedlub Date: Thu, 24 Nov 2022 18:30:29 +0100 Subject: [PATCH 10/46] qcenter: Added widgets & fix remove --- apps/qcenter/app.js | 11 ++++++++--- apps/qcenter/settings.js | 18 ++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/qcenter/app.js b/apps/qcenter/app.js index 0493805ac..a6499f508 100644 --- a/apps/qcenter/app.js +++ b/apps/qcenter/app.js @@ -82,8 +82,8 @@ var layoutContent = [ type: "h", pad: 5, c: [ - { type: "txt", font: "8x12", label: E.getBattery() + "%" }, - { type: "txt", font: "8x12", label: " " + E.getTemperature() + "°C" }, + { type: "txt", font: "8x12", scale: 2, label: E.getBattery() + "%" }, + { type: "txt", font: "8x12", scale: 2, label: " " + E.getTemperature() + "°C" }, ], }, ]; @@ -97,6 +97,10 @@ appButtons.forEach((appGroup) => { }); }); +// create layout with content + +Bangle.loadWidgets(); + var Layout = require("Layout"); var layout = new Layout({ type: "v", @@ -104,8 +108,9 @@ var layout = new Layout({ }); g.clear(); layout.render(); +Bangle.drawWidgets(); -// add swipe event listener for exit gesture +// swipe event listener for exit gesture Bangle.on("swipe", function (lr, ud) { if(exitGesture == "swipeup" && ud == -1) Bangle.showClock(); if(exitGesture == "swipedown" && ud == 1) Bangle.showClock(); diff --git a/apps/qcenter/settings.js b/apps/qcenter/settings.js index 5550fd149..10484bc8f 100644 --- a/apps/qcenter/settings.js +++ b/apps/qcenter/settings.js @@ -53,19 +53,13 @@ }; //List all pinned apps - for (let i = 0; i < pinnedApps.length; i++) { - mainmenu[pinnedApps[i].name] = function () { - E.showMenu({ - "": { title: pinnedApps[i].name }, - "< Back": showMainMenu, - Unpin: function () { - pinnedApps.splice(i, 1); - save("pinnedApps", pinnedApps); - showMainMenu(); - }, - }); + pinnedApps.forEach((app, i) => { + mainmenu["Remove " + app.name] = function () { + pinnedApps.splice(i, 1); + save("pinnedApps", pinnedApps); + showMainMenu(); }; - } + }); // Show pin app menu, or show alert if max amount of apps are pinned mainmenu["Pin App"] = function () { From f4a48e62e676897edffc0ea77c77c9357558c18d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 09:42:25 +0000 Subject: [PATCH 11/46] Added Util.close to allow interfaces to close the window Fix My Location that couldn't be installed after the most recent update --- apps/mylocation/ChangeLog | 1 + apps/mylocation/README.md | 9 ++++-- .../{custom.html => interface.html} | 31 +++++++++++++++---- apps/mylocation/metadata.json | 4 +-- core | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) rename apps/mylocation/{custom.html => interface.html} (79%) diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index b46b3b178..afe1810e9 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -6,3 +6,4 @@ 0.06: renamed source files to match standard 0.07: Move mylocation app into 'Settings -> Apps' 0.08: Allow setting location from webinterface in the AppLoader +0.09: Fix web interface so app can be installed (replaced custom with interface html) diff --git a/apps/mylocation/README.md b/apps/mylocation/README.md index b12ed5dcf..11a644262 100644 --- a/apps/mylocation/README.md +++ b/apps/mylocation/README.md @@ -1,8 +1,13 @@ # My Location - *Sets and stores GPS lat and lon of your preferred city* +*Sets and stores GPS lat and lon of your preferred city* -To access, go to `Settings -> Apps -> My Location` +To access, you have two options: + +**In the App Loader** once My Location is installed, click on the 'Save' icon +next to it - and you can choose your location on a map. + +**On Bangle.js** go to `Settings -> Apps -> My Location` * Select one of the preset Cities, setup through the GPS or use the webinterface from the AppLoader * Other Apps can read this information to do calculations based on location diff --git a/apps/mylocation/custom.html b/apps/mylocation/interface.html similarity index 79% rename from apps/mylocation/custom.html rename to apps/mylocation/interface.html index 5c0130199..79a122bf7 100644 --- a/apps/mylocation/custom.html +++ b/apps/mylocation/interface.html @@ -33,10 +33,11 @@
+ Click the map to select a location
- + @@ -68,15 +69,19 @@ let latlon; var marker; - - map.on('click', function(e){ - console.log(e); + + function setPosition(ll) { + latlon = ll; if (map.hasLayer(marker)) { map.removeLayer(marker); } - latlon = e.latlng; - marker = new L.marker(e.latlng).addTo(map); + marker = new L.marker(latlon).addTo(map); + document.getElementById("select-hint").style.display="none"; document.getElementById("select").style.display=""; + } + + map.on('click', function(e){ + setPosition(e.latlng); }); document.getElementById("select").addEventListener("click", function() { @@ -87,9 +92,23 @@ Util.showModal("Saving..."); Util.writeStorage("mylocation.json", JSON.stringify(settings), ()=>{ Util.hideModal(); + Util.close(); // close this window }); }); + function onInit() { + // read existing location + Util.readStorage("mylocation.json", function(data) { + if (data===undefined) return; // no file + try { + var j = JSON.parse(data); + setPosition(j); + } catch (e) { + console.error(e); + } + }); + } + diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index bef5b983c..1c2974030 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -4,12 +4,12 @@ "icon": "app.png", "type": "settings", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.08", + "version":"0.09", "description": "Sets and stores the latitude and longitude of your preferred City. It can be set from GPS or webinterface. `mylocation.json` can be used by other apps that need your main location. See README for details.", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], - "custom": "custom.html","custom": "custom.html", + "interface": "interface.html", "storage": [ {"name":"mylocation.settings.js","url":"settings.js"} ], diff --git a/core b/core index db08367e0..aba9b6a51 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit db08367e0a2c25040449a4b556eaed459e8f47fc +Subproject commit aba9b6a51fe02dfbde307c303560b8382857916d From bc052c3aced0ce524b6ef0946964d81edf2e7136 Mon Sep 17 00:00:00 2001 From: Kedlub Date: Fri, 25 Nov 2022 14:42:10 +0100 Subject: [PATCH 12/46] qcenter: Grammar fixes & Final touches --- apps/qcenter/README.md | 16 ++++++++-------- apps/qcenter/app.js | 4 ++-- apps/qcenter/metadata.json | 30 ++++++++++++++++-------------- apps/qcenter/screenshot.png | Bin 0 -> 3647 bytes apps/qcenter/settings.js | 32 +++++++++++++++++++++++++++----- 5 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 apps/qcenter/screenshot.png diff --git a/apps/qcenter/README.md b/apps/qcenter/README.md index eb40e63ed..4931b9c7f 100644 --- a/apps/qcenter/README.md +++ b/apps/qcenter/README.md @@ -1,20 +1,20 @@ # Quick Center -App with status bar showing various info, and up to six shortcuts for your favorite apps! -Meant to be used with any kind of quick launcher, such as Quick Launch or Pattern Launcher +An app with a status bar showing various information and up to six shortcuts for your favorite apps! +Designed for use with any kind of quick launcher, such as Quick Launch or Pattern Launcher. -Add screen shots (if possible) to the app folder and link then into this file with ![](.png) +![](screenshot.png) ## Usage -Pin apps using settings, and then run this using your favorite quick launcher to access them quickly -If you don't have any pinned apps, it shows setting and about app as an example +Pin your apps with settings, then launch them with your favorite quick launcher to access them quickly. +If you don't have any apps pinned, the settings and about apps will be shown as an example. ## Features -Showing battery and temperature (for now) +Battery and GPS status display (for now) Up to six shortcuts to your favorite apps ## Upcoming features -- Quick toggles for toggleable functions, such as Bluetooth, or it's HID mode -- Customizable status info \ No newline at end of file +- Quick switches for toggleable features such as Bluetooth or HID mode +- Customizable status information \ No newline at end of file diff --git a/apps/qcenter/app.js b/apps/qcenter/app.js index a6499f508..876d4aba9 100644 --- a/apps/qcenter/app.js +++ b/apps/qcenter/app.js @@ -76,14 +76,14 @@ var appButtons = groupBy3(pinnedApps).map((appGroup, i) => { }); }); -// create basic layout content with status info on top +// create basic layout content with status info and sensor status on top var layoutContent = [ { type: "h", pad: 5, c: [ { type: "txt", font: "8x12", scale: 2, label: E.getBattery() + "%" }, - { type: "txt", font: "8x12", scale: 2, label: " " + E.getTemperature() + "°C" }, + { type: "txt", font: "8x12", scale: 2, label: "GPS: " + (Bangle.isGPSOn() ? "ON" : "OFF") }, ], }, ]; diff --git a/apps/qcenter/metadata.json b/apps/qcenter/metadata.json index 736db2024..abef0fc36 100644 --- a/apps/qcenter/metadata.json +++ b/apps/qcenter/metadata.json @@ -1,15 +1,17 @@ -{ "id": "qcenter", - "name": "Quick Center", - "shortName":"QCenter", - "version":"0.01", - "description": "Shortcut for running your favorite apps & more", - "icon": "app.png", - "tags": "", - "supports" : ["BANGLEJS2"], - "readme": "README.md", - "storage": [ - {"name":"qcenter.app.js","url":"app.js"}, - {"name":"qcenter.settings.js","url":"settings.js"}, - {"name":"qcenter.img","url":"app-icon.js","evaluate":true} - ] +{ + "id": "qcenter", + "name": "Quick Center", + "shortName": "QCenter", + "version": "0.01", + "description": "Shortcut app for quickly running your favorite apps", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{ "url": "screenshot.png" }], + "storage": [ + { "name": "qcenter.app.js", "url": "app.js" }, + { "name": "qcenter.settings.js", "url": "settings.js" }, + { "name": "qcenter.img", "url": "app-icon.js", "evaluate": true } + ] } diff --git a/apps/qcenter/screenshot.png b/apps/qcenter/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0a335aa58ba70ca0223116ca014294119c2036 GIT binary patch literal 3647 zcmcInXH%1l5`GgBL69bf-i!2r1qcL*bfhXFkxoQWks6{%QAj{U6e&s(2t{cs5)cSQ z0!9T;T=b7EvK0C8X=N+y2c_n!P0N_7oW8r)ttN$gQ zLkGO7VaoeJfC$dkW&njDJ$qpE`<#WTON94g{?FW6jU)FOwqw7KS!-)6UOKA$0I^rg zxucjidtyH2~2@Bi8NEc!g&pO9R=7}w29n5k$>E#rqE5~zcnN`l+#XEljv zQ;PyMst#D|gDunwUbx<9C2SXTnqyb+l%dB(mLmGMk?CdFy9yef%QTc8Nm>Lu-Tk<5 zD*%ij+e2dxRcnJx_(Go9F;fk>y^USjKh!hMPyYfr^K7dj>UFSSW@&G5l7IN`% znBiPUbA|G4c5e#r^J|2{y>J4?+-X?ND6V7;$wv{nfVZPOZK%4Zzg3K$8Volt_Y?2a7dZ}fXM=3h8NR(ZxA^qcN&20cN)>!a^{l@KBGNsh~)yye}5Kc|GsSe ze^8;h$k7sAM;&HamFYvY1)gr^R~xD)%iL7)D~-akVW zl5a46_Hx!I%8vxOH*`OAUqr52g{Zz8Hm#D0RxaJhOW2y>e}A!Gj2G&HCPn+7^O{DIWK%A zmrt(3s0?JGZtFN*YW(Bf4h{G21iB;IsR%$NR(+dp#?*#TnrFR zvKqXr`;Ic~56TK-@36k-A<`x7^q74f?fo=Z!uZKUAgl2yd=ySs(7uu$|2wxIzXE{5 zLp7QgI3Vz7&#?5@4_uIu53d$#3b}bgQ9cA^Zb2a&cX>@$jI2C;?@o8RwDDP@47n4& z8%YGV*Y&K!{(A>>e)h(PuQA7il%AKX*R4O7ad*o6L49a#^pKCPWX$aI70~{JklGg| zT>sv|6F&8;SDIZ|JN`%j9-`R1u)OP4)mB%0ca+g)?taz*Bzvi}{Ql6lSHH-$ya|k| z9CWwgb=hmF4F%{^#B|?Mo$20sCz$74`9PuHbaCX^w3MGHvNsM7i1VwwtLE4-l3UzTkL%v4}MNpQgi1+ z-e@%)@1lOw-&tD5x>a*JMUg_10zk1sY{z2RvJM|r$qjp_DadGY?MUCV0FEX#;96qP z@kcUE^Q-Q{^&_OKbHK{gpmPetndBI(&NPbEkgCe5e40AGEX8b(6-%go6H!t(v*Y;t z;xsc(uGR3yG0oooK7lUdm-NHgiBK=%?bx}1T~Rv-sM5LXVMr-moA4RugG8ovY;_QL zjqeJO(Vz3z!0ldD61F&NSuJ&Z092q$mAbsWES?fHw*{HgfChW@G>njFvuS2u%wx>A zGF1)z&H`2ban4D58Jgd4wdS3${-muhGTQn|HtupE|28Aluo@c~U;Odz`U`NP&#xp} z9(ux?m)4C*{WX6}5IT&Ps2#c(XkOml@g_g%dPGa`2gS@?7o%ii}*ig6U zwi=IbW`A#YO^rUk13fzvF~_uQ=I9a$7V&Pt zIOM0;!02GB3a7?J!dK;C{2PpK0DSaQO3GN%dEGl(ZGA zhk7oL{jH|CdLNyC)MEnlzVb)8&=WYR#+7{Ijx+eCu$Yt>=VJ>DfgKgMEoplfsK0jY zm%(BQ;DLctJQ5>+=2`Gm{f(a=wJmPcSb6v2Q^y5TYUIjm8J~lh?&L~GpxIT=Z>Y$g zBVByElOYuoEqy2KlAUqyQ9OXCqUX+i+WzL`*nBtP=W9zFWQkdpU%Ll$Y3S-&Q0~W^ z3diJ^bbPuwO#RUtOJ=p&umseK1!sC*TzlFsa4b6U*oz{%!p0Z#>qB2HWtn1u7mA%d za>gdPQk$I75xPe)swICyHIZr2-(?V8#$lK9&!Cq2L2Q~?d$!Ld7CcPZSRnlQ-~!6B z)6XY*VlgxD*2E2{BZ1X8m&Ww2Xlp>TLtqMfYt;S~KlfTe1jT!%>dUR@;7SaS`uqr| zZCWMZ0^IJ$fKy-7tCQv=Vab7S4w4ml;3_Ap5+|(zFgenwsqWEe%|trbZqn7^ux<1` zGsfGN#a^|*H@&H7+=Zm#QAA5M-|d(%C#A$6-8P4{G{^g}G`@zc2U0crLmbY@%7B zIc@T=(`K+=Y1sRgOuN0QvaQ2TmPkRUfK!l;tW{_92-oVl%$v7X-|hV+1|;8Kh&?Dx zJ1E-2_u&36ehDvX(nGT`N~mR_YKl$ImL91@CKvhm)?u83v8|8+bcg%xrW;osV=ko- zKa@k5zp7by>ni*jL*}c1KfD_xYnkK$35KYY$xQoAfkTOno4oZK_b$;M|D3p9Hnh2nf`wa~R0L$cFkN*FR;)NJB`D-7{kE8e)JOo5QJm)vWb%nBZM(j+1W4-a?g~MGB?uxMOU94PMggK|dy%0pw zN1M*PSiw)?g5Oer<&)fCEwHEDi;Zr*Ypg3GSIuB1pYH(%Slp42vE5@IrEa>PnR0t? z++p+-K$AKY`w&v#Q8!Z0)W{nv-2~!%vt>b2KM&$@V7w@yLMn2!_9fj4YT}(1WT+j7 zHPRVK*Xy*0mVwRH)nAce&I2ZuaQOtwj^}t0{UUIo3l8GHxH>9hqM-{DztJlJ#j>#b zw0ANe@*T5y?TP%r@;sHNeyHOWUDqXmt4!#jk!~Eik~Z7J1E`dNFF!b3eUjTsC|ZWK z_vibcyknz$!imX%<}ugTqU^q5Q}bHXG580kgXot9)nJIqVa4{r{nt;9EaQ716+*NJ z@n-`l8;hMDSl`89z3$N>(o;Pg?4jlrTu=5r&@vAk#I=@t4x?antxLt z_Dmh3INx0UK`C)?A!lY&>Qk?kzS@eyMSj%%J3Be97tApj9*ydlO(C40Kyuhc z7l-PC9wgrAAYC((@ST~D{4&>MT)6`%FiIkW(A0GfT}mJ-F!gW( zp6OglVrWu&B2z|{?yI5*?}Hfpd@t@Dy(G2l8zA<_FiMebCT-nmV4d4_eME$9W}zQC zm$73{k$Rt#zmk=G1a#<04RCWhG4;C$Tl4qi2t&3Aj6^;g+l?XuyMMA { - mainmenu["Remove " + app.name] = function () { - pinnedApps.splice(i, 1); - save("pinnedApps", pinnedApps); - showMainMenu(); + mainmenu[app.name] = function () { + E.showMenu({ + "": { title: app.name }, + "< Back": () => { + showMainMenu(); + }, + "Unpin": () => { + pinnedApps.splice(i, 1); + save("pinnedApps", pinnedApps); + showMainMenu(); + }, + "Move Up": () => { + if (i > 0) { + pinnedApps.splice(i - 1, 0, pinnedApps.splice(i, 1)[0]); + save("pinnedApps", pinnedApps); + showMainMenu(); + } + }, + "Move Down": () => { + if (i < pinnedApps.length - 1) { + pinnedApps.splice(i + 1, 0, pinnedApps.splice(i, 1)[0]); + save("pinnedApps", pinnedApps); + showMainMenu(); + } + }, + }); }; }); From 459a643c8720015e582248a8a6790661624ed0d0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 15:34:51 +0000 Subject: [PATCH 13/46] openstmap Added ability to upload multiple sets of map tiles Support for zooming in on map Satellite count moved to widget bar to leave more room for the map --- apps/openstmap/ChangeLog | 2 + apps/openstmap/README.md | 48 +++++ apps/openstmap/app.js | 101 +++++---- .../openstmap/{custom.html => interface.html} | 198 +++++++++++++++--- apps/openstmap/metadata.json | 9 +- apps/openstmap/openstmap.js | 73 +++++-- apps/recorder/widget.js | 1 + 7 files changed, 343 insertions(+), 89 deletions(-) create mode 100644 apps/openstmap/README.md rename apps/openstmap/{custom.html => interface.html} (62%) diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 2a6c15a09..4b8dd108c 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -12,3 +12,5 @@ Fix alignment of satellite info text 0.12: switch to using normal OpenStreetMap tiles (opentopomap was too slow) 0.13: Use a single image file with 'frames' of data (drastically reduces file count, possibility of >1 map on device) +0.14: Added ability to upload multiple sets of map tiles + Support for zooming in on map diff --git a/apps/openstmap/README.md b/apps/openstmap/README.md new file mode 100644 index 000000000..707dbc7f8 --- /dev/null +++ b/apps/openstmap/README.md @@ -0,0 +1,48 @@ +# OpenStreetMap + +This app allows you to upload and use OpenSteetMap map tiles onto your +Bangle. There's an uploader, the app, and also a library that +allows you to use the maps in your Bangle.js applications. + +## Uploader + +Once you've installed OpenStreepMap on your Bangle, find it +in the App Loader and click the Disk icon next to it. + +A window will pop up showing what maps you have loaded. + +To add a map: + +* Click `Add Map` +* Scroll and zoom to the area of interest or use the Search button in the top left +* Now choose the size you want to upload (Small/Medium/etc) +* On Bangle.js 1 you can choose if you want a 3 bits per pixel map (this is lower +quality but uploads faster and takes less space). On Bangle.js 2 you only have a 3bpp +display so can only use 3bpp. +* Click `Get Map`, and a preview will be displayed. If you need to adjust the area you +can change settings, move the map around, and click `Get Map` again. +* When you're ready, click `Upload` + +## Bangle.js App + +The Bangle.js app allows you to view a map - it also turns the GPS on and marks +the path that you've been travelling. + +* Drag on the screen to move the map +* Press the button to bring up a menu, where you can zoom, go to GPS location +or put the map back in its default location + +## Library + +See the documentation in the library itself for full usage info: +https://github.com/espruino/BangleApps/blob/master/apps/openstmap/openstmap.js + +Or check the app itself: https://github.com/espruino/BangleApps/blob/master/apps/openstmap/app.js + +But in the most simple form: + +``` +var m = require("openstmap"); +// m.lat/lon are now the center of the loaded map +m.draw(); // draw centered on the middle of the loaded map +``` diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 62597ca20..99362d97b 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -1,20 +1,27 @@ var m = require("openstmap"); var HASWIDGETS = true; -var y1,y2; +var R; var fix = {}; +var mapVisible = false; +var hasScrolled = false; +// Redraw the whole page function redraw() { - g.setClipRect(0,y1,g.getWidth()-1,y2); + g.setClipRect(R.x,R.y,R.x2,R.y2); m.draw(); drawMarker(); - if (WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { - g.flip(); // force immediate draw on double-buffered screens - track will update later - g.setColor(0.75,0.2,0); + if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { + g.flip().setColor("#f00"); // force immediate draw on double-buffered screens - track will update later WIDGETS["gpsrec"].plotTrack(m); } + if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { + g.flip().setColor("#f00"); // force immediate draw on double-buffered screens - track will update later + WIDGETS["recorder"].plotTrack(m); + } g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); } +// Draw the marker for where we are function drawMarker() { if (!fix.fix) return; var p = m.latLonToXY(fix.lat, fix.lon); @@ -22,50 +29,66 @@ function drawMarker() { g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2); } -var fix; Bangle.on('GPS',function(f) { fix=f; - g.reset().clearRect(0,y1,g.getWidth()-1,y1+8).setFont("6x8").setFontAlign(0,0); - var txt = fix.satellites+" satellites"; - if (!fix.fix) - txt += " - NO FIX"; - g.drawString(txt,g.getWidth()/2,y1 + 4); - drawMarker(); + if (HASWIDGETS) WIDGETS["sats"].draw(WIDGETS["sats"]); + if (mapVisible) drawMarker(); }); Bangle.setGPSPower(1, "app"); if (HASWIDGETS) { Bangle.loadWidgets(); + WIDGETS["sats"] = { area:"tl", width:48, draw:w=>{ + var txt = (0|fix.satellites)+" Sats"; + if (!fix.fix) txt += "\nNO FIX"; + g.reset().setFont("6x8").setFontAlign(0,0) + .drawString(txt,w.x+24,w.y+12); + } + }; Bangle.drawWidgets(); - y1 = 24; - var hasBottomRow = Object.keys(WIDGETS).some(w=>WIDGETS[w].area[0]=="b"); - y2 = g.getHeight() - (hasBottomRow ? 24 : 1); -} else { - y1=0; - y2=g.getHeight()-1; } +R = Bangle.appRect; -redraw(); - -function recenter() { - if (!fix.fix) return; - m.lat = fix.lat; - m.lon = fix.lon; +function showMap() { + mapVisible = true; + g.reset().clearRect(R); redraw(); + Bangle.setUI({mode:"custom",drag:e=>{ + if (e.b) { + g.setClipRect(R.x,R.y,R.x2,R.y2); + g.scroll(e.dx,e.dy); + m.scroll(e.dx,e.dy); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + hasScrolled = true; + } else if (hasScrolled) { + hasScrolled = false; + redraw(); + } + }, btn: btn=>{ + mapVisible = false; + var menu = {"":{title:"Map"}, + "< Back": ()=> showMap(), + /*LANG*/"Zoom In": () =>{ + m.scale /= 2; + showMap(); + }, + /*LANG*/"Zoom Out": () =>{ + m.scale *= 2; + showMap(); + }, + /*LANG*/"Center Map": () =>{ + m.lat = m.map.lat; + m.lon = m.map.lon; + m.scale = m.map.scale; + showMap(); + }}; + if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ + m.lat = fix.lat; + m.lon = fix.lon; + showMap(); + }; + E.showMenu(menu); + }}); } -setWatch(recenter, global.BTN2?BTN2:BTN1, {repeat:true}); - -var hasScrolled = false; -Bangle.on('drag',e=>{ - if (e.b) { - g.setClipRect(0,y1,g.getWidth()-1,y2); - g.scroll(e.dx,e.dy); - m.scroll(e.dx,e.dy); - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); - hasScrolled = true; - } else if (hasScrolled) { - hasScrolled = false; - redraw(); - } -}); +showMap(); diff --git a/apps/openstmap/custom.html b/apps/openstmap/interface.html similarity index 62% rename from apps/openstmap/custom.html rename to apps/openstmap/interface.html index c1a161458..81406d807 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/interface.html @@ -9,7 +9,8 @@ padding: 0; margin: 0; } - html, body, #map { + html, body, #map, #mapsLoaded, #mapContainer { + position: relative; height: 100%; width: 100%; } @@ -27,20 +28,40 @@ width: 256px; height: 256px; } + .tile-title { + font-weight:bold; + font-size: 125%; + } + .tile-map { + width: 128px; + height: 128px; + } -
+
-
-

3 bit
-
- - +
+
+
+
+

3 bit
+
+ +
+
+ + +
- + @@ -60,8 +81,6 @@ TODO: */ var TILESIZE = 96; // Size of our tiles var OSMTILESIZE = 256; // Size of openstreetmap tiles - var MAPSIZE = TILESIZE*5; ///< 480 - Size of map we download - var OSMTILECOUNT = 3; // how many tiles do we download in each direction (Math.floor(MAPSIZE / OSMTILESIZE)+1) /* Can see possible tiles on http://leaflet-extras.github.io/leaflet-providers/preview/ However some don't allow cross-origin use */ //var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast, TOO SLOW @@ -69,8 +88,8 @@ TODO: var TILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; var PREVIEWTILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - // Create map and try and set the location to where the browser thinks we are - var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true}); + var loadedMaps = []; + // Tiles used for Bangle.js itself var bangleTileLayer = L.tileLayer(TILELAYER, { maxZoom: 18, @@ -83,6 +102,10 @@ TODO: }); // Could optionally overlay trails: https://wiki.openstreetmap.org/wiki/Tiles + // Create map and try and set the location to where the browser thinks we are + var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true}); + previewTileLayer.addTo(map); + // Search box: const searchProvider = new window.GeoSearch.OpenStreetMapProvider(); const searchControl = new GeoSearch.GeoSearchControl({ @@ -96,6 +119,7 @@ TODO: }); map.addControl(searchControl); + // ---------------------------------------- Run at startup function onInit(device) { if (device && device.info && device.info.g) { // On 3 bit devices, don't even offer the option. 3 bit is the only way @@ -104,12 +128,120 @@ TODO: document.getElementById("3bitdiv").style = "display:none"; } } + + showLoadedMaps(); } - var mapFiles = []; - previewTileLayer.addTo(map); + function showLoadedMaps() { + document.getElementById("mapsLoadedContainer").style.display = ""; + document.getElementById("mapContainer").style.display = "none"; - function tilesLoaded(ctx, width, height) { + Util.showModal("Loading maps..."); + let mapsLoadedContainer = document.getElementById("mapsLoadedContainer"); + mapsLoadedContainer.innerHTML = ""; + loadedMaps = []; + + Puck.write(`\x10Bluetooth.println(require("Storage").list(/openstmap\\.\\d+\\.json/))\n`,function(files) { + console.log("MAPS:",files); + let promise = Promise.resolve(); + files.trim().split(",").forEach(filename => { + if (filename=="") return; + promise = promise.then(() => new Promise(resolve => { + Util.readStorage(filename, fileContents => { + console.log(filename + " => " + fileContents); + let mapNumber = filename.match(/\d+/)[0]; // figure out what map number we are + let mapInfo; + try { + mapInfo = JSON.parse(fileContents); + } catch (e) { + console.error(e); + } + loadedMaps[mapNumber] = mapInfo; + if (mapInfo!==undefined) { + let latlon = L.latLng(mapInfo.lat, mapInfo.lon); + mapsLoadedContainer.innerHTML += ` +
+
+
+
+
+
+

Map ${mapNumber}

+

${mapInfo.w*mapInfo.h} Tiles (${((mapInfo.imgx*mapInfo.imgy)>>11).toFixed(0)}k)

+
+
+ +
+
+ `; + let map = L.map(`tile-map-${mapNumber}`); + L.tileLayer(PREVIEWTILELAYER, { + maxZoom: 18 + }).addTo(map); + let marker = new L.marker(latlon).addTo(map); + map.fitBounds(latlon.toBounds(2000/*meters*/), {animation: false}); + } + resolve(); + }); + })); + }); + promise = promise.then(() => new Promise(resolve => { + if (!loadedMaps.length) { + mapsLoadedContainer.innerHTML += ` +
+
+
+
+
+
+

No Maps Loaded

+
+
+
+
+ `; + } + mapsLoadedContainer.innerHTML += ` +
+
+
+
+
+
+
+
+ +
+
+ `; + Util.hideModal(); + })); + }); + } + + function onMapDelete(mapNumber) { + console.log("delete", mapNumber); + Util.showModal(`Erasing map ${mapNumber}...`); + Util.eraseStorage(`openstmap.${mapNumber}.json`, function() { + Util.eraseStorage(`openstmap.${mapNumber}.img`, function() { + Util.hideModal(); + showLoadedMaps(); + }); + }); + } + + function showMap() { + document.getElementById("mapsLoadedContainer").style.display = "none"; + document.getElementById("mapContainer").style.display = ""; + document.getElementById("maptiles").style.display="none"; + document.getElementById("uploadbuttons").style.display="none"; + } + + // ----------------------------------------------------- + var mapFiles = []; + + // convert canvas into an actual tiled image file + function tilesLoaded(ctx, width, height, mapImageFile) { var options = { compression:false, output:"raw", mode:"web" @@ -166,12 +298,17 @@ TODO: } } return [{ - name:"openstmap.0.img", + name:mapImageFile, content:tiledImage }]; } document.getElementById("getmap").addEventListener("click", function() { + + var MAPTILES = parseInt(document.getElementById("mapSize").value); + var MAPSIZE = TILESIZE*MAPTILES; /// Size of map we download to Bangle in pixels + var OSMTILECOUNT = (Math.ceil((MAPSIZE+TILESIZE) / OSMTILESIZE)+1); // how many tiles do we download from OSM in each direction + var zoom = map.getZoom(); var centerlatlon = map.getBounds().getCenter(); var center = map.project(centerlatlon, zoom).divideBy(OSMTILESIZE); @@ -242,8 +379,11 @@ TODO: Promise.all(tileGetters).then(() => { document.getElementById("uploadbuttons").style.display=""; - mapFiles = tilesLoaded(ctx, canvas.width, canvas.height); - mapFiles.unshift({name:"openstmap.0.json",content:JSON.stringify({ + var mapNumber = 0; + while (loadedMaps[mapNumber]) mapNumber++; + let mapImageFile = `openstmap.${mapNumber}.img`; + mapFiles = tilesLoaded(ctx, canvas.width, canvas.height, mapImageFile); + mapFiles.unshift({name:`openstmap.${mapNumber}.json`,content:JSON.stringify({ imgx : canvas.width, imgy : canvas.height, tilesize : TILESIZE, @@ -252,21 +392,31 @@ TODO: lon : centerlatlon.lng, w : Math.round(canvas.width / TILESIZE), // width in tiles h : Math.round(canvas.height / TILESIZE), // height in tiles - fn : "openstmap.0.img" + fn : mapImageFile })}); console.log(mapFiles); }); }); document.getElementById("upload").addEventListener("click", function() { - sendCustomizedApp({ - storage:mapFiles + Util.showModal("Uploading..."); + let promise = Promise.resolve(); + mapFiles.forEach(file => { + promise = promise.then(function() { + return new Promise(resolve => { + Util.writeStorage(file.name, file.content, resolve); + }); + }); + }); + promise.then(function() { + Util.hideModal(); + console.log("Upload Complete"); + showLoadedMaps(); }); }); document.getElementById("cancel").addEventListener("click", function() { - document.getElementById("maptiles").style.display="none"; - document.getElementById("uploadbuttons").style.display="none"; + showMap(); }); diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 32093f70e..09b4c68e3 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,17 +2,20 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.13", + "version": "0.14", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", + "readme": "README.md", "icon": "app.png", "tags": "outdoors,gps,osm", "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url":"screenshot.png"}], - "custom": "custom.html", - "customConnect": true, + "interface": "interface.html", "storage": [ {"name":"openstmap","url":"openstmap.js"}, {"name":"openstmap.app.js","url":"app.js"}, {"name":"openstmap.img","url":"app-icon.js","evaluate":true} + ], "data": [ + {"wildcard":"openstmap.*.json"}, + {"wildcard":"openstmap.*.img"} ] } diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index 2bd7d2e2e..692344357 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -20,32 +20,59 @@ function center() { m.draw(); } +// you can even change the scale - eg 'm/scale *= 2' + */ -var map = require("Storage").readJSON("openstmap.0.json"); -map.center = Bangle.project({lat:map.lat,lon:map.lon}); -exports.map = map; -exports.lat = map.lat; // actual position of middle of screen -exports.lon = map.lon; // actual position of middle of screen var m = exports; +m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{ + let map = require("Storage").readJSON(f); + map.center = Bangle.project({lat:map.lat,lon:map.lon}); + return map; +}); +// we base our start position on the middle of the first map +m.map = m.maps[0]; +m.scale = m.map.scale; // current scale (based on first map) +m.lat = m.map.lat; // position of middle of screen +m.lon = m.map.lon; // position of middle of screen exports.draw = function() { - var img = require("Storage").read(map.fn); var cx = g.getWidth()/2; var cy = g.getHeight()/2; var p = Bangle.project({lat:m.lat,lon:m.lon}); - var ix = (p.x-map.center.x)/map.scale + (map.imgx/2) - cx; - var iy = (map.center.y-p.y)/map.scale + (map.imgy/2) - cy; - //console.log(ix,iy); - var tx = 0|(ix/map.tilesize); - var ty = 0|(iy/map.tilesize); - var ox = (tx*map.tilesize)-ix; - var oy = (ty*map.tilesize)-iy; - for (var x=ox,ttx=tx;x=0 && ttx=0 && tty { + var d = map.scale/m.scale; + var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx; + var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy; + var o = {}; + var s = map.tilesize; + if (d!=1) { // if the two are different, add scaling + s *= d; + o.scale = d; } + //console.log(ix,iy); + var tx = 0|(ix/s); + var ty = 0|(iy/s); + var ox = (tx*s)-ix; + var oy = (ty*s)-iy; + var img = require("Storage").read(map.fn); + // fix out of range so we don't have to iterate over them + if (tx<0) { + ox+=s*-tx; + tx=0; + } + if (ty<0) { + oy+=s*-ty; + ty=0; + } + var mx = g.getWidth(); + var my = g.getHeight(); + for (var x=ox,ttx=tx; x Date: Fri, 25 Nov 2022 16:08:26 +0000 Subject: [PATCH 14/46] agenda 0.08: Fix error in clkinfo (didn't require Storage & locale) --- apps/agenda/ChangeLog | 3 ++- apps/agenda/agenda.clkinfo.js | 48 +++++++++++++++++------------------ apps/agenda/metadata.json | 2 +- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index 0a7916810..bbde4d80b 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -4,4 +4,5 @@ 0.04: Added awareness of allDay field 0.05: Displaying calendar colour and name 0.06: Added clkinfo for clocks. -0.07: Clkinfo improvements. \ No newline at end of file +0.07: Clkinfo improvements. +0.08: Fix error in clkinfo (didn't require Storage & locale) diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index 6c2ddb3da..84da715b9 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -1,29 +1,29 @@ (function() { - var agendaItems = { - name: "Agenda", - img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="), - items: [] - }; + var agendaItems = { + name: "Agenda", + img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="), + items: [] + }; + var locale = require("locale"); + var now = new Date(); + var agenda = require("Storage").readJSON("android.calendar.json") + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); - var now = new Date(); - var agenda = storage.readJSON("android.calendar.json") - .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) - .sort((a,b)=>a.timestamp - b.timestamp); + agenda.forEach((entry, i) => { - agenda.forEach((entry, i) => { + var title = entry.title.slice(0,18); + var date = new Date(entry.timestamp*1000); + var dateStr = locale.date(date).replace(/\d\d\d\d/,""); + dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; - var title = entry.title.slice(0,18); - var date = new Date(entry.timestamp*1000); - var dateStr = locale.date(date).replace(/\d\d\d\d/,""); - dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; + agendaItems.items.push({ + name: null, + get: () => ({ text: title + "\n" + dateStr, img: null}), + show: function() { agendaItems.items[i].emit("redraw"); }, + hide: function () {} + }); + }); - agendaItems.items.push({ - name: null, - get: () => ({ text: title + "\n" + dateStr, img: null}), - show: function() { agendaItems.items[i].emit("redraw"); }, - hide: function () {} - }); - }); - - return agendaItems; -}) \ No newline at end of file + return agendaItems; +}) diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 4436e143c..7e49e3f96 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -1,7 +1,7 @@ { "id": "agenda", "name": "Agenda", - "version": "0.07", + "version": "0.08", "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], From e5894c36af66a5e024d31525f202c4526afea1f9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 16:08:40 +0000 Subject: [PATCH 15/46] oops - forgotten commit --- apps/openstmap/ChangeLog | 1 + apps/openstmap/app.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 4b8dd108c..7f22014ae 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -14,3 +14,4 @@ 0.13: Use a single image file with 'frames' of data (drastically reduces file count, possibility of >1 map on device) 0.14: Added ability to upload multiple sets of map tiles Support for zooming in on map + Satellite count moved to widget bar to leave more room for the map diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 99362d97b..9df4fa83f 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -11,11 +11,11 @@ function redraw() { m.draw(); drawMarker(); if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { - g.flip().setColor("#f00"); // force immediate draw on double-buffered screens - track will update later + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later WIDGETS["gpsrec"].plotTrack(m); } if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { - g.flip().setColor("#f00"); // force immediate draw on double-buffered screens - track will update later + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later WIDGETS["recorder"].plotTrack(m); } g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); From 902db2a19a5a9a57112d497cc77213ff11ee3f55 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 16:08:52 +0000 Subject: [PATCH 16/46] recorder 0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap --- apps/recorder/ChangeLog | 1 + apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 70 ++++++++++++++++++++++--------------- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 1941a435b..2f6ec84f6 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -21,3 +21,4 @@ 0.15: Show distance more accurately in conjunction with new locale app (fix #1523) 0.16: Ability to append to existing track (fix #1712) 0.17: Use default Bangle formatter for booleans +0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index a7eb09cd5..9f8110cee 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.17", + "version": "0.18", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index cb42ac8ab..070675e05 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -1,10 +1,10 @@ -(() => { - var storageFile; // file for GPS track - var entriesWritten = 0; - var activeRecorders = []; - var writeInterval; +{ + let storageFile; // file for GPS track + let entriesWritten = 0; + let activeRecorders = []; + let writeInterval; - function loadSettings() { + let loadSettings = function() { var settings = require("Storage").readJSON("recorder.json",1)||{}; settings.period = settings.period||10; if (!settings.file || !settings.file.startsWith("recorder.log")) @@ -12,12 +12,12 @@ return settings; } - function updateSettings(settings) { + let updateSettings = function(settings) { require("Storage").writeJSON("recorder.json", settings); if (WIDGETS["recorder"]) WIDGETS["recorder"].reload(); } - function getRecorders() { + let getRecorders = function() { var recorders = { gps:function() { var lat = 0; @@ -159,7 +159,7 @@ return recorders; } - function writeLog() { + let writeLog = function() { entriesWritten++; WIDGETS["recorder"].draw(); try { @@ -178,7 +178,7 @@ } // Called by the GPS app to reload settings and decide what to do - function reload() { + let reload = function() { var settings = loadSettings(); if (writeInterval) clearInterval(writeInterval); writeInterval = undefined; @@ -224,7 +224,7 @@ // add the widget WIDGETS["recorder"]={area:"tl",width:0,draw:function() { if (!writeInterval) return; - g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); + g.reset().drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); activeRecorders.forEach((recorder,i)=>{ recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12); }); @@ -265,24 +265,38 @@ updateSettings(settings); WIDGETS["recorder"].reload(); return Promise.resolve(settings.recording); - }/*,plotTrack:function(m) { // m=instance of openstmap module - // FIXME - add track plotting - // if we're here, settings was already loaded - var f = require("Storage").open(settings.file,"r"); - var l = f.readLine(f); - if (l===undefined) return; - var c = l.split(","); - var mp = m.latLonToXY(+c[1], +c[2]); - g.moveTo(mp.x,mp.y); - l = f.readLine(f); - while(l!==undefined) { - c = l.split(","); - mp = m.latLonToXY(+c[1], +c[2]); - g.lineTo(mp.x,mp.y); - g.fillCircle(mp.x,mp.y,2); // make the track more visible + },plotTrack:function(m) { // m=instance of openstmap module + // Plots the current track in the currently set color + if (!activeRecorders) return; // not recording & not recording GPS + var settings = loadSettings(); + // keep function to draw track in RAM + function plot(g) { "ram"; + var f = require("Storage").open(settings.file,"r"); + var l = f.readLine(); + if (l===undefined) return; // empty file? + var mp, c = l.split(","); + var la=c.indexOf("Latitude"),lo=c.indexOf("Longitude"); + l = f.readLine(); + while (l && !c[la]) { + c = l.split(","); + l = f.readLine(f); + } + if (l===undefined) return; // empty file? + mp = m.latLonToXY(+c[la], +c[lo]); + g.moveTo(mp.x,mp.y); l = f.readLine(f); + var n = 200; // only plot first 200 points to keep things fast(ish) + while(l && n--) { + c = l.split(","); + if (c[la]) { + mp = m.latLonToXY(+c[la], +c[lo]); + g.lineTo(mp.x,mp.y); + } + l = f.readLine(f); + } } - }*/}; + plot(g); + }}; // load settings, set correct widget width reload(); -})() +} From 602689ed28fbe8866fae44b176a41307a137c11c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 16:26:38 +0000 Subject: [PATCH 17/46] Fix clkinfo icon --- apps/agenda/ChangeLog | 1 + apps/agenda/agenda.clkinfo.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index bbde4d80b..7f749ff25 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -6,3 +6,4 @@ 0.06: Added clkinfo for clocks. 0.07: Clkinfo improvements. 0.08: Fix error in clkinfo (didn't require Storage & locale) + Fix clkinfo icon diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index 84da715b9..baa8b9516 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -1,7 +1,7 @@ (function() { var agendaItems = { name: "Agenda", - img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="), + img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), items: [] }; var locale = require("locale"); @@ -12,13 +12,13 @@ agenda.forEach((entry, i) => { - var title = entry.title.slice(0,18); + var title = entry.title.slice(0,12); var date = new Date(entry.timestamp*1000); var dateStr = locale.date(date).replace(/\d\d\d\d/,""); dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; agendaItems.items.push({ - name: null, + name: "Agenda "+i, get: () => ({ text: title + "\n" + dateStr, img: null}), show: function() { agendaItems.items[i].emit("redraw"); }, hide: function () {} From 7bdafa281aa4309a49291ec9cb2e08728ba06aa1 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 16:27:42 +0000 Subject: [PATCH 18/46] clock_info images now optional --- apps/slopeclockpp/ChangeLog | 1 + apps/slopeclockpp/app.js | 10 +++++----- apps/slopeclockpp/metadata.json | 2 +- modules/clock_info.js | 15 ++++++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/slopeclockpp/ChangeLog b/apps/slopeclockpp/ChangeLog index 1d28ea553..b5e455f78 100644 --- a/apps/slopeclockpp/ChangeLog +++ b/apps/slopeclockpp/ChangeLog @@ -4,3 +4,4 @@ 0.04: Changed to use clock_info for displayed data Made fonts smaller to avoid overlap when (eg) 22:00 Allowed black/white background (as that can look nice too) +0.05: Images in clkinfo are optional now diff --git a/apps/slopeclockpp/app.js b/apps/slopeclockpp/app.js index 99ef7272b..0f2416a4f 100644 --- a/apps/slopeclockpp/app.js +++ b/apps/slopeclockpp/app.js @@ -121,18 +121,18 @@ let animate = function(isIn, callback) { // clock info menus (scroll up/down for info) let clockInfoDraw = (itm, info, options) => { - let texty = options.y+26; + let texty = options.y+41; g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty, options.x+options.w-2, texty+15); if (options.focus) g.setColor(options.hl); if (options.x < g.getWidth()/2) { // left align let x = options.x+2; - g.clearRect(x, options.y, x+23, options.y+23).drawImage(info.img, x, options.y); - g.setFontAlign(-1,-1).drawString(info.text, x,texty); + if (info.img) g.clearRect(x, options.y, x+23, options.y+23).drawImage(info.img, x, options.y); + g.setFontAlign(-1,1).drawString(info.text, x,texty); } else { // right align let x = options.x+options.w-3; - g.clearRect(x-23, options.y, x, options.y+23).drawImage(info.img, x-23, options.y); - g.setFontAlign(1,-1).drawString(info.text, x,texty); + if (info.img) g.clearRect(x-23, options.y, x, options.y+23).drawImage(info.img, x-23, options.y); + g.setFontAlign(1,1).drawString(info.text, x,texty); } }; let clockInfoItems = require("clock_info").load(); diff --git a/apps/slopeclockpp/metadata.json b/apps/slopeclockpp/metadata.json index 3100d8b50..b419ef898 100644 --- a/apps/slopeclockpp/metadata.json +++ b/apps/slopeclockpp/metadata.json @@ -1,6 +1,6 @@ { "id": "slopeclockpp", "name": "Slope Clock ++", - "version":"0.04", + "version":"0.05", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/modules/clock_info.js b/modules/clock_info.js index 6f26f5c4b..cdd3c7520 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -15,7 +15,7 @@ Note that each item is an object with: { 'text' // the text to display for this item - 'img' // a 24x24px image to display for this item + 'img' // optional: a 24x24px image to display for this item 'v' // (if hasRange==true) a numerical value 'min','max' // (if hasRange==true) a minimum and maximum numerical value (if this were to be displayed as a guage) } @@ -36,7 +36,8 @@ example.clkinfo.js : items: [ { name : "Item1", get : () => ({ text : "TextOfItem1", v : 10, min : 0, max : 100, - img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), + img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") + }), show : () => {}, hide : () => {} // run : () => {} optional (called when tapped) @@ -139,8 +140,8 @@ let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { g.reset().clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused var midx = options.x+options.w/2; - g.drawImage(info.img, midx-12,options.y+4); - g.setFont("6x8:2").setFontAlign(0,0).drawString(info.text, midx,options.y+36); + if (info.img) g.drawImage(info.img, midx-12,options.y+4); + g.setFont("6x8:2").setFontAlign(0,1).drawString(info.text, midx,options.y+44); } }); // then when clock 'unloads': @@ -166,8 +167,8 @@ let clockInfoDraw = (itm, info, options) => { g.reset().setBgColor(options.bg).setColor(options.fg).clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) var midx = options.x+options.w/2; - g.drawImage(info.img, midx-12,options.y); - g.setFont("6x15").setFontAlign(0,-1).drawString(info.text, midx,options.y+26); + if (info.img) g.drawImage(info.img, midx-12,options.y); + g.setFont("6x15").setFontAlign(0,1).drawString(info.text, midx,options.y+41); }; let clockInfoItems = require("clock_info").load(); let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg }); @@ -225,7 +226,7 @@ exports.addInteractive = function(menu, options) { } else if (lr) { if (menu.length==1) return; // 1 item - can't move oldMenuItem = menu[options.menuA].items[options.menuB]; - options.menuA += ud; + options.menuA += lr; if (options.menuA<0) options.menuA = menu.length-1; if (options.menuA>=menu.length) options.menuA = 0; options.menuB = 0; From c0a89a375e38201e5c3c4274d97b2be0db527eb3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 16:36:07 +0000 Subject: [PATCH 19/46] oops --- apps/slopeclockpp/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slopeclockpp/app.js b/apps/slopeclockpp/app.js index 0f2416a4f..013c25733 100644 --- a/apps/slopeclockpp/app.js +++ b/apps/slopeclockpp/app.js @@ -122,7 +122,7 @@ let animate = function(isIn, callback) { // clock info menus (scroll up/down for info) let clockInfoDraw = (itm, info, options) => { let texty = options.y+41; - g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty, options.x+options.w-2, texty+15); + g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty); if (options.focus) g.setColor(options.hl); if (options.x < g.getWidth()/2) { // left align From fb0dcb82cd99e7a44b4e95e80d59a31c7ea2bee4 Mon Sep 17 00:00:00 2001 From: Kedlub Date: Fri, 25 Nov 2022 17:53:34 +0100 Subject: [PATCH 20/46] qcenter: Update description --- apps/qcenter/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/qcenter/metadata.json b/apps/qcenter/metadata.json index abef0fc36..96d8fa9f7 100644 --- a/apps/qcenter/metadata.json +++ b/apps/qcenter/metadata.json @@ -3,7 +3,7 @@ "name": "Quick Center", "shortName": "QCenter", "version": "0.01", - "description": "Shortcut app for quickly running your favorite apps", + "description": "An app for quickly launching your favourite apps, inspired by the control centres of other watches.", "icon": "app.png", "tags": "", "supports": ["BANGLEJS2"], From a9b12f0dbac0841509dfe525976d69cfa4d5ee9b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 25 Nov 2022 17:05:30 +0000 Subject: [PATCH 21/46] misc fixes/tweaks for map rendering --- apps/openstmap/interface.html | 1 + apps/recorder/widget.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openstmap/interface.html b/apps/openstmap/interface.html index 81406d807..e44474d63 100644 --- a/apps/openstmap/interface.html +++ b/apps/openstmap/interface.html @@ -250,6 +250,7 @@ TODO: options = { compression:false, output:"raw", mode:"3bit", + diffusion:"bayer2" }; /* If in 3 bit mode, go through all the data beforehand and turn the saturation up to maximum, so when thresholded it diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 070675e05..9d61cfee5 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -267,7 +267,7 @@ return Promise.resolve(settings.recording); },plotTrack:function(m) { // m=instance of openstmap module // Plots the current track in the currently set color - if (!activeRecorders) return; // not recording & not recording GPS + if (!activeRecorders.length) return; // not recording var settings = loadSettings(); // keep function to draw track in RAM function plot(g) { "ram"; @@ -276,6 +276,7 @@ if (l===undefined) return; // empty file? var mp, c = l.split(","); var la=c.indexOf("Latitude"),lo=c.indexOf("Longitude"); + if (la<0 || lb<0) return; // no GPS! l = f.readLine(); while (l && !c[la]) { c = l.split(","); From c8ee05a4b60fb573b20ce886efdbda405914e43c Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 11 Aug 2022 13:07:28 +0200 Subject: [PATCH 22/46] clkinfo: added sched and ranges in weather --- apps/sched/ChangeLog | 1 + apps/sched/clkinfo.js | 71 ++++++++++++++++++++++++++++++++++++++++ apps/sched/metadata.json | 5 +-- apps/weather/clkinfo.js | 16 +++++---- modules/clock_info.js | 11 ++++++- 5 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 apps/sched/clkinfo.js diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index f23cf93cb..3bb321ccf 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -14,3 +14,4 @@ Improve timer message using formatDuration Fix wrong fallback for buzz pattern 0.13: Ask to delete a timer after stopping it +0.14: Added clkinfo here diff --git a/apps/sched/clkinfo.js b/apps/sched/clkinfo.js new file mode 100644 index 000000000..578089c1e --- /dev/null +++ b/apps/sched/clkinfo.js @@ -0,0 +1,71 @@ +(function() { + const alarm = require('sched'); + const iconAlarmOn = atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA=="); + const iconAlarmOff = + atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="); + //atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="); + + const iconTimerOn = + atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="); + //atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="); + const iconTimerOff = + atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="); + //atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="); + + //from 0 to max, the higher the closer to fire (as in a progress bar) + function getAlarmValue(a){ + let min = Math.round(alarm.getTimeToAlarm(a)/(60*1000)); + if(!min) return 0; //not active or more than a day + return getAlarmMax(a)-min; + } + + function getAlarmMax(a) { + if(a.timer) + return Math.round(a.timer/(60*1000)); + //minutes cannot be more than a full day + return 1440; + } + + function getAlarmIcon(a) { + if(a.on) { + if(a.timer) return iconTimerOn; + return iconAlarmOn; + } else { + if(a.timer) return iconTimerOff; + return iconAlarmOff; + } + } + + function getAlarmText(a){ + if(a.timer) { + if(!a.on) return "off"; + let time = Math.round(alarm.getTimeToAlarm(a)/(60*1000)); + if(time > 60) + time = Math.round(time / 60) + "h"; + else + time += "m"; + return time; + } + return require("time_utils").formatTime(a.t); + } + + var img = iconAlarmOn; + //get only alarms not created by other apps + var alarmItems = { + name: "Alarms", + img: img, + items: alarm.getAlarms().filter(a=>!a.appid) + .sort((a,b)=>alarm.getTimeToAlarm(a)-alarm.getTimeToAlarm(b)) + //.sort((a,b)=>getAlarmMinutes(a)-getAlarmMinutes(b)) + .map((a, i)=>({ + name: null, + get: () => ({ text: getAlarmText(a), img: getAlarmIcon(a), + hasRange: true, v: getAlarmValue(a), min:0, max:getAlarmMax(a)}), + show: function() { alarmItems.items[i].emit("redraw"); }, + hide: function () {}, + run: function() { } + })), + }; + + return alarmItems; +}) diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 163d2f552..4b38ee653 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.13", + "version": "0.14", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", @@ -13,7 +13,8 @@ {"name":"sched.js","url":"sched.js"}, {"name":"sched.img","url":"app-icon.js","evaluate":true}, {"name":"sched","url":"lib.js"}, - {"name":"sched.settings.js","url":"settings.js"} + {"name":"sched.settings.js","url":"settings.js"}, + {"name":"sched.clkinfo.js","url":"clkinfo.js"} ], "data": [{"name":"sched.json"}, {"name":"sched.settings.json"}] } diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js index caaebf273..2d1966c82 100644 --- a/apps/weather/clkinfo.js +++ b/apps/weather/clkinfo.js @@ -5,34 +5,38 @@ wind: "?", }; - var weatherJson = storage.readJSON('weather.json'); + var weatherJson = require("Storage").readJSON('weather.json'); if(weatherJson !== undefined && weatherJson.weather !== undefined){ weather = weatherJson.weather; - weather.temp = locale.temp(weather.temp-273.15); + weather.temp = require("locale").temp(weather.temp-273.15); weather.hum = weather.hum + "%"; - weather.wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + weather.wind = require("locale").speed(weather.wind).match(/^(\D*\d*)(.*)$/); weather.wind = Math.round(weather.wind[1]) + "kph"; } + //FIXME ranges are somehow arbitrary var weatherItems = { name: "Weather", img: atob("GBiBAf+///u5//n7//8f/9wHP8gDf/gB//AB/7AH/5AcP/AQH/DwD/uAD84AD/4AA/wAAfAAAfAAAfAAAfgAA/////+bP/+zf/+zfw=="), items: [ { name: "temperature", - get: () => ({ text: weather.temp, img: atob("GBiBAAA8AAB+AADnAADDAADDAADDAADDAADDAADbAADbAADbAADbAADbAADbAAHbgAGZgAM8wAN+wAN+wAM8wAGZgAHDgAD/AAA8AA==")}), + get: () => ({ text: weather.temp, img: atob("GBiBAAA8AAB+AADnAADDAADDAADDAADDAADDAADbAADbAADbAADbAADbAADbAAHbgAGZgAM8wAN+wAN+wAM8wAGZgAHDgAD/AAA8AA=="), + hasRange: true, v: parseInt(weather.temp), min: -30, max: 55}), show: function() { weatherItems.items[0].emit("redraw"); }, hide: function () {} }, { name: "humidity", - get: () => ({ text: weather.hum, img: atob("GBiBAAAEAAAMAAAOAAAfAAAfAAA/gAA/gAI/gAY/AAcfAA+AQA+A4B/A4D/B8D/h+D/j+H/n/D/n/D/n/B/H/A+H/AAH/AAD+AAA8A==")}), + get: () => ({ text: weather.hum, img: atob("GBiBAAAEAAAMAAAOAAAfAAAfAAA/gAA/gAI/gAY/AAcfAA+AQA+A4B/A4D/B8D/h+D/j+H/n/D/n/D/n/B/H/A+H/AAH/AAD+AAA8A=="), + hasRange: true, v: parseInt(weather.hum), min: 0, max: 100}), show: function() { weatherItems.items[1].emit("redraw"); }, hide: function () {} }, { name: "wind", - get: () => ({ text: weather.wind, img: atob("GBiBAAHgAAPwAAYYAAwYAAwMfAAY/gAZh3/xg//hgwAAAwAABg///g//+AAAAAAAAP//wH//4AAAMAAAMAAYMAAYMAAMcAAP4AADwA==")}), + get: () => ({ text: weather.wind, img: atob("GBiBAAHgAAPwAAYYAAwYAAwMfAAY/gAZh3/xg//hgwAAAwAABg///g//+AAAAAAAAP//wH//4AAAMAAAMAAYMAAYMAAMcAAP4AADwA=="), + hasRange: true, v: parseInt(weather.wind), min: 0, max: 118}), show: function() { weatherItems.items[2].emit("redraw"); }, hide: function () {} }, diff --git a/modules/clock_info.js b/modules/clock_info.js index cdd3c7520..d2f1dea7c 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -48,6 +48,15 @@ example.clkinfo.js : */ +let storage = require("Storage"); +let stepGoal = undefined; +// Load step goal from health app and pedometer widget +let d = storage.readJSON("health.json", true) || {}; +stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined; +if (stepGoal == undefined) { + d = storage.readJSON("wpedom.json", true) || {}; + stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; +} exports.load = function() { // info used for drawing... @@ -81,7 +90,7 @@ exports.load = function() { { name : "Steps", hasRange : true, get : () => { let v = Bangle.getHealthStatus("day").steps; return { - text : v, v : v, min : 0, max : 10000, // TODO: do we have a target step amount anywhere? + text : v, v : v, min : 0, max : stepGoal, img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }}, show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); }, From 4ff8e9a52cefa526a52c81cf4f2599c46d01b60f Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Fri, 25 Nov 2022 08:59:07 +0100 Subject: [PATCH 23/46] Fixed sched clkinfo icons and sorting --- apps/sched/clkinfo.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/sched/clkinfo.js b/apps/sched/clkinfo.js index 578089c1e..52f553988 100644 --- a/apps/sched/clkinfo.js +++ b/apps/sched/clkinfo.js @@ -1,16 +1,9 @@ (function() { const alarm = require('sched'); const iconAlarmOn = atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA=="); - const iconAlarmOff = - atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="); - //atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="); - - const iconTimerOn = - atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="); - //atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="); - const iconTimerOff = - atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="); - //atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="); + const iconAlarmOff = atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/nAB/mAB/geB/5/g/5tg/zAwfzhwPzhwHzAwB5tgAB/gAAeA=="); + const iconTimerOn = atob("GBiBAAAAAAAAAAAAAAf/4Af/4AGBgAGBgAGBgAD/AAD/AAB+AAA8AAA8AAB+AADnAADDAAGBgAGBgAGBgAf/4Af/4AAAAAAAAAAAAA=="); + const iconTimerOff = atob("GBiBAAAAAAAAAAAAAAf/4Af/4AGBgAGBgAGBgAD/AAD/AAB+AAA8AAA8AAB+AADkeADB/gGBtgGDAwGDhwfzhwfzAwABtgAB/gAAeA=="); //from 0 to max, the higher the closer to fire (as in a progress bar) function getAlarmValue(a){ @@ -49,14 +42,21 @@ return require("time_utils").formatTime(a.t); } + //workaround for sorting undefined values + function getAlarmOrder(a) { + let val = alarm.getTimeToAlarm(a); + if(typeof val == "undefined") return 86400*1000; + return val; + } + var img = iconAlarmOn; //get only alarms not created by other apps var alarmItems = { name: "Alarms", img: img, items: alarm.getAlarms().filter(a=>!a.appid) - .sort((a,b)=>alarm.getTimeToAlarm(a)-alarm.getTimeToAlarm(b)) - //.sort((a,b)=>getAlarmMinutes(a)-getAlarmMinutes(b)) + //.sort((a,b)=>alarm.getTimeToAlarm(a)-alarm.getTimeToAlarm(b)) + .sort((a,b)=>getAlarmOrder(a)-getAlarmOrder(b)) .map((a, i)=>({ name: null, get: () => ({ text: getAlarmText(a), img: getAlarmIcon(a), From 18d91bf5f95d02014c52a40a53a2c79d06bfd81a Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Fri, 25 Nov 2022 18:28:20 +0100 Subject: [PATCH 24/46] Added short field and general clkinfo improvements --- apps/agenda/ChangeLog | 1 + apps/agenda/agenda.clkinfo.js | 29 +++++++++++++++++++---------- apps/sched/ChangeLog | 2 +- apps/sched/clkinfo.js | 4 +++- apps/weather/ChangeLog | 3 ++- apps/weather/clkinfo.js | 9 ++++++--- apps/weather/metadata.json | 2 +- modules/clock_info.js | 3 ++- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index 7f749ff25..6384fe887 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -7,3 +7,4 @@ 0.07: Clkinfo improvements. 0.08: Fix error in clkinfo (didn't require Storage & locale) Fix clkinfo icon +0.09: Clkinfo new fields and filter past events. diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index baa8b9516..43b7cf57e 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -1,25 +1,34 @@ (function() { var agendaItems = { - name: "Agenda", - img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), - items: [] - }; + name: "Agenda", + img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), + dynamic: true, + items: [] + }; + var storage = require("Storage"); var locale = require("locale"); var now = new Date(); - var agenda = require("Storage").readJSON("android.calendar.json") - .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) - .sort((a,b)=>a.timestamp - b.timestamp); + var agenda = (storage.readJSON("android.calendar.json",true)||[]) + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); + var settings = storage.readJSON("agenda.settings.json",true)||{}; agenda.forEach((entry, i) => { - var title = entry.title.slice(0,12); var date = new Date(entry.timestamp*1000); var dateStr = locale.date(date).replace(/\d\d\d\d/,""); + var dateStrToday = locale.date(new Date()).replace(/\d\d\d\d/,""); + var timeStr = locale.time(date); + //maybe not the most efficient.. + var shortTxt = (dateStrToday == dateStr) ? timeStr : dateStr; dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; + if(!settings.pastEvents && entry.timestamp + entry.durationInSeconds < (new Date())/1000) + return; + agendaItems.items.push({ - name: "Agenda "+i, - get: () => ({ text: title + "\n" + dateStr, img: null}), + name: null, + get: () => ({ text: title + "\n" + dateStr, short: shortTxt, img: null}), show: function() { agendaItems.items[i].emit("redraw"); }, hide: function () {} }); diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 3bb321ccf..f2dd54338 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -14,4 +14,4 @@ Improve timer message using formatDuration Fix wrong fallback for buzz pattern 0.13: Ask to delete a timer after stopping it -0.14: Added clkinfo here +0.14: Added clkinfo for alarms and timers diff --git a/apps/sched/clkinfo.js b/apps/sched/clkinfo.js index 52f553988..71992dbb8 100644 --- a/apps/sched/clkinfo.js +++ b/apps/sched/clkinfo.js @@ -54,13 +54,15 @@ var alarmItems = { name: "Alarms", img: img, + dynamic: true, items: alarm.getAlarms().filter(a=>!a.appid) //.sort((a,b)=>alarm.getTimeToAlarm(a)-alarm.getTimeToAlarm(b)) .sort((a,b)=>getAlarmOrder(a)-getAlarmOrder(b)) .map((a, i)=>({ name: null, + hasRange: true, get: () => ({ text: getAlarmText(a), img: getAlarmIcon(a), - hasRange: true, v: getAlarmValue(a), min:0, max:getAlarmMax(a)}), + v: getAlarmValue(a), min:0, max:getAlarmMax(a)}), show: function() { alarmItems.items[i].emit("redraw"); }, hide: function () {}, run: function() { } diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index da28d8d5a..b11ed24ff 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -13,4 +13,5 @@ 0.14: Use weather condition code for icon selection 0.15: Fix widget icon 0.16: Don't mark app as clock -0.17: Added clkinfo for clocks. \ No newline at end of file +0.17: Added clkinfo for clocks. +0.18: Added hasRange to clkinfo. diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js index 2d1966c82..6191c6dbe 100644 --- a/apps/weather/clkinfo.js +++ b/apps/weather/clkinfo.js @@ -21,22 +21,25 @@ items: [ { name: "temperature", + hasRange : true, get: () => ({ text: weather.temp, img: atob("GBiBAAA8AAB+AADnAADDAADDAADDAADDAADDAADbAADbAADbAADbAADbAADbAAHbgAGZgAM8wAN+wAN+wAM8wAGZgAHDgAD/AAA8AA=="), - hasRange: true, v: parseInt(weather.temp), min: -30, max: 55}), + v: parseInt(weather.temp), min: -30, max: 55}), show: function() { weatherItems.items[0].emit("redraw"); }, hide: function () {} }, { name: "humidity", + hasRange : true, get: () => ({ text: weather.hum, img: atob("GBiBAAAEAAAMAAAOAAAfAAAfAAA/gAA/gAI/gAY/AAcfAA+AQA+A4B/A4D/B8D/h+D/j+H/n/D/n/D/n/B/H/A+H/AAH/AAD+AAA8A=="), - hasRange: true, v: parseInt(weather.hum), min: 0, max: 100}), + v: parseInt(weather.hum), min: 0, max: 100}), show: function() { weatherItems.items[1].emit("redraw"); }, hide: function () {} }, { name: "wind", + hasRange : true, get: () => ({ text: weather.wind, img: atob("GBiBAAHgAAPwAAYYAAwYAAwMfAAY/gAZh3/xg//hgwAAAwAABg///g//+AAAAAAAAP//wH//4AAAMAAAMAAYMAAYMAAMcAAP4AADwA=="), - hasRange: true, v: parseInt(weather.wind), min: 0, max: 118}), + v: parseInt(weather.wind), min: 0, max: 118}), show: function() { weatherItems.items[2].emit("redraw"); }, hide: function () {} }, diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json index e28a282d6..4a8751302 100644 --- a/apps/weather/metadata.json +++ b/apps/weather/metadata.json @@ -1,7 +1,7 @@ { "id": "weather", "name": "Weather", - "version": "0.17", + "version": "0.18", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/modules/clock_info.js b/modules/clock_info.js index d2f1dea7c..238888b1c 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -2,9 +2,9 @@ that can be scrolled through on the clock face. `load()` returns an array of menu objects, where each object contains a list of menu items: - * `name` : text to display and identify menu object (e.g. weather) * `img` : a 24x24px image +* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date) * `items` : menu items such as temperature, humidity, wind etc. Note that each item is an object with: @@ -15,6 +15,7 @@ Note that each item is an object with: { 'text' // the text to display for this item + 'short' : (optional) a shorter text to display for this item (at most 6 characters) 'img' // optional: a 24x24px image to display for this item 'v' // (if hasRange==true) a numerical value 'min','max' // (if hasRange==true) a minimum and maximum numerical value (if this were to be displayed as a guage) From 93c2429bf57aeb3a16c3212982a97efaab359d01 Mon Sep 17 00:00:00 2001 From: eyecreate Date: Fri, 25 Nov 2022 12:51:08 -0500 Subject: [PATCH 25/46] Add locale time support to watch --- apps/slopeclockpp/app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/slopeclockpp/app.js b/apps/slopeclockpp/app.js index 013c25733..bf719344e 100644 --- a/apps/slopeclockpp/app.js +++ b/apps/slopeclockpp/app.js @@ -51,8 +51,9 @@ let draw = function() { x = R.w / 2; y = R.y + R.h / 2 - 12; // 12 = room for date var date = new Date(); - var hourStr = date.getHours(); - var minStr = date.getMinutes().toString().padStart(2,0); + var local_time = require("locale").time(date, 1); + var hourStr = local_time.split(":")[0].trim().padStart(2,'0'); + var minStr = local_time.split(":")[1].trim().padStart(2, '0'); dateStr = require("locale").dow(date, 1).toUpperCase()+ " "+ require("locale").date(date, 0).toUpperCase(); From ebc2091c2994e8aea228da0a2e379258e74103d8 Mon Sep 17 00:00:00 2001 From: eyecreate Date: Fri, 25 Nov 2022 12:52:13 -0500 Subject: [PATCH 26/46] Update metadata --- apps/slopeclockpp/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slopeclockpp/metadata.json b/apps/slopeclockpp/metadata.json index b419ef898..bebed4b71 100644 --- a/apps/slopeclockpp/metadata.json +++ b/apps/slopeclockpp/metadata.json @@ -1,6 +1,6 @@ { "id": "slopeclockpp", "name": "Slope Clock ++", - "version":"0.05", + "version":"0.06", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], From 58fbe0455eb4ae11e038ed534a2da3b5e71e665d Mon Sep 17 00:00:00 2001 From: eyecreate Date: Fri, 25 Nov 2022 12:52:37 -0500 Subject: [PATCH 27/46] Update ChangeLog --- apps/slopeclockpp/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/slopeclockpp/ChangeLog b/apps/slopeclockpp/ChangeLog index b5e455f78..9ae4ccc96 100644 --- a/apps/slopeclockpp/ChangeLog +++ b/apps/slopeclockpp/ChangeLog @@ -5,3 +5,4 @@ Made fonts smaller to avoid overlap when (eg) 22:00 Allowed black/white background (as that can look nice too) 0.05: Images in clkinfo are optional now +0.06: Added support for locale based time From e35f3a26bb629c61b8da535a3e8e369ed1616acf Mon Sep 17 00:00:00 2001 From: Kedlub Date: Fri, 25 Nov 2022 21:37:54 +0100 Subject: [PATCH 28/46] qcenter: Add padding --- apps/qcenter/app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/qcenter/app.js b/apps/qcenter/app.js index 876d4aba9..1530cc5fb 100644 --- a/apps/qcenter/app.js +++ b/apps/qcenter/app.js @@ -81,9 +81,10 @@ var layoutContent = [ { type: "h", pad: 5, + fillx: 1, c: [ - { type: "txt", font: "8x12", scale: 2, label: E.getBattery() + "%" }, - { type: "txt", font: "8x12", scale: 2, label: "GPS: " + (Bangle.isGPSOn() ? "ON" : "OFF") }, + { type: "txt", font: "8x12", pad: 3, scale: 2, label: E.getBattery() + "%" }, + { type: "txt", font: "8x12", pad: 3, scale: 2, label: "GPS: " + (Bangle.isGPSOn() ? "ON" : "OFF") }, ], }, ]; From 21e9fc1e24b3d4d6fca04829ddfb105b6dfa0f41 Mon Sep 17 00:00:00 2001 From: "Marko.Kl.Berkenbusch@gmail.com" Date: Fri, 25 Nov 2022 20:06:29 -0500 Subject: [PATCH 29/46] Add tetris app, first commit --- apps/tetris/README.md | 8 ++ apps/tetris/app-icon.js | 1 + apps/tetris/metadata.json | 14 ++++ apps/tetris/tetris.app.js | 170 ++++++++++++++++++++++++++++++++++++++ apps/tetris/tetris.png | Bin 0 -> 492 bytes 5 files changed, 193 insertions(+) create mode 100644 apps/tetris/README.md create mode 100644 apps/tetris/app-icon.js create mode 100644 apps/tetris/metadata.json create mode 100644 apps/tetris/tetris.app.js create mode 100644 apps/tetris/tetris.png diff --git a/apps/tetris/README.md b/apps/tetris/README.md new file mode 100644 index 000000000..2c41657f4 --- /dev/null +++ b/apps/tetris/README.md @@ -0,0 +1,8 @@ +# Tetris + +Bangle version of the classic game of Tetris. + +## Controls + +Tapping the screen rotates the pieces once, swiping left, right or down moves the +piece in that direction, if possible. \ No newline at end of file diff --git a/apps/tetris/app-icon.js b/apps/tetris/app-icon.js new file mode 100644 index 000000000..b87ef84f4 --- /dev/null +++ b/apps/tetris/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+If4A/AH4A/AH4A/ABe5AA0jABwvYAIovBgABEFAQHFL7IuEL4QuFA45fcF4YuNL7i/FFwoHHL7QvFFxpfaF4wAOF/4nHF5+0AAy3SXYoHGW4QBDF4MAAIgvRFwwHHdAbqDFIQuDL6ouJL4ovDFwpfUAAoHFL4a/FFwhfTFxZfDF4ouFL6QANFopfDF/4vNjwAGF8ABFF4MAAIgvBX4IBDX4YBDL6TyFFIIuEL4QuEL4QuEL6ovDFwpfFF4YuFL6i/FFwhfEX4ouEL6YvFFwpfDF4ouFL6QvGAAwtFL4Yv/AAonHAB4vHG563CAIbuDA5i/CAIb2DA4hfJEwoHPFApZEGwpfLFyJfFFxJfMAAoHNFAa5GX54uTL4YuLL5QAVFowAIF+4A/AH4A/AH4A/AHY")) diff --git a/apps/tetris/metadata.json b/apps/tetris/metadata.json new file mode 100644 index 000000000..778e55c24 --- /dev/null +++ b/apps/tetris/metadata.json @@ -0,0 +1,14 @@ +{ "id": "tetris", + "name": "Tetris", + "shortName":"Tetris", + "version":"0.01", + "description": "Tetris", + "icon": "tetris.png", + "readme": "README.md", + "tags": "games", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"tetris.app.js","url":"app.js"}, + {"name":"tetris.img","url":"app-icon.js","evaluate":true}, + ] +} diff --git a/apps/tetris/tetris.app.js b/apps/tetris/tetris.app.js new file mode 100644 index 000000000..e24a731a9 --- /dev/null +++ b/apps/tetris/tetris.app.js @@ -0,0 +1,170 @@ +const block = Graphics.createImage(` +######## +# # # ## +## # ### +# # #### +## ##### +# ###### +######## +######## +`); +const tcols = [ {r:0, g:0, b:1}, {r:0, g:1, b:0}, {r:0, g:1, b:1}, {r:1, g:0, b:0}, {r:1, g:0, b:1}, {r:1, g:1, b:0}, {r:1, g:0.5, b:0.5} ]; +const tiles = [ + [[0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0]], + [[0, 0, 0], + [0, 1, 0], + [1, 1, 1]], + [[0, 0, 0], + [1, 0, 0], + [1, 1, 1]], + [[0, 0, 0], + [0, 0, 1], + [1, 1, 1]], + [[0, 0, 0], + [1, 1, 0], + [0, 1, 1]], + [[0, 0, 0], + [0, 1, 1], + [1, 1, 0]], + [[1, 1], + [1, 1]] +]; + +const ox = 176/2 - 5*8; +const oy = 8; + +var pf = Array(23).fill().map(()=>Array(12).fill(0)); // field is really 10x20, but adding a border for collision checks +pf[20].fill(1); +pf[21].fill(1); +pf[22].fill(1); +pf.forEach((x,i) => { pf[i][0] = 1; pf[i][11] = 1; }); + +function rotateTile(t, r) { + var nt = JSON.parse(JSON.stringify(t)); + if (t.length==2) return nt; + var s = t.length; + for (m=0; m0) + if (qClear) g.fillRect(x+8*i, y+8*j, x+8*(i+1)-1, y+8*(j+1)-1); + else g.drawImage(block, x+8*i, y+8*j); +} + +function showNext(n, r) { + var nt = rotateTile(tiles[n], r); + g.setColor(0).fillRect(176-33, 40, 176-33+33, 82); + drawTile(nt, ntn, 176-33, 40); +} + +var time = Date.now(); +var px=4, py=0; +var ctn = Math.floor(Math.random()*7); // current tile number +var ntn = Math.floor(Math.random()*7); // next tile number +var ntr = Math.floor(Math.random()*4); // next tile rotation +var ct = rotateTile(tiles[ctn], Math.floor(Math.random()*4)); // current tile (rotated) +var dropInterval = 450; +var nlines = 0; + +function redrawPF(ly) { + for (y=0; y<=ly; ++y) + for (x=1; x<11; ++x) { + c = pf[y][x]; + if (c>0) g.setColor(tcols[c-1].r, tcols[c-1].g, tcols[c-1].b).drawImage(block, ox+(x-1)*8, oy+y*8); + else g.setColor(0, 0, 0).fillRect(ox+(x-1)*8, oy+y*8, ox+x*8-1, oy+(y+1)*8-1); + } +} + +function insertAndCheck() { + for (y=0; y0) pf[py+y][px+x+1] = ctn+1; + // check for full lines + for (y=19; y>0; y--) { + var qFull = true; + for (x=1; x<11; ++x) qFull &= pf[y][x]>0; + if (qFull) { + nlines++; + dropInterval -= 5; + Bangle.buzz(30); + for (ny=y; ny>0; ny--) pf[ny] = JSON.parse(JSON.stringify(pf[ny-1])); + redrawPF(y); + g.setColor(0).fillRect(5, 30, 41, 80).setColor(1, 1, 1).drawString(nlines.toString(), 22, 50); + } + } + // spawn new tile + px = 4; py = 0; + ctn = ntn; + ntn = Math.floor(Math.random()*7); + ct = rotateTile(tiles[ctn], ntr); + ntr = Math.floor(Math.random()*4); + showNext(ntn, ntr); +} + +function moveOk(t, dx, dy) { + var ok = true; + for (y=0; y 0) ok = false; + return ok; +} + +function gameStep() { + if (Date.now()-time > dropInterval) { // drop one step + time = Date.now(); + if (moveOk(ct, 0, 1)) { + drawTile(ct, ctn, ox+px*8, oy+py*8, true); + py++; + } + else { // reached the bottom + insertAndCheck(ct, ctn, px, py); + } + drawTile(ct, ctn, ox+px*8, oy+py*8, false); + } +} + +Bangle.setUI(); +Bangle.on("touch", (e) => { + t = rotateTile(ct, 3); + if (moveOk(t, 0, 0)) { + drawTile(ct, ctn, ox+px*8, oy+py*8, true); + ct = t; + drawTile(ct, ctn, ox+px*8, oy+py*8, false); + } +}); + +Bangle.on("swipe", (x,y) => { + if (y<0) y = 0; + if (moveOk(ct, x, y)) { + drawTile(ct, ctn, ox+px*8, oy+py*8, true); + px += x; + py += y; + drawTile(ct, ctn, ox+px*8, oy+py*8, false); + } +}); + +drawBoundingBox(); +g.setColor(1, 1, 1).setFontAlign(0, 1, 0).setFont("6x15", 1).drawString("Lines", 22, 30).drawString("Next", 176-22, 30); +showNext(ntn, ntr); +g.setColor(0).fillRect(5, 30, 41, 80).setColor(1, 1, 1).drawString(nlines.toString(), 22, 50); +var gi = setInterval(gameStep, 20); diff --git a/apps/tetris/tetris.png b/apps/tetris/tetris.png new file mode 100644 index 0000000000000000000000000000000000000000..8e884eaf35c9bfa6103591255bba423228c12192 GIT binary patch literal 492 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)ISkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq8r{zDi(Vu$sZZAYL$MSD+10LMXr|#1%;Y0Kz{%egIj2 z82zm0Xkxq!^40jEr;*4RsAoLkvx=jEt=e%(V>+tPBihXXOZ> dXvob^$xN%nt>KmVl{G*O44$rjF6*2UngE)~sv`gZ literal 0 HcmV?d00001 From 5a1c701adde86a7de92e81ef8a426648b29f7218 Mon Sep 17 00:00:00 2001 From: "Marko.Kl.Berkenbusch@gmail.com" Date: Fri, 25 Nov 2022 20:24:30 -0500 Subject: [PATCH 30/46] Fix metadata typo --- apps/tetris/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tetris/metadata.json b/apps/tetris/metadata.json index 778e55c24..374f97ac8 100644 --- a/apps/tetris/metadata.json +++ b/apps/tetris/metadata.json @@ -9,6 +9,6 @@ "supports" : ["BANGLEJS2"], "storage": [ {"name":"tetris.app.js","url":"app.js"}, - {"name":"tetris.img","url":"app-icon.js","evaluate":true}, + {"name":"tetris.img","url":"app-icon.js","evaluate":true} ] } From 43fdeecdb105ceaa559d1cc66919f5306340e94a Mon Sep 17 00:00:00 2001 From: "Marko.Kl.Berkenbusch@gmail.com" Date: Fri, 25 Nov 2022 20:27:44 -0500 Subject: [PATCH 31/46] Another mistake in metadata --- apps/tetris/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tetris/metadata.json b/apps/tetris/metadata.json index 374f97ac8..5669d8953 100644 --- a/apps/tetris/metadata.json +++ b/apps/tetris/metadata.json @@ -8,7 +8,7 @@ "tags": "games", "supports" : ["BANGLEJS2"], "storage": [ - {"name":"tetris.app.js","url":"app.js"}, + {"name":"tetris.app.js","url":"tetris.app.js"}, {"name":"tetris.img","url":"app-icon.js","evaluate":true} ] } From 2a506e7421f2320ba65afcadeb9d3458a8958dd1 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 20 Nov 2022 15:41:02 +0100 Subject: [PATCH 32/46] messages: split library/gui/widget into separate apps --- apps/android/ChangeLog | 1 + apps/android/metadata.json | 4 +- apps/android/settings.js | 2 +- apps/hcclock/ChangeLog | 3 +- apps/hcclock/hcclock.app.js | 2 +- apps/hcclock/metadata.json | 2 +- apps/ios/ChangeLog | 1 + apps/ios/app.js | 2 +- apps/ios/metadata.json | 4 +- apps/messagegui/ChangeLog | 79 +++++ apps/messagegui/README.md | 68 +++++ apps/{messages => messagegui}/app-icon.js | 0 .../app-newmessage.js | 4 +- apps/{messages => messagegui}/app.js | 25 +- apps/messagegui/app.png | Bin 0 -> 917 bytes apps/messagegui/boot.js | 3 + apps/messagegui/lib.js | 60 ++++ apps/messagegui/metadata.json | 22 ++ apps/{messages => messagegui}/screenshot.png | Bin apps/messages/ChangeLog | 78 +---- apps/messages/README.md | 89 ++---- apps/messages/lib.js | 283 +++++++++++------- apps/messages/metadata.json | 21 +- apps/messages/widget.js | 56 ---- apps/messagesmusic/ChangeLog | 1 + apps/messagesmusic/README.md | 6 - apps/messagesmusic/app.js | 15 +- apps/messagesmusic/metadata.json | 4 +- apps/widmessages/ChangeLog | 1 + apps/widmessages/README.md | 30 ++ apps/widmessages/app.png | Bin 0 -> 917 bytes apps/widmessages/lib.js | 8 + apps/widmessages/metadata.json | 18 ++ .../screenshot.gif} | Bin apps/widmessages/widget.js | 73 +++++ apps/widmsggrid/ChangeLog | 1 + apps/widmsggrid/README.md | 6 +- apps/widmsggrid/lib.js | 8 + apps/widmsggrid/metadata.json | 9 +- apps/widmsggrid/widget.js | 19 +- bin/sanitycheck.js | 3 +- 41 files changed, 642 insertions(+), 369 deletions(-) create mode 100644 apps/messagegui/ChangeLog create mode 100644 apps/messagegui/README.md rename apps/{messages => messagegui}/app-icon.js (100%) rename apps/{messages => messagegui}/app-newmessage.js (50%) rename apps/{messages => messagegui}/app.js (96%) create mode 100644 apps/messagegui/app.png create mode 100644 apps/messagegui/boot.js create mode 100644 apps/messagegui/lib.js create mode 100644 apps/messagegui/metadata.json rename apps/{messages => messagegui}/screenshot.png (100%) delete mode 100644 apps/messages/widget.js create mode 100644 apps/widmessages/ChangeLog create mode 100644 apps/widmessages/README.md create mode 100644 apps/widmessages/app.png create mode 100644 apps/widmessages/lib.js create mode 100644 apps/widmessages/metadata.json rename apps/{messages/screenshot-notify.gif => widmessages/screenshot.gif} (100%) create mode 100644 apps/widmessages/widget.js create mode 100644 apps/widmsggrid/lib.js diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index fcb139c94..f75638265 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -15,3 +15,4 @@ 0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later) 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) 0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge +0.18: Use new message library diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ac47449d6..45c08a75a 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,11 +2,11 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.17", + "version": "0.18", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/android/settings.js b/apps/android/settings.js index c7c34a76f..aede4b413 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -23,7 +23,7 @@ updateSettings(); } }, - /*LANG*/"Messages" : ()=>load("messages.app.js"), + /*LANG*/"Messages" : ()=>require("message").openGUI(), }; E.showMenu(mainmenu); }) diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 289c7ac2d..e2eb18be3 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,4 +1,5 @@ 0.01: Base code 0.02: Saved settings when switching color scheme 0.03: Added Button 3 opening messages (if app is installed) -0.04: Use `messages` library to check for new messages \ No newline at end of file +0.04: Use `messages` library to check for new messages +0.05: Use `messages` library to open message GUI \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index 9558c052b..f12a4733e 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -234,7 +234,7 @@ function handleMessages() { if(!hasMessages()) return; E.showMessage("Loading Messages..."); - load("messages.app.js"); + require("messages").openGUI(); } function hasMessages() diff --git a/apps/hcclock/metadata.json b/apps/hcclock/metadata.json index b8f8c14b9..407114e25 100644 --- a/apps/hcclock/metadata.json +++ b/apps/hcclock/metadata.json @@ -1,7 +1,7 @@ { "id": "hcclock", "name": "Hi-Contrast Clock", - "version": "0.04", + "version": "0.05", "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", "icon": "hcclock-icon.png", "type": "clock", diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 85aafb34f..5763801f5 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -9,3 +9,4 @@ 0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365) 0.10: Added more bundleIds 0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language +0.12: Use new message library diff --git a/apps/ios/app.js b/apps/ios/app.js index b210886fd..2a9e8f322 100644 --- a/apps/ios/app.js +++ b/apps/ios/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -setTimeout(()=>load("messages.app.js"),10); +setTimeout(()=>require("messages").openGUI(),10); diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json index a26d22cb1..5042ff1c0 100644 --- a/apps/ios/metadata.json +++ b/apps/ios/metadata.json @@ -1,11 +1,11 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.11", + "version": "0.12", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog new file mode 100644 index 000000000..3dcb20fe8 --- /dev/null +++ b/apps/messagegui/ChangeLog @@ -0,0 +1,79 @@ +0.01: New App! +0.02: Add 'messages' library +0.03: Fixes for Bangle.js 1 +0.04: Add require("messages").clearAll() +0.05: Handling of message actions (ok/clear) +0.06: New messages now go at the start (fix #898) + Answering true/false now exits the messages app if no new messages + Back now marks a message as read + Clicking top-left opens a menu which allows you to delete a message or mark unread +0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) +0.08: Fix rendering of long messages (fix #969) + buzz on new message (fix #999) +0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) + Fix phone icon (#1014) +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 notification icon (instead of large blinking 'MESSAGES') + Added screenshots +0.13: Add /*LANG*/ comments for internationalisation + Add 'Delete All' option to message options + Now update correctly when 'require("messages").clearAll()' is called +0.14: Hide widget when all unread notifications are dismissed from phone +0.15: Don't buzz when Quiet Mode is active +0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) +0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font +0.18: Use app-specific icon colors + Spread message action buttons out + Back button now goes back to list of messages + If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) +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 + 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 +0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) +0.26: Setting to auto-open music +0.27: Add 'mark all read' option to popup menu (fix #1624) +0.28: Option to auto-unlock the watch when a new message arrives +0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) +0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) +0.31: Option to disable icon flashing +0.32: Added an option to allow quiet mode to override message auto-open +0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going +0.34: Don't buzz for 'map' update messages +0.35: Reset graphics colors before rendering a message (possibly fix #1752) +0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) +0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items +0.38: Add telegram foss handling +0.39: Set default color for message icons according to theme +0.40: Use default Bangle formatter for booleans +0.41: Add notification icons in the widget +0.42: Fix messages ignoring "Vibrate: Off" setting +0.43: Add new Icons (Airbnb, warnwetter) +0.44: Separate buzz pattern for incoming calls +0.45: Added new app colors and icons +0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off + Fix message removal from widget bar (previously caused exception as .hide has been removed) +0.47: Add new Icons (Nextbike, Mattermost, etc.) +0.48: When getting new message from the clock, only buzz once the messages app is loaded +0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally +0.50: Add `getMessages` and `status` functions to library + Option to disable auto-open of messages + Option to make message icons monochrome (not colored) + messages widget buzz now returns a promise +0.51: Emit "message events" + Setting to hide widget + Add custom event handlers to prevent default app form loading + Move WIDGETS.messages.buzz() to require("messages").buzz() +0.52: Fix require("messages").buzz() regression + Fix background color in messages list after one unread message is shown +0.53: Messages now uses Bangle.load() to load messages app faster (if possible) +0.54: Move icons out to messageicons module +0.55: Rename to messagegui, move global message handling library to message module + Move widget to widmessage diff --git a/apps/messagegui/README.md b/apps/messagegui/README.md new file mode 100644 index 000000000..699588e1b --- /dev/null +++ b/apps/messagegui/README.md @@ -0,0 +1,68 @@ +# Messages app + +Default app to handle the display of messages and message notifications. It allows +them to be listed, viewed, and responded to. +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + + +## Settings + +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received +* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received +* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds +* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and +buzz every `Repeat` seconds. This is how long we continue to do that. +* `Unread Timer` - When a new message is received when showing the clock 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. +* `Auto-Open Music` - Should the app automatically open when the phone starts playing music? +* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? + +## New Messages + +When a new message is received: + +* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. +* If you're in a clock, the Messages app will automatically start and show the message + +When a message is shown, you'll see a screen showing the message title and text. + +* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. +* The top-left icon shows more options, for instance deleting the message of marking unread +* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) +* If shown, the 'tick' button: + * **Android** opens the notification on the phone + * **iOS** responds positively to the notification (accept call/etc) +* If shown, the 'cross' button: + * **Android** dismisses the notification on the phone + * **iOS** responds negatively to the notification (dismiss call/etc) + +## Images +_1. Screenshot of a notification_ + +![](screenshot.png) + + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/messages/app-icon.js b/apps/messagegui/app-icon.js similarity index 100% rename from apps/messages/app-icon.js rename to apps/messagegui/app-icon.js diff --git a/apps/messages/app-newmessage.js b/apps/messagegui/app-newmessage.js similarity index 50% rename from apps/messages/app-newmessage.js rename to apps/messagegui/app-newmessage.js index 328927c70..73d9a79c1 100644 --- a/apps/messages/app-newmessage.js +++ b/apps/messagegui/app-newmessage.js @@ -1,5 +1,5 @@ /* Called when we have a new message when we're in the clock... -BUZZ_ON_NEW_MESSAGE is set so when messages.app.js loads it knows +BUZZ_ON_NEW_MESSAGE is set so when messagegui.app.js loads it knows that it should buzz */ global.BUZZ_ON_NEW_MESSAGE = true; -eval(require("Storage").read("messages.app.js")); +eval(require("Storage").read("messagegui.app.js")); diff --git a/apps/messages/app.js b/apps/messagegui/app.js similarity index 96% rename from apps/messages/app.js rename to apps/messagegui/app.js index bebd92816..bf086dd3d 100644 --- a/apps/messages/app.js +++ b/apps/messagegui/app.js @@ -19,7 +19,6 @@ require("messages").pushMessage({"t":"add","id":1,"src":"Maps","title":"0 yd - H // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) */ - var Layout = require("Layout"); var settings = require('Storage').readJSON("messages.settings.json", true) || {}; var fontSmall = "6x8"; @@ -49,8 +48,11 @@ to the clock. */ var unreadTimeout; /// List of all our messages var MESSAGES = require("messages").getMessages(); -if (!Array.isArray(MESSAGES)) MESSAGES=[]; -var onMessagesModified = function(msg) { + +var onMessagesModified = function(type,msg) { + if (msg.handled) return; + msg.handled = true; + require("messages").apply(msg, MESSAGES); // TODO: if new, show this new one if (msg && msg.id!=="music" && msg.new && active!="map" && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { @@ -62,9 +64,15 @@ var onMessagesModified = function(msg) { } showMessage(msg&&msg.id); }; +Bangle.on("message", onMessagesModified); + function saveMessages() { - require("Storage").writeJSON("messages.json",MESSAGES) + require("messages").write(MESSAGES.map(m => { + delete m.show; + return m; + })); } +E.on("kill", saveMessages); function showMapMessage(msg) { active = "map"; @@ -355,12 +363,16 @@ function checkMessages(options) { } // we have >0 messages var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music"); + var toShow = MESSAGES.find(m=>m.show); + if (toShow) { + newMessages.unshift(toShow); + } // If we have a new message, show it - if (options.showMsgIfUnread && newMessages.length) { + if ((toShow||options.showMsgIfUnread) && newMessages.length) { showMessage(newMessages[0].id); // buzz after showMessage, so being busy during layout doesn't affect the buzz pattern if (global.BUZZ_ON_NEW_MESSAGE) { - // this is set if we entered the messages app by loading `messages.new.js` + // this is set if we entered the messages app by loading `messagegui.new.js` // ... but only buzz the first time we view a new message global.BUZZ_ON_NEW_MESSAGE = false; // messages.buzz respects quiet mode - no need to check here @@ -428,6 +440,7 @@ function cancelReloadTimeout() { g.clear(); Bangle.loadWidgets(); +require("messages").toggleWidget(false); Bangle.drawWidgets(); setTimeout(() => { diff --git a/apps/messagegui/app.png b/apps/messagegui/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/messagegui/boot.js b/apps/messagegui/boot.js new file mode 100644 index 000000000..4592f825d --- /dev/null +++ b/apps/messagegui/boot.js @@ -0,0 +1,3 @@ +(function() { + Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg)); +})(); \ No newline at end of file diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js new file mode 100644 index 000000000..5b07dd160 --- /dev/null +++ b/apps/messagegui/lib.js @@ -0,0 +1,60 @@ +/** + * Listener set up in boot.js, calls into here to keep boot.js short + */ +exports.listener = function(type, msg) { + // Default handler: Launch the GUI for all unhandled messages (except music if disabled in settings) + if (msg.handled || (global.__FILE__ && __FILE__.startsWith('messagegui.'))) return; // already handled or app open + + // if no new messages now, make sure we don't load the messages app + if (exports.messageTimeout && !msg.new && require("messages").status(msg) !== "new") { + clearTimeout(exports.messageTimeout); + delete exports.messageTimeout; + } + if (msg.t==="remove") return; + + const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; + let loadMessages = (Bangle.CLOCK || event.important); + if (type==="music") { + if (Bangle.CLOCK && msg.new && appSettings.openMusic) loadMessages = true; + else return; + } + require("messages").save(msg); + msg.handled = true; + if (msg.t!=="add" || !msg.new) { + return; + } + + const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet; + const unlockWatch = appSettings.unlockWatch; + // don't auto-open messages in quiet mode if quietNoAutOpn is true + if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) + loadMessages = false; + + // after a delay load the app, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + delete exports.messageTimeout; + if (type!=="music") { + if (!loadMessages) return require("messages").buzz(msg.src); // no opening the app, just buzz + if (!quiet && unlockWatch) { + Bangle.setLocked(false); + Bangle.setLCDPower(1); // turn screen on + } + } + exports.open(msg); + }, 500); +}; + +/** + * Launch GUI app with given message + * @param {object} msg + */ +exports.open = function(msg) { + if (msg && msg.id && !msg.show) { + // store which message to load + msg.show = 1; + require("messages").save(msg, {force: 1}); + } + + Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js"); +}; diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json new file mode 100644 index 000000000..7444050fd --- /dev/null +++ b/apps/messagegui/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "messagegui", + "name": "Messages", + "version": "0.55", + "description": "Default app to display notifications from iOS and Gadgetbridge/Android", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module" }, + "provides_modules": ["messagegui"], + "readme": "README.md", + "storage": [ + {"name":"messagegui","url":"lib.js"}, + {"name":"messagegui.app.js","url":"app.js"}, + {"name":"messagegui.new.js","url":"app-newmessage.js"}, + {"name":"messagegui.boot.js","url":"boot.js"}, + {"name":"messagegui.img","url":"app-icon.js","evaluate":true} + ], + "screenshots": [{"url":"screenshot.png"}], + "sortorder": -9 +} diff --git a/apps/messages/screenshot.png b/apps/messagegui/screenshot.png similarity index 100% rename from apps/messages/screenshot.png rename to apps/messagegui/screenshot.png diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index ee27d41c6..b0a84783b 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,77 +1 @@ -0.01: New App! -0.02: Add 'messages' library -0.03: Fixes for Bangle.js 1 -0.04: Add require("messages").clearAll() -0.05: Handling of message actions (ok/clear) -0.06: New messages now go at the start (fix #898) - Answering true/false now exits the messages app if no new messages - Back now marks a message as read - Clicking top-left opens a menu which allows you to delete a message or mark unread -0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) -0.08: Fix rendering of long messages (fix #969) - buzz on new message (fix #999) -0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) - Fix phone icon (#1014) -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 notification icon (instead of large blinking 'MESSAGES') - Added screenshots -0.13: Add /*LANG*/ comments for internationalisation - Add 'Delete All' option to message options - Now update correctly when 'require("messages").clearAll()' is called -0.14: Hide widget when all unread notifications are dismissed from phone -0.15: Don't buzz when Quiet Mode is active -0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) -0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font -0.18: Use app-specific icon colors - Spread message action buttons out - Back button now goes back to list of messages - If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) -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 - 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 -0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) -0.26: Setting to auto-open music -0.27: Add 'mark all read' option to popup menu (fix #1624) -0.28: Option to auto-unlock the watch when a new message arrives -0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) -0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) -0.31: Option to disable icon flashing -0.32: Added an option to allow quiet mode to override message auto-open -0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going -0.34: Don't buzz for 'map' update messages -0.35: Reset graphics colors before rendering a message (possibly fix #1752) -0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) -0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items -0.38: Add telegram foss handling -0.39: Set default color for message icons according to theme -0.40: Use default Bangle formatter for booleans -0.41: Add notification icons in the widget -0.42: Fix messages ignoring "Vibrate: Off" setting -0.43: Add new Icons (Airbnb, warnwetter) -0.44: Separate buzz pattern for incoming calls -0.45: Added new app colors and icons -0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off - Fix message removal from widget bar (previously caused exception as .hide has been removed) -0.47: Add new Icons (Nextbike, Mattermost, etc.) -0.48: When getting new message from the clock, only buzz once the messages app is loaded -0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally -0.50: Add `getMessages` and `status` functions to library - Option to disable auto-open of messages - Option to make message icons monochrome (not colored) - messages widget buzz now returns a promise -0.51: Emit "message events" - Setting to hide widget - Add custom event handlers to prevent default app form loading - Move WIDGETS.messages.buzz() to require("messages").buzz() -0.52: Fix require("messages").buzz() regression - Fix background color in messages list after one unread message is shown -0.53: Messages now uses Bangle.load() to load messages app faster (if possible) -0.54: Move icons out to messageicons module +0.01: Moved messages library into standalone library \ No newline at end of file diff --git a/apps/messages/README.md b/apps/messages/README.md index 72a989146..1a6758311 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -1,62 +1,25 @@ -# Messages app +# Messages library -This app handles the display of messages and message notifications. It stores -a list of currently received messages and allows them to be listed, viewed, -and responded to. +This library handles the passing of messages. It can storess a list of messages +and allows them to be retrieved by other apps. -It is a replacement for the old `notify`/`gadgetbridge` apps. +## Example -## Settings +Assuming you are using GadgetBridge and "overlay notifications": -You can change settings by going to the global `Settings` app, then `App Settings` -and `Messages`: - -* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received -* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received -* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds -* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and -buzz every `Repeat` seconds. This is how long we continue to do that. -* `Unread Timer` - When a new message is received when showing the clock 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. -* `Auto-Open Music` - Should the app automatically open when the phone starts playing music? -* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? -* `Flash Icon` - Toggle flashing of the widget icon. -* `Widget messages` - The maximum amount of message icons to show on the widget, or `Hide` the widget completely. - -## New Messages - -When a new message is received: - -* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. -* If you're in a clock, the Messages app will automatically start and show the message - -When a message is shown, you'll see a screen showing the message title and text. - -* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. -* The top-left icon shows more options, for instance deleting the message of marking unread -* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) -* If shown, the 'tick' button: - * **Android** opens the notification on the phone - * **iOS** responds positively to the notification (accept call/etc) -* If shown, the 'cross' button: - * **Android** dismisses the notification on the phone - * **iOS** responds negatively to the notification (dismiss call/etc) - -## Images -_1. Screenshot of a notification_ - -![](screenshot.png) - -_2. What the notify icon looks like (it's touchable on Bangle.js2!)_ - -![](screenshot-notify.gif) +1. Gadgetbridge sends an event to your watch for an incoming message +2. The `android` app parses the message, and calls `require("messages").pushMessage({/** the message */})` +3. `require("messages")` (provided by `messagelib`) calls `Bangle.emit("message", "text", {/** the message */})` +4. Overlay Notifications shows the message in an overlay, and marks it as `handled` +5. The default GUI app (`messages`) sees the event is marked as `handled`, so does nothing. +6. The default widget (`widmessages`) does nothing with `handled`, and shows a notification icon. +7. You tap the notification, in order to open the full GUI Overlay Notifications + calls `require("messages").openGUI({/** the message */})` +8. The default GUI app (`messages`) sees the "messageGUI" event, and launches itself -## Events (for app/widget developers) + +## Events When a new message arrives, a `"message"` event is emitted, you can listen for it like this: @@ -64,9 +27,8 @@ it like this: ```js myMessageListener = Bangle.on("message", (type, message)=>{ if (message.handled) return; // another app already handled this message - // is one of "text", "call", "alarm", "map", "music", or "clearAll" - if (type === "clearAll") return; // not a message - // see `messages/lib.js` for possible formats + // is one of "text", "call", "alarm", "map", or "music" + // see `messagelib/lib.js` for possible formats // message.t could be "add", "modify" or "remove" E.showMessage(`${message.title}\n${message.body}`, `${message.t} ${type} message`); // You can prevent the default `message` app from loading by setting `message.handled = true`: @@ -74,10 +36,23 @@ myMessageListener = Bangle.on("message", (type, message)=>{ }); ``` +Apps can launch the full GUI by calling `require("messages").openGUI()`, if you +want to write your own GUI, it should include boot code that listens for +`"messageGUI"` events: + +```js +Bangle.on("messageGUI", message=>{ + if (message.handled) return; // another app already opened it's GUI + message.handled = true; // prevent other apps form launching + Bangle.load("my_message_gui.app.js"); +}) + +``` + ## Requests -Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messagelib%library ## Creator diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 0ed03e4b6..fa1419c95 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -1,10 +1,16 @@ -function openMusic() { - // only read settings file for first music message - if ("undefined"==typeof exports._openMusic) { - exports._openMusic = !!((require('Storage').readJSON("messages.settings.json", true) || {}).openMusic); - } - return exports._openMusic; +exports.music = {}; +/** + * Emit "message" event with appropriate type from Bangle + * @param {object} msg + */ +function emit(msg) { + let type = "text"; + if (["call", "music", "map"].includes(msg.id)) type = msg.id; + if (type==="music" && msg.t!=="remove" && (!("state" in msg) || (!("track" in msg)))) return; // wait for complete music info + if (msg.src && msg.src.toLowerCase().startsWith("alarm")) type = "alarm"; + Bangle.emit("message", type, msg); } + /* Push a new message onto messages queue, event is: {t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool} {t:"add",id:int, id:"music", state, artist, track, etc} // add new @@ -12,125 +18,178 @@ function openMusic() { {t:"modify",id:int, title:string} // modified */ exports.pushMessage = function(event) { - var messages = exports.getMessages(); // now modify/delete as appropriate - var mIdx = messages.findIndex(m=>m.id==event.id); - if (event.t=="remove") { - if (mIdx>=0) messages.splice(mIdx, 1); // remove item - mIdx=-1; + if (event.t==="remove") { + if (event.id==="music") exports.music = {}; } else { // add/modify - if (event.t=="add"){ - if(event.new === undefined ) { // If 'new' has not been set yet, set it - event.new=true; // Assume it should be new - } + if (event.t==="add") { + if (event.new===undefined) event.new = true; // Assume it should be new + } else if (event.t==="modify") { + const old = exports.getMessages().find(m => m.id===event.id); + if (old) event = Object.assign(old, event); } - if (mIdx<0) { - mIdx=0; - messages.unshift(event); // add new messages to the beginning - } - else Object.assign(messages[mIdx], event); - if (event.id=="music" && messages[mIdx].state=="play") { - messages[mIdx].new = true; // new track, or playback (re)started - type = 'music'; + + // combine musicinfo and musicstate events + if (event.id==="music") { + if (event.state==="play") event.new = true; // new track, or playback (re)started + event = Object.assign(exports.music, event); } } - require("Storage").writeJSON("messages.json",messages); - var message = mIdx<0 ? {id:event.id, t:'remove'} : messages[mIdx]; - // if in app, process immediately - if ("undefined"!=typeof MESSAGES) return onMessagesModified(message); - // emit message event - var type = 'text'; - if (["call", "music", "map"].includes(message.id)) type = message.id; - if (message.src && message.src.toLowerCase().startsWith("alarm")) type = "alarm"; - Bangle.emit("message", type, message); - // update the widget icons shown - if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages,true); - var handleMessage = () => { - // if no new messages now, make sure we don't load the messages app - if (event.t=="remove" && exports.messageTimeout && !messages.some(m => m.new)) { - clearTimeout(exports.messageTimeout); - delete exports.messageTimeout; - } - // ok, saved now - if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) { - // just load the app to display music: no buzzing - Bangle.load("messages.app.js"); - } else if (event.t!="add") { - // we only care if it's new - return; - } else if (event.new==false) { - return; - } - // otherwise load messages/show widget - var loadMessages = Bangle.CLOCK || event.important; - var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet; - var appSettings = require('Storage').readJSON('messages.settings.json', 1) || {}; - var unlockWatch = appSettings.unlockWatch; - // don't auto-open messages in quiet mode if quietNoAutOpn is true - if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) - loadMessages = false; - delete appSettings; - // after a delay load the app, to ensure we have all the messages - if (exports.messageTimeout) clearTimeout(exports.messageTimeout); - exports.messageTimeout = setTimeout(function() { - exports.messageTimeout = undefined; - // if we're in a clock or it's important, go straight to messages app - if (loadMessages) { - if (!quiet && unlockWatch) { - Bangle.setLocked(false); - Bangle.setLCDPower(1); // turn screen on - } - // we will buzz when we enter the messages app - return Bangle.load("messages.new.js"); - } - if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages); - exports.buzz(message.src); - }, 500); - }; - setTimeout(()=>{ - if (!message.handled) handleMessage(); - },0); -} -/// Remove all messages + // reset state (just in case) + delete event.handled; + delete event.saved; + emit(event); +}; + +/** + * Save a single message to flash + * Also sets msg.saved=true + * + * @param {object} msg + * @param {object} [options={}] Options: + * {boolean} [force=false] Force save even if msg.saved is already set + */ +exports.save = function(msg, options) { + if (!options) options = {}; + if (msg.saved && !options.force) return; //already saved + let messages = exports.getMessages(); + exports.apply(msg, messages); + exports.write(messages); + msg.saved = true; +}; + +/** + * Apply incoming event to array of messages + * + * @param {object} event Event to apply + * @param {array} messages Array of messages, *will be modified in-place* + * @return {array} Modified messages array + */ +exports.apply = function(event, messages) { + if (!event || !event.id) return messages; + const mIdx = messages.findIndex(m => m.id===event.id); + if (event.t==="remove") { + if (mIdx<0) return messages; // already gone -> nothing to do + messages.splice(mIdx, 1); + } else if (event.t==="add") { + if (mIdx>=0) messages.splice(mIdx, 1); // duplicate ID! erase previous version + messages.unshift(event); + } else if (event.t==="modify") { + if (mIdx>=0) messages[mIdx] = Object.assign(messages[mIdx], event); + else messages.unshift(event); + } + return messages; +}; + +/** + * Accept a call (or other acceptable event) + * @param {object} msg + */ +exports.accept = function(msg) { + if (msg.positive) Bangle.messageResponse(msg, true); +}; + +/** + * Dismiss a message (if applicable), and erase it from flash + * Emits a "message" event with t="remove", only if message existed + * + * @param {object} msg + */ +exports.dismiss = function(msg) { + if (msg.negative) Bangle.messageResponse(msg, false); + let messages = exports.getMessages(); + const mIdx = messages.findIndex(m=>m.id===msg.id); + if (mIdx<0) return; + messages.splice(mIdx, 1); + exports.write(messages); + if (msg.t==="remove") return; // already removed, don't re-emit + msg.t = "remove"; + emit(msg); // emit t="remove", so e.g. widgets know to update +}; + +/** + * Emit a "type=openGUI" event, to open GUI app + * + * @param {object} [msg={}] Message the app should show + */ +exports.openGUI = function(msg) { + if (!require("Storage").read("messagegui")) return; // "messagegui" module is missing! + // Mark the event as unhandled for GUI, but leave passed arguments intact + let copy = Object.assign({}, msg); + delete copy.handled; + require("messagegui").open(copy); +}; + +/** + * Show/hide the messages widget + * + * @param {boolean} show + */ +exports.toggleWidget = function(show) { + if (!require("Storage").read("messagewidget")) return; // "messagewidget" module is missing! + if (show) require("messagewidget").show(); + else require("messagewidget").hide(); +}; + +/** + * Replace all stored messages + * @param {array} messages Messages to save + */ +exports.write = function(messages) { + require("Storage").writeJSON("messages.json", messages.map(m => { + // we never want to save saved/handled status to file; + delete m.saved; + delete m.handled; + return m; + })); +}; +/** + * Erase all messages + */ exports.clearAll = function() { - if ("undefined"!= typeof MESSAGES) { // we're in a messages app, clear that as well - MESSAGES = []; - } - // Clear all messages - require("Storage").writeJSON("messages.json", []); - // if we have a widget, update it - if (global.WIDGETS && WIDGETS.messages) - WIDGETS.messages.update([]); - // let message listeners know - Bangle.emit("message", "clearAll", {}); // guarantee listeners an object as `message` - // clearAll cannot be marked as "handled" - // update app if in app - if ("function"== typeof onMessagesModified) onMessagesModified(); + exports.write([]); + Bangle.emit("message", "clearAll", {}); } /** + * Get saved messages + * + * Optionally pass in a message to apply to the list, this is for event handlers: + * By passing the message from the event, you can make sure the list is up-to-date, + * even if the message has not been saved (yet) + * + * Example: + * Bangle.on("message", (type, msg) => { + * console.log("All messages:", require("messages").getMessages(msg)); + * }); + * + * @param {object} [withMessage] Apply this event to messages * @returns {array} All messages */ -exports.getMessages = function() { - if ("undefined"!=typeof MESSAGES) return MESSAGES; // loaded/managed by app - return require("Storage").readJSON("messages.json",1)||[]; -} +exports.getMessages = function(withMessage) { + let messages = require("Storage").readJSON("messages.json", true); + messages = Array.isArray(messages) ? messages : []; // make sure we always return an array + if (withMessage && withMessage.id) exports.apply(withMessage, messages); + return messages; +}; /** * Check if there are any messages + * + * @param {object} [withMessage] Apply this event to messages, see getMessages * @returns {string} "new"/"old"/"none" */ - exports.status = function() { +exports.status = function(withMessage) { try { - let status= "none"; - for(const m of exports.getMessages()) { + let status = "none"; + for(const m of exports.getMessages(withMessage)) { if (["music", "map"].includes(m.id)) continue; if (m.new) return "new"; status = "old"; } return status; } catch(e) { - return "none"; // don't bother e.g. the widget with errors + return "none"; // don't bother callers with errors } }; @@ -141,24 +200,24 @@ exports.getMessages = function() { */ exports.buzz = function(msgSrc) { exports.stopBuzz(); // cancel any previous buzz timeouts - if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode - var msgSettings = require('Storage').readJSON("messages.settings.json", true) || {}; - var pattern; - if (msgSrc && msgSrc.toLowerCase() === "phone") { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet) return Promise.resolve(); // never buzz during Quiet Mode + const msgSettings = require("Storage").readJSON("messages.settings.json", true) || {}; + let pattern; + if (msgSrc && msgSrc.toLowerCase()==="phone") { // special vibration pattern for incoming calls pattern = msgSettings.vibrateCalls; } else { pattern = msgSettings.vibrate; } - if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here + if (pattern===undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here if (!pattern) return Promise.resolve(); - var repeat = msgSettings.repeat; - if (repeat===undefined) repeat=4; // repeat may be zero + let repeat = msgSettings.repeat; + if (repeat===undefined) repeat = 4; // repeat may be zero if (repeat) { - exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000); - var vibrateTimeout = msgSettings.vibrateTimeout; - if (vibrateTimeout===undefined) vibrateTimeout=60; + exports.buzzTimeout = setTimeout(() => require("buzz").pattern(pattern), repeat*1000); + let vibrateTimeout = msgSettings.vibrateTimeout; + if (vibrateTimeout===undefined) vibrateTimeout = 60; if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000); } return require("buzz").pattern(pattern); diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index f3051958e..f94f01c26 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,23 +1,18 @@ { "id": "messages", "name": "Messages", - "version": "0.54", - "description": "App to display notifications from iOS and Gadgetbridge/Android", + "version": "0.01", + "description": "Library to handle message events", "icon": "app.png", - "type": "app", + "type": "module", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], - "dependencies" : { "messageicons":"module" }, + "provides_modules" : ["messages"], + "dependencies" : { "messagegui":"module","messagewidget":"module" }, "readme": "README.md", "storage": [ - {"name":"messages.app.js","url":"app.js"}, - {"name":"messages.new.js","url":"app-newmessage.js"}, - {"name":"messages.settings.js","url":"settings.js"}, - {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.wid.js","url":"widget.js"}, - {"name":"messages","url":"lib.js"} + {"name":"messages","url":"lib.js"}, + {"name":"messages.settings.js","url":"settings.js"} ], - "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], - "sortorder": -9 + "data": [{"name":"messages.json"},{"name":"messages.settings.json"}] } diff --git a/apps/messages/widget.js b/apps/messages/widget.js deleted file mode 100644 index c0dcd132f..000000000 --- a/apps/messages/widget.js +++ /dev/null @@ -1,56 +0,0 @@ -(() => { -if ((require('Storage').readJSON("messages.settings.json", true) || {}).maxMessages===0) return; - -function filterMessages(msgs) { - return msgs.filter(msg => msg.new && msg.id != "music") - .map(m => m.src) // we only need this for icon/color - .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); -} - -WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) { - // If we had a setTimeout queued from the last time we were called, remove it - if (WIDGETS["messages"].i) { - clearTimeout(WIDGETS["messages"].i); - delete WIDGETS["messages"].i; - } - Bangle.removeListener('touch', this.touch); - if (!this.width) return; - let settings = Object.assign({flash:true, maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); - if (recall !== true || settings.flash) { - var msgsShown = E.clip(this.msgs.length, 0, settings.maxMessages); - g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); - for(let i = 0;i < msgsShown;i++) { - const msg = this.msgs[i]; - const colors = [g.theme.bg, - require("messageicons").getColor(msg, {settings:settings})]; - if (settings.flash && ((Date.now()/1000)&1)) { - if (colors[1] == g.theme.fg) { - colors.reverse(); - } else { - colors[1] = g.theme.fg; - } - } - g.setColor(colors[1]).setBgColor(colors[0]); - // draw the icon, or '...' if too many messages - g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(msg), - this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/}); - } - } - WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000); - if (process.env.HWVERSION>1) Bangle.on('touch', this.touch); -},update:function(rawMsgs) { - const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); - this.msgs = filterMessages(rawMsgs); - this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages); - Bangle.drawWidgets(); -},touch:function(b,c) { - var w=WIDGETS["messages"]; - if (!w||!w.width||c.xw.x+w.width||c.yw.y+24) return; - load("messages.app.js"); -}}; - -/* We might have returned here if we were in the Messages app for a -message but then the watch was never viewed. */ -if (global.MESSAGES===undefined) - WIDGETS["messages"].update(require("messages").getMessages()); -})(); diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog index 9f4cafb0e..65fd09686 100644 --- a/apps/messagesmusic/ChangeLog +++ b/apps/messagesmusic/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app. +0.03: Use the new messages library \ No newline at end of file diff --git a/apps/messagesmusic/README.md b/apps/messagesmusic/README.md index 85608118d..9a50de93e 100644 --- a/apps/messagesmusic/README.md +++ b/apps/messagesmusic/README.md @@ -1,15 +1,9 @@ Hacky app that uses Messages app and it's library to push a message that triggers the music controls. It's nearly not an app, and yet it moves. -This app require Messages setting 'Auto-open Music' to be 'Yes'. If it isn't, the app will change it to 'Yes' and let it stay that way. - Making the music controls accessible this way lets one start a music stream on the phone in some situations even though the message app didn't receive a music message from gadgetbridge to begin with. (I think.) It is suggested to use Messages Music along side the app Quick Launch. -Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14. - -Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. - Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986). The icon used for this app is from [https://icons8.com](https://icons8.com). diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js index 27f3f6e4d..11e7e5a4e 100644 --- a/apps/messagesmusic/app.js +++ b/apps/messagesmusic/app.js @@ -1,14 +1 @@ -let showMusic = () => { - Bangle.CLOCK = 1; // To pass condition in messages library - require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); -}; - -var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict -if (!settings.openMusic) { - settings.openMusic = true; // This app/hack works as intended only if this setting is true - require('Storage').writeJSON('messages.settings.json', settings); - E.showMessage("First run:\n\nMessages setting\n\n 'Auto-Open Music'\n\n set to 'Yes'"); - setTimeout(()=>{showMusic();}, 5000); -} else { - showMusic(); -} +require('messages').openGUI({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json index c29ffbc34..ce5281563 100644 --- a/apps/messagesmusic/metadata.json +++ b/apps/messagesmusic/metadata.json @@ -1,7 +1,7 @@ { "id": "messagesmusic", "name":"Messages Music", - "version":"0.02", + "version":"0.03", "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", "icon":"app.png", "type": "app", @@ -13,6 +13,6 @@ {"name":"messagesmusic.app.js","url":"app.js"}, {"name":"messagesmusic.img","url":"app-icon.js","evaluate":true} ], - "dependencies": {"messages":"app"} + "dependencies" : { "messagelib":"module" } } diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog new file mode 100644 index 000000000..3a41005e9 --- /dev/null +++ b/apps/widmessages/ChangeLog @@ -0,0 +1 @@ +0.01: Moved messages widget into standalone widget app \ No newline at end of file diff --git a/apps/widmessages/README.md b/apps/widmessages/README.md new file mode 100644 index 000000000..398cb4fa8 --- /dev/null +++ b/apps/widmessages/README.md @@ -0,0 +1,30 @@ +# Messages widget + +The default widget to show icons for new messages +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +![screenshot](screenshot.gif) + +## Settings +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Flash icon` Toggle flashing of the widget icons. + +* `Widget messages` Not used by this widget. + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=widmessages%widget + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/widmessages/app.png b/apps/widmessages/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/widmessages/lib.js b/apps/widmessages/lib.js new file mode 100644 index 000000000..897611ad1 --- /dev/null +++ b/apps/widmessages/lib.js @@ -0,0 +1,8 @@ +exports.hide = function() { + if (!global.WIDGETS||!WIDGETS["messages"]) return; + WIDGETS["messages"].hide(); +} +exports.show = function() { + if (!global.WIDGETS||!WIDGETS["messages"]) return; + WIDGETS["messages"].show(); +} \ No newline at end of file diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json new file mode 100644 index 000000000..0c3ac7e05 --- /dev/null +++ b/apps/widmessages/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "widmessages", + "name": "Message Widget", + "version": "0.01", + "description": "Widget showing new messages", + "icon": "app.png", + "type": "widget", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url": "screenshot.gif"}], + "dependencies" : { "messageicons":"module" }, + "provides_modules" : ["messagewidget"], + "readme": "README.md", + "storage": [ + {"name":"messagewidget","url":"lib.js"}, + {"name":"widmessages.wid.js","url":"widget.js"} + ] +} diff --git a/apps/messages/screenshot-notify.gif b/apps/widmessages/screenshot.gif similarity index 100% rename from apps/messages/screenshot-notify.gif rename to apps/widmessages/screenshot.gif diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js new file mode 100644 index 000000000..2ee11b690 --- /dev/null +++ b/apps/widmessages/widget.js @@ -0,0 +1,73 @@ +(() => { + if ((require("Storage").readJSON("messages.settings.json", true) || {}).maxMessages===0) return; + + function filterMessages(msgs) { + return msgs.filter(msg => msg.new && msg.id != "music") + .map(m => m.src) // we only need this for icon/color + .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); + } + + WIDGETS["messages"] = { + area: "tl", width: 0, srcs: [], draw: function(recall) { + // If we had a setTimeout queued from the last time we were called, remove it + if (WIDGETS["messages"].i) { + clearTimeout(WIDGETS["messages"].i); + delete WIDGETS["messages"].i; + } + Bangle.removeListener("touch", this.touch); + if (!this.width) return; + let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {}); + if (recall!==true || settings.flash) { + const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages), + srcs = Object.keys(this.srcs); + g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); + for(let i = 0; isettings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(src), + this.x+12+i*24, this.y+12, {rotate: 0/*force centering*/}); + } + } + WIDGETS["messages"].i = setTimeout(() => WIDGETS["messages"].draw(true), 1000); + if (process.env.HWVERSION>1) Bangle.on("touch", this.touch); + }, onMsg: function(type, msg) { + if (this.hidden) return; + if (type==="music") return; + if (msg.id && !msg.new && msg.t!=="remove") return; + this.srcs = filterMessages(require("messages").getMessages(msg)); + const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); + this.width = 24 * E.clip(this.srcs.length, 0, settings.maxMessages); + if (type!=="init") Bangle.drawWidgets(); // "init" is not a real message type: see below + }, touch: function(b, c) { + var w = WIDGETS["messages"]; + if (!w || !w.width || c.xw.x+w.width || c.yw.y+24) return; + require("messages").openGUI(); + }, hide() { + this.hidden=true; + if (this.width) { + // hide widget + this.width = 0; + Bangle.drawWidgets(); + } + }, show() { + delete this.hidden + this.onMsg("show", {}); // reload messages+redraw + } + }; + + Bangle.on("message", WIDGETS["messages"].onMsg); + this.srcs = {}; + WIDGETS["messages"].onMsg("init", {}); // abuse type="init" to prevent Bangle.drawWidgets(); +})(); diff --git a/apps/widmsggrid/ChangeLog b/apps/widmsggrid/ChangeLog index 75259c4f0..9612da729 100644 --- a/apps/widmsggrid/ChangeLog +++ b/apps/widmsggrid/ChangeLog @@ -1,2 +1,3 @@ 0.01: New widget! 0.02: Adjust to message icons moving to messageicons lib +0.03: Use new message library \ No newline at end of file diff --git a/apps/widmsggrid/README.md b/apps/widmsggrid/README.md index 86a80c403..274858d66 100644 --- a/apps/widmsggrid/README.md +++ b/apps/widmsggrid/README.md @@ -20,9 +20,9 @@ You probably want to disable the default widget, to do so: 3. Scroll down to the `Widget messages` entry, and change it to `Hide` ## Settings -This widget uses the `Widget` settings from the `messages` app: +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: -### Widget * `Flash icon` Toggle flashing of the widget icons. -* `Widget messages` Not used by this widget, but you should select `Hide` to hide the default widget. \ No newline at end of file +* `Widget messages` Not used by this widget. \ No newline at end of file diff --git a/apps/widmsggrid/lib.js b/apps/widmsggrid/lib.js new file mode 100644 index 000000000..430577209 --- /dev/null +++ b/apps/widmsggrid/lib.js @@ -0,0 +1,8 @@ +exports.hide = function() { + if (!global.WIDGETS||!WIDGETS["msggrid"]) return; + WIDGETS["msggrid"].hide(); +} +exports.show = function() { + if (!global.WIDGETS||!WIDGETS["msggrid"]) return; + WIDGETS["msggrid"].show(); +} \ No newline at end of file diff --git a/apps/widmsggrid/metadata.json b/apps/widmsggrid/metadata.json index c9ed5bbe0..68c2c3771 100644 --- a/apps/widmsggrid/metadata.json +++ b/apps/widmsggrid/metadata.json @@ -1,16 +1,17 @@ { "id": "widmsggrid", "name": "Messages Grid Widget", - "version": "0.02", - "description": "Widget that display notification icons in a grid", + "version": "0.03", + "description": "Widget that displays notification icons in a grid", "icon": "widget.png", "type": "widget", - "dependencies": {"messages":"app"}, "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], - "dependencies" : { "messageicons":"module" }, + "dependencies" : { "messages":"module" }, + "provides_modules" : ["messagewidget"], "readme": "README.md", "storage": [ + {"name":"messagewidget","url":"lib.js"}, {"name":"widmsggrid.wid.js","url":"widget.js"} ], "screenshots": [{"url":"screenshot.png"}] diff --git a/apps/widmsggrid/widget.js b/apps/widmsggrid/widget.js index 431adf479..786f590b5 100644 --- a/apps/widmsggrid/widget.js +++ b/apps/widmsggrid/widget.js @@ -24,7 +24,7 @@ clearTimeout(w.t); delete w.t; } - if (!w.width) return; + if (!w.width || this.hidden) return; const b = w.flash && w.status === "new" && ((Date.now() / 1000) & 1), // Blink(= inverse colors) on this second? // show multiple icons in a grid, by scaling them down cols = Math.ceil(Math.sqrt(w.srcs.length - 0.1)); // cols===rows, -0.1 to work around rounding error @@ -57,9 +57,10 @@ .drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9); } if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking - }, show: function () { + }, show: function (m) { + delete w.hidden; w.width = 24; - w.srcs = require("messages").getMessages() + w.srcs = require("messages").getMessages(m) .filter(m => !['call', 'map', 'music'].includes(m.id)) .filter(m => m.new || w.showRead) .map(m => m.src); @@ -68,6 +69,7 @@ Bangle.drawWidgets(); Bangle.setLCDPower(1); // turns screen on }, hide: function () { + w.hidden = true; w.width = 0; w.srcs = []; w.total = 0; @@ -82,13 +84,16 @@ } // Bangle.js 2: open app when touching the widget else if (c.x < w.x || c.x > w.x + w.width || c.y < w.y || c.y > w.y + 24) return; - load("messages.app.js"); - }, listener: function () { - w.status = require("messages").status(); - if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(); + require("messages").openGUI(); + }, listener: function (t,m) { + if (this.hidden) return; + w.status = require("messages").status(m); + if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(m); else w.hide(); + delete w.hidden; // always set by w.hide(), but we checked it wasn't there before } }; delete s; const w = WIDGETS["msggrid"]; + Bangle.on("message", w.listener); })(); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 7ee07bebc..0a4765d9c 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -92,7 +92,8 @@ const INTERNAL_FILES_IN_APP_TYPE = { // list of app types and files they SHOULD }; /* These are warnings we know about but don't want in our output */ var KNOWN_WARNINGS = [ -"App gpsrec data file wildcard .gpsrc? does not include app ID" +"App gpsrec data file wildcard .gpsrc? does not include app ID", +"App widmessages storage file messagewidget is also listed as storage file for app widmsggrid", ]; function globToRegex(pattern) { From 8da09e3ea19a5d6617e134c1e11ba3a46925a91e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Sun, 27 Nov 2022 09:34:27 +0000 Subject: [PATCH 33/46] quick fix for last update - recorder now plots tracks ok --- apps/recorder/widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 9d61cfee5..87d4fb012 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -276,8 +276,8 @@ if (l===undefined) return; // empty file? var mp, c = l.split(","); var la=c.indexOf("Latitude"),lo=c.indexOf("Longitude"); - if (la<0 || lb<0) return; // no GPS! - l = f.readLine(); + if (la<0 || lo<0) return; // no GPS! + l = f.readLine();c=[]; while (l && !c[la]) { c = l.split(","); l = f.readLine(f); From dc219416ebd3a06cba5671c9a4015c37ff43dcc2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Sun, 27 Nov 2022 09:41:58 +0000 Subject: [PATCH 34/46] bump version --- apps/recorder/ChangeLog | 1 + apps/recorder/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 2f6ec84f6..3cf5d8372 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -22,3 +22,4 @@ 0.16: Ability to append to existing track (fix #1712) 0.17: Use default Bangle formatter for booleans 0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap +0.19: Fix track plotting code diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 9f8110cee..bbc08e0ef 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.18", + "version": "0.19", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", From 77e270d356542e341ea4a69d16486735e62298c3 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 27 Nov 2022 19:05:40 +0100 Subject: [PATCH 35/46] hrmmar: Add fft elimination algorithm to remove MA The fft with size=256 is calculated over a window of 8 seconds with step size 2 seconds. Sample rate of acceleration (12.5Hz) and PPG sensor (25Hz) is unchanged, so the algorithm runs with 12.5Hz. Generally the maximum peak in fft spectrum in an interval of -5..+10BPM of last one is used. The low fft resolution limits the accuracy to ~3BPM, but mean error is ~5-10BPM. If firmware reports confidence >= 90% its value is used. --- apps/hrmmar/README.md | 11 +++ apps/hrmmar/app.png | Bin 0 -> 1192 bytes apps/hrmmar/boot.js | 40 ++++++++ apps/hrmmar/fftelim.js | 190 ++++++++++++++++++++++++++++++++++++++ apps/hrmmar/metadata.json | 18 ++++ apps/hrmmar/settings.js | 26 ++++++ 6 files changed, 285 insertions(+) create mode 100644 apps/hrmmar/README.md create mode 100644 apps/hrmmar/app.png create mode 100644 apps/hrmmar/boot.js create mode 100644 apps/hrmmar/fftelim.js create mode 100644 apps/hrmmar/metadata.json create mode 100644 apps/hrmmar/settings.js diff --git a/apps/hrmmar/README.md b/apps/hrmmar/README.md new file mode 100644 index 000000000..ff90d9156 --- /dev/null +++ b/apps/hrmmar/README.md @@ -0,0 +1,11 @@ +# HRM Motion Artifacts removal + +Measurements from the build in PPG-Sensor (Photoplethysmograph) is sensitive to motion and can be corrupted with Motion Artifacts (MA). This module allows to remove these. + +## Settings + +* **MA removal** + +Select the algorithm to Remove Motion artifacts: + - None: (default) No Motion Artifact removal. + - fft elim: (*experimental*) Remove Motion Artifacts by cutting out the frequencies from the HRM frequency spectrum that are noisy in acceleration spectrum. Under motion this can report a heart rate that is closer to the real one but will fail if motion frequency and heart rate overlap. diff --git a/apps/hrmmar/app.png b/apps/hrmmar/app.png new file mode 100644 index 0000000000000000000000000000000000000000..9db19be376fcfbd57a41ed0a04eee292b34c828e GIT binary patch literal 1192 zcmV;Z1XufsP)7N8&wpCzj16QO=3?ye$>y9#7#&f2&F`l*fYj2&35nxJ-PlfTCr)jTJuJo^JBi0_eQgSo zztx>NbI-ZInRD)AV8n69>sIT^kJmm)b~9ScdZ`Vr^6Uo>pp4bk56z6G0yK#WB2lO0 z1Cj1W(agtUDnKOXC!J+xU)=7udpBe=gQ)-!LW?Opf&4IBpTIf{r=e&XS72`#u1_!& z0l|edlg=_o8fZOdYGDSRLQ`@X*;B-3SCQSNLAyV}zy*XBQbyYy9DW@PTtM(_+Gs2P z2LZCDc;vcMfG5;lz^Um&CLM>Op#ebw-bj|{xxbC;Y1aX=r%0o&yH!l4((v;sB1;L8 z&;~*RqAzdapUW86p|g7aZ`__k)4=I2VxOp;=oem0{x<~7uWac8Bn@b%&-R@eN_!3@ zsry$5Eu@)QN+4M@k???k0Pjo|*;8bEvV!YO0nq1vfK<#+E~)PW-Nyu+_7!j_CB~;J z_#(UghBxS2K=j+yW+^Qcp90m#P59c`4c#=&h`+cFUL_@V;u{N4@ zpx;Yx_yNeJnqMH0N0LC(z%te#6iuTwQ{Oj}W%;$g9y|D6mdY_qGp{lPV4 zZ-Md2D$xD{g`9(0#fE*NhQn2&n0MfdWU-&Bl1nKx>K43_EU&-$GXOWge1r8{(H_yd zXN1r!RI6Ia-*+KN`r=f`Iyrbar5iPgT-pVIyIBXV^x3%=B7P?VfYqk5ci+YOuk(G4 zL3kz3Z2OFMs`>U?wCcbR&@ABOT8Q}Fi1FMn-=FV$*D5UF!Isx(pAe8}*MZHha%ScM z4b?(=!%rseo>bpjNR_$T^TbMHXL)0g$*CGVIxe*x+G6=Yh!+@5fC>QkWaP$jU{6NW2-$V26CuRt_4tQ-Cbonay+>f>iUcb19=^0g9nUF9l%aU z0S}6y{8s6twCA9DXs1@S@i23G;Qog$K-2W~ppbPA#ea_!l8WZGrz4+INQk9R!OYM@ zfObpyCn%|B-;7bpAep0|u?SIms20)F`Vv$eX_ z`Vtb~h^Et{{fRz$NkufiwOF)QZ+!JdOeFq@5hF$nkADCxICO5y(}2|g0000 { + bpm_corrected = bpm; + }; + + Bangle.on('HRM', (hrm) => { + if (bpm_corrected > 0) { + // replace bpm data in event + hrm.bpm_orig = hrm.bpm; + hrm.confidence_orig = hrm.confidence; + hrm.bpm = bpm_corrected; + hrm.confidence = 0; + } + }); + + let run = () => { + const settings = Object.assign({ + mAremoval: 0 + }, require("Storage").readJSON("hrmmar.json", true) || {}); + + // select motion artifact removal algorithm + switch(settings.mAremoval) { + case 1: + require("hrmfftelim").run(settings, updateHrm); + break; + } + } + + // override setHRMPower so we can run our code on HRM enable + const oldSetHRMPower = Bangle.setHRMPower; + Bangle.setHRMPower = function(on, id) { + if (on && run !== undefined) { + run(); + run = undefined; // Make sure we run only once + } + return oldSetHRMPower(on, id); + }; +} diff --git a/apps/hrmmar/fftelim.js b/apps/hrmmar/fftelim.js new file mode 100644 index 000000000..98b7f33ad --- /dev/null +++ b/apps/hrmmar/fftelim.js @@ -0,0 +1,190 @@ +exports.run = (settings, updateHrm) => { + const SAMPLE_RATE = 12.5; + const NUM_POINTS = 256; // fft size + const ACC_PEAKS = 2; // remove this number of ACC peaks + + // ringbuffers + const hrmvalues = new Int16Array(8*SAMPLE_RATE); + const accvalues = new Int16Array(8*SAMPLE_RATE); + // fft buffers + const hrmfftbuf = new Int16Array(NUM_POINTS); + const accfftbuf = new Int16Array(NUM_POINTS); + let BPM_est_1 = 0; + let BPM_est_2 = 0; + + let hrmdata; + let idx=0, wraps=0; + + // init settings + Bangle.setOptions({hrmPollInterval: 40, powerSave: false}); // hrm=25Hz + Bangle.setPollInterval(80); // 12.5Hz + + calcfft = (values, idx, normalize, fftbuf) => { + fftbuf.fill(0); + let i_out=0; + let avg = 0; + if (normalize) { + const sum = values.reduce((a, b) => a + b, 0); + avg = sum/values.length; + } + // sort ringbuffer to fft buffer + for(let i_in=idx; i_in { + let maxVal = -Number.MAX_VALUE; + let maxIdx = 0; + + values.forEach((value,i) => { + if (value > maxVal) { + maxVal = value; + maxIdx = i; + } + }); + return {idx: maxIdx, val: maxVal}; + }; + + getSign = (value) => { + return value < 0 ? -1 : 1; + }; + + // idx in fft buffer to frequency + getFftFreq = (idx, rate, size) => { + return idx*rate/(size-1); + }; + + // frequency to idx in fft buffer + getFftIdx = (freq, rate, size) => { + return Math.round(freq*(size-1)/rate); + }; + + calc2ndDeriative = (values) => { + const result = new Int16Array(values.length-2); + for(let i=1; i { + // fft + const ppg_fft = calcfft(hrmvalues, idx, true, hrmfftbuf).subarray(minFreqIdx, maxFreqIdx+1); + const acc_fft = calcfft(accvalues, idx, false, accfftbuf).subarray(minFreqIdx, maxFreqIdx+1); + + // remove spectrum that have peaks in acc fft from ppg fft + const accGlobalMax = getMax(acc_fft); + const acc2nddiff = calc2ndDeriative(acc_fft); // calculate second derivative + for(let iClean=0; iClean < ACC_PEAKS; iClean++) { + // get max peak in ACC + const accMax = getMax(acc_fft); + + if (accMax.val >= 10 && accMax.val/accGlobalMax.val > 0.75) { + // set all values in PPG FFT to zero until second derivative of ACC has zero crossing + for (let k = accMax.idx-1; k>=0; k--) { + ppg_fft[k] = 0; + acc_fft[k] = -Math.abs(acc_fft[k]); // max(acc_fft) should no longer find this + if (k-2 > 0 && getSign(acc2nddiff[k-1-2]) != getSign(acc2nddiff[k-2]) && Math.abs(acc_fft[k]) < accMax.val*0.75) { + break; + } + } + // set all values in PPG FFT to zero until second derivative of ACC has zero crossing + for (let k = accMax.idx; k < acc_fft.length-1; k++) { + ppg_fft[k] = 0; + acc_fft[k] = -Math.abs(acc_fft[k]); // max(acc_fft) should no longer find this + if (k-2 >= 0 && getSign(acc2nddiff[k+1-2]) != getSign(acc2nddiff[k-2]) && Math.abs(acc_fft[k]) < accMax.val*0.75) { + break; + } + } + } + } + + // bpm result is maximum peak in PPG fft + const hrRangeMax = getMax(ppg_fft.subarray(rangeIdx[0], rangeIdx[1])); + const hrTotalMax = getMax(ppg_fft); + const maxDiff = hrTotalMax.val/hrRangeMax.val; + let idxMaxPPG = hrRangeMax.idx+rangeIdx[0]; // offset range limit + + if ((maxDiff > 3 && idxMaxPPG != hrTotalMax.idx) || hrRangeMax.val === 0) { // prevent tracking from loosing the real heart rate by checking the full spectrum + if (hrTotalMax.idx > idxMaxPPG) { + idxMaxPPG = idxMaxPPG+Math.ceil(6/freqStep); // step 6 BPM up into the direction of max peak + } else { + idxMaxPPG = idxMaxPPG-Math.ceil(2/freqStep); // step 2 BPM down into the direction of max peak + } + } + + idxMaxPPG = idxMaxPPG + minFreqIdx; + const BPM_est_0 = getFftFreq(idxMaxPPG, SAMPLE_RATE, NUM_POINTS)*60; + + // smooth with moving average + let BPM_est_res; + if (BPM_est_2 > 0) { + BPM_est_res = 0.9*BPM_est_0 + 0.05*BPM_est_1 + 0.05*BPM_est_2; + } else { + BPM_est_res = BPM_est_0; + } + + return BPM_est_res.toFixed(1); + }; + + Bangle.on('HRM-raw', (hrm) => { + hrmdata = hrm; + }); + + Bangle.on('accel', (acc) => { + if (hrmdata !== undefined) { + hrmvalues[idx] = hrmdata.filt; + accvalues[idx] = acc.x*1000 + acc.y*1000 + acc.z*1000; + idx++; + if (idx >= 8*SAMPLE_RATE) { + idx = 0; + wraps++; + } + + if (idx % (SAMPLE_RATE*2) == 0) { // every two seconds + if (wraps === 0) { // use rate of firmware until hrmvalues buffer is filled + updateHrm(undefined); + BPM_est_2 = BPM_est_1; + BPM_est_1 = hrmdata.bpm; + } else { + let bpm_result; + if (hrmdata.confidence >= 90) { // display firmware value if good + bpm_result = hrmdata.bpm; + updateHrm(undefined); + } else { + bpm_result = calculate(idx); + bpm_corrected = bpm_result; + updateHrm(bpm_result); + } + BPM_est_2 = BPM_est_1; + BPM_est_1 = bpm_result; + + // set search range of next BPM + const est_res_idx = getFftIdx(bpm_result/60, SAMPLE_RATE, NUM_POINTS)-minFreqIdx; + rangeIdx = [est_res_idx-maxBpmDiffIdxDown, est_res_idx+maxBpmDiffIdxUp]; + if (rangeIdx[0] < 0) { + rangeIdx[0] = 0; + } + if (rangeIdx[1] > maxFreqIdx-minFreqIdx) { + rangeIdx[1] = maxFreqIdx-minFreqIdx; + } + } + } + } + }); +}; diff --git a/apps/hrmmar/metadata.json b/apps/hrmmar/metadata.json new file mode 100644 index 000000000..232ff64a7 --- /dev/null +++ b/apps/hrmmar/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "hrmmar", + "name": "HRM Motion Artifacts removal", + "shortName":"HRM MA removal", + "icon": "app.png", + "version":"0.01", + "description": "Removes Motion Artifacts in Bangle.js's heart rate sensor data.", + "type": "bootloader", + "tags": "health", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"hrmmar.boot.js","url":"boot.js"}, + {"name":"hrmfftelim","url":"fftelim.js"}, + {"name":"hrmmar.settings.js","url":"settings.js"} + ], + "data": [{"name":"hrmmar.json"}] +} diff --git a/apps/hrmmar/settings.js b/apps/hrmmar/settings.js new file mode 100644 index 000000000..3c6e62c91 --- /dev/null +++ b/apps/hrmmar/settings.js @@ -0,0 +1,26 @@ +(function(back) { + var FILE = "hrmmar.json"; + // Load settings + var settings = Object.assign({ + mAremoval: 0, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "HRM MA removal" }, + "< Back" : () => back(), + 'MA removal': { + value: settings.mAremoval, + min: 0, max: 1, + format: v => ["None", "fft elim."][v], + onchange: v => { + settings.mAremoval = v; + writeSettings(); + } + }, + }); +}) From bc28900e60ee9cea407b1c21693064fa1c1210ab Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 27 Nov 2022 14:58:39 +0100 Subject: [PATCH 36/46] launch - Use Bangle.showClock --- apps/launch/ChangeLog | 1 + apps/launch/app.js | 19 +++++++------------ apps/launch/metadata.json | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 5da1b2661..0aff8c49f 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -19,3 +19,4 @@ 0.17: Don't display 'Loading...' now the watch has its own loading screen 0.18: Add 'back' icon in top-left to go back to clock 0.19: Fix regression after back button added (returnToClock was called twice!) +0.20: Use Bangle.showClock for changing to clock diff --git a/apps/launch/app.js b/apps/launch/app.js index b8e598f73..36f3aaf4b 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -42,16 +42,6 @@ let apps = launchCache.apps; if (!settings.fullscreen) Bangle.loadWidgets(); -let returnToClock = function() { - // unload everything manually - // ... or we could just call `load();` but it will be slower - Bangle.setUI(); // remove scroller's handling - if (lockTimeout) clearTimeout(lockTimeout); - Bangle.removeListener("lock", lockHandler); - // now load the default clock - just call .bootcde as this has the code already - setTimeout(eval,0,s.read(".bootcde")); -} - E.showScroller({ h : 64*scaleval, c : apps.length, draw : (i, r) => { @@ -74,7 +64,12 @@ E.showScroller({ load(app.src); } }, - back : returnToClock // button press or tap in top left calls returnToClock now + back : Bangle.showClock, // button press or tap in top left shows clock now + remove : () => { + // cleanup the timeout to not leave anything behind after being removed from ram + if (lockTimeout) clearTimeout(lockTimeout); + Bangle.removeListener("lock", lockHandler); + } }); g.flip(); // force a render before widgets have finished drawing @@ -85,7 +80,7 @@ let lockHandler = function(locked) { if (lockTimeout) clearTimeout(lockTimeout); lockTimeout = undefined; if (locked) - lockTimeout = setTimeout(returnToClock, 10000); + lockTimeout = setTimeout(Bangle.showClock, 10000); } Bangle.on("lock", lockHandler); if (!settings.fullscreen) // finally draw widgets diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json index ce9b1f801..4d0270e37 100644 --- a/apps/launch/metadata.json +++ b/apps/launch/metadata.json @@ -2,7 +2,7 @@ "id": "launch", "name": "Launcher", "shortName": "Launcher", - "version": "0.19", + "version": "0.20", "description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "readme": "README.md", "icon": "app.png", From a011f929fd7e29f67fe3b01e86bc41477f1c4ed0 Mon Sep 17 00:00:00 2001 From: glemco Date: Sat, 26 Nov 2022 20:12:08 +0100 Subject: [PATCH 37/46] Rolled back agenda clkinfo --- apps/agenda/ChangeLog | 1 - apps/agenda/agenda.clkinfo.js | 29 ++++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index 6384fe887..7f749ff25 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -7,4 +7,3 @@ 0.07: Clkinfo improvements. 0.08: Fix error in clkinfo (didn't require Storage & locale) Fix clkinfo icon -0.09: Clkinfo new fields and filter past events. diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index 43b7cf57e..baa8b9516 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -1,34 +1,25 @@ (function() { var agendaItems = { - name: "Agenda", - img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), - dynamic: true, - items: [] - }; - var storage = require("Storage"); + name: "Agenda", + img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), + items: [] + }; var locale = require("locale"); var now = new Date(); - var agenda = (storage.readJSON("android.calendar.json",true)||[]) - .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) - .sort((a,b)=>a.timestamp - b.timestamp); - var settings = storage.readJSON("agenda.settings.json",true)||{}; + var agenda = require("Storage").readJSON("android.calendar.json") + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); agenda.forEach((entry, i) => { + var title = entry.title.slice(0,12); var date = new Date(entry.timestamp*1000); var dateStr = locale.date(date).replace(/\d\d\d\d/,""); - var dateStrToday = locale.date(new Date()).replace(/\d\d\d\d/,""); - var timeStr = locale.time(date); - //maybe not the most efficient.. - var shortTxt = (dateStrToday == dateStr) ? timeStr : dateStr; dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; - if(!settings.pastEvents && entry.timestamp + entry.durationInSeconds < (new Date())/1000) - return; - agendaItems.items.push({ - name: null, - get: () => ({ text: title + "\n" + dateStr, short: shortTxt, img: null}), + name: "Agenda "+i, + get: () => ({ text: title + "\n" + dateStr, img: null}), show: function() { agendaItems.items[i].emit("redraw"); }, hide: function () {} }); From 7d253a2d5ffc9eeb302ba3586fed432103f8d946 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 09:46:25 +0000 Subject: [PATCH 38/46] let users know about new firmware --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 251f94ff1..c525fd963 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v15"; +var RECOMMENDED_VERSION = "2v16"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles From e2da2a87a8dc7845c5c39fcac01ec59467fbce92 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 10:09:55 +0000 Subject: [PATCH 39/46] 0.03: Added support for locale based time ref #2311, fix #2317 --- apps/slopeclock/ChangeLog | 1 + apps/slopeclock/app.js | 5 +++-- apps/slopeclock/metadata.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/slopeclock/ChangeLog b/apps/slopeclock/ChangeLog index af766d95d..2eb04a4f0 100644 --- a/apps/slopeclock/ChangeLog +++ b/apps/slopeclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Reset font to save some memory during remove +0.03: Added support for locale based time diff --git a/apps/slopeclock/app.js b/apps/slopeclock/app.js index 178084fb0..cc3dce630 100644 --- a/apps/slopeclock/app.js +++ b/apps/slopeclock/app.js @@ -34,8 +34,9 @@ let draw = function() { x = R.w / 2; y = R.y + R.h / 2 - 12; // 12 = room for date var date = new Date(); - var hourStr = date.getHours(); - var minStr = date.getMinutes().toString().padStart(2,0); + var local_time = require("locale").time(date, 1); + var hourStr = local_time.split(":")[0].trim().padStart(2,'0'); + var minStr = local_time.split(":")[1].trim().padStart(2, '0'); dateStr = require("locale").dow(date, 1).toUpperCase()+ " "+ require("locale").date(date, 0).toUpperCase(); diff --git a/apps/slopeclock/metadata.json b/apps/slopeclock/metadata.json index 18820b2cc..6ee78350f 100644 --- a/apps/slopeclock/metadata.json +++ b/apps/slopeclock/metadata.json @@ -1,6 +1,6 @@ { "id": "slopeclock", "name": "Slope Clock", - "version":"0.02", + "version":"0.03", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], From 83279f4cf729da0e172f2d50e270ea4422a9cf07 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 10:54:05 +0000 Subject: [PATCH 40/46] Ensure you don't have to favourite bootloader/etc - they just get installed automatically --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index aba9b6a51..f3f54106b 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit aba9b6a51fe02dfbde307c303560b8382857916d +Subproject commit f3f54106b3d7f84927ff734b715023a49a87cc6f From eba77c8a9915180d1b822b04c292b7f68a4952fd Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 11:16:36 +0000 Subject: [PATCH 41/46] openstmap 0.15: Make track drawing an option (default off) --- apps/openstmap/ChangeLog | 1 + apps/openstmap/README.md | 9 +++++++-- apps/openstmap/app.js | 22 +++++++++++++++------- apps/openstmap/metadata.json | 3 ++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 7f22014ae..7f788c139 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -15,3 +15,4 @@ 0.14: Added ability to upload multiple sets of map tiles Support for zooming in on map Satellite count moved to widget bar to leave more room for the map +0.15: Make track drawing an option (default off) diff --git a/apps/openstmap/README.md b/apps/openstmap/README.md index 707dbc7f8..f19b13bd1 100644 --- a/apps/openstmap/README.md +++ b/apps/openstmap/README.md @@ -26,11 +26,16 @@ can change settings, move the map around, and click `Get Map` again. ## Bangle.js App The Bangle.js app allows you to view a map - it also turns the GPS on and marks -the path that you've been travelling. +the path that you've been travelling (if enabled). * Drag on the screen to move the map * Press the button to bring up a menu, where you can zoom, go to GPS location -or put the map back in its default location +, put the map back in its default location, or choose whether to draw the currently +recording GPS track (from the `Recorder` app). + +**Note:** If enabled, drawing the currently recorded GPS track can take a second +or two (which happens after you've finished scrolling the screen with your finger). + ## Library diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 9df4fa83f..89e2d2ddb 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -4,19 +4,23 @@ var R; var fix = {}; var mapVisible = false; var hasScrolled = false; +var settings = require("Storage").readJSON("openstmap.json",1)||{}; // Redraw the whole page function redraw() { g.setClipRect(R.x,R.y,R.x2,R.y2); m.draw(); drawMarker(); - if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { - g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later - WIDGETS["gpsrec"].plotTrack(m); - } - if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { - g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later - WIDGETS["recorder"].plotTrack(m); + // if track drawing is enabled... + if (settings.drawTrack) { + if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["gpsrec"].plotTrack(m); + } + if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["recorder"].plotTrack(m); + } } g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); } @@ -76,6 +80,10 @@ function showMap() { m.scale *= 2; showMap(); }, + /*LANG*/"Draw Track": { + value : !!settings.drawTrack, + onchange : v => { settings.drawTrack=v; require("Storage").writeJSON("openstmap.json",settings); } + }, /*LANG*/"Center Map": () =>{ m.lat = m.map.lat; m.lon = m.map.lon; diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 09b4c68e3..819dc4122 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,7 +2,7 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.14", + "version": "0.15", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", "readme": "README.md", "icon": "app.png", @@ -15,6 +15,7 @@ {"name":"openstmap.app.js","url":"app.js"}, {"name":"openstmap.img","url":"app-icon.js","evaluate":true} ], "data": [ + {"name":"openstmap.json"}, {"wildcard":"openstmap.*.json"}, {"wildcard":"openstmap.*.img"} ] From b097b93bbfde10cd2e76ebcb9911f337c4cbc0a5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 13:55:02 +0000 Subject: [PATCH 42/46] active pedometer notes --- apps/activepedom/README.md | 8 +++++++- apps/activepedom/metadata.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/activepedom/README.md b/apps/activepedom/README.md index ac32a1dd6..06ad280ee 100644 --- a/apps/activepedom/README.md +++ b/apps/activepedom/README.md @@ -1,6 +1,11 @@ # Active Pedometer + Pedometer that filters out arm movement and displays a step goal progress. +**Note:** Since creation of this app, Bangle.js's step counting algorithm has +improved significantly - and as a result the algorithm in this app (which + runs *on top* of Bangle.js's algorithm) may no longer be accurate. + I changed the step counting algorithm completely. Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long. To get in 'active' mode, you have to reach the step threshold before the active timer runs out. @@ -9,6 +14,7 @@ When you reach the step threshold, the steps needed to reach the threshold are c Steps are saved to a datafile every 5 minutes. You can watch a graph using the app. ## Screenshots + * 600 steps ![](600.png) @@ -70,4 +76,4 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a ## Requests -If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ \ No newline at end of file +If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ diff --git a/apps/activepedom/metadata.json b/apps/activepedom/metadata.json index 4deb7006d..81bafb573 100644 --- a/apps/activepedom/metadata.json +++ b/apps/activepedom/metadata.json @@ -3,7 +3,7 @@ "name": "Active Pedometer", "shortName": "Active Pedometer", "version": "0.09", - "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "description": "(NOT RECOMMENDED) Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph. The `Health` app now provides step logging and graphs.", "icon": "app.png", "tags": "outdoors,widget", "supports": ["BANGLEJS"], From 8678d0cc2137c0d2344f71f92912df46daa2302f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 14:22:35 +0000 Subject: [PATCH 43/46] slight tweaks to https://github.com/espruino/BangleApps/pull/2304/ --- apps/android/ChangeLog | 1 + apps/android/README.md | 2 ++ apps/android/boot.js | 48 ++++++++++++++++---------------------- apps/android/metadata.json | 2 +- apps/android/settings.js | 16 ++++++------- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index fcb139c94..f71e94cf7 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -15,3 +15,4 @@ 0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later) 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) 0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge +0.18: If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) diff --git a/apps/android/README.md b/apps/android/README.md index f9ab73699..c76e6e528 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -20,6 +20,8 @@ It contains: of Gadgetbridge - making your phone make noise so you can find it. * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js keep any messages it has received, or should it delete them? +* `Overwrite GPS` - when GPS is requested by an app, this doesn't use Bangle.js's GPS +but instead asks Gadgetbridge on the phone to use the phone's GPS * `Messages` - launches the messages app, showing a list of messages ## How it works diff --git a/apps/android/boot.js b/apps/android/boot.js index 8d9270b2a..8e75241e7 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -137,8 +137,7 @@ Bangle.emit('gps', event); }, "is_gps_active": function() { - const gpsActive = originalIsGpsOn(); - sendGPSPowerStatus(gpsActive); + gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 }); } }; var h = HANDLERS[event.t]; @@ -202,34 +201,27 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; - // GPS overwrite logic - // Save current logic - const originalSetGpsPower = Bangle.setGPSPower; - const originalIsGpsOn = Bangle.isGPSOn; - - function sendGPSPowerStatus(status) { gbSend({ t: "gps_power", status: status }); } - - // Replace set GPS power logic to suppress activation of gps, if the overwrite option is active - Bangle.setGPSPower = (isOn, appID) => { - const currentSettings = require("Storage").readJSON("android.settings.json",1)||{}; - if (!currentSettings.overwriteGps) { - originalSetGpsPower(isOn, appID); - } else { - sendGPSPowerStatus(Bangle.isGPSOn()); - const logMessage = 'Ignore gps power change due to the gps overwrite from android integration app'; - console.log(logMessage); - Bluetooth.println(logMessage); + if (settings.overwriteGps) { // if the overwrite option is set../ + // Save current logic + const originalSetGpsPower = Bangle.setGPSPower; + // Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) + Bangle.setGPSPower = (isOn, appID) => { + // if not connected, use old logic + if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID); + // Emulate old GPS power logic + if (!Bangle._PWR) Bangle._PWR={}; + if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; + if (!appID) appID="?"; + if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); + if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); + let pwr = Bangle._PWR.GPS.length>0; + gbSend({ t: "gps_power", status: pwr }); + return pwr; } - } - - // Replace check if the GPS is on, to show it as always active, if the overwrite option is set - Bangle.isGPSOn = () => { - const currentSettings = require("Storage").readJSON("android.settings.json",1)||{}; - if (!currentSettings.overwriteGps) { - return originalIsGpsOn(); - } else { - return true; + // Replace check if the GPS is on to check the _PWR variable + Bangle.isGPSOn = () => { + return Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0; } } diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ac47449d6..ad5f09243 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.17", + "version": "0.18", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/android/settings.js b/apps/android/settings.js index 94a1eba0b..b3057e888 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -1,12 +1,6 @@ (function(back) { - function onGpsOverwriteChange(newValue) { - if (newValue) { - Bangle.setGPSPower(false, 'android'); - } - settings.overwriteGps = newValue; - updateSettings(); - } + function gb(j) { Bluetooth.println(JSON.stringify(j)); @@ -34,7 +28,13 @@ }, /*LANG*/"Overwrite GPS" : { value : !!settings.overwriteGps, - onchange: onGpsOverwriteChange + onchange: newValue => { + if (newValue) { + Bangle.setGPSPower(false, 'android'); + } + settings.overwriteGps = newValue; + updateSettings(); + } }, /*LANG*/"Messages" : ()=>load("messages.app.js"), }; From d0d35ecec7f101d38c3db02603e1a0a202d485ff Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 14:48:46 +0000 Subject: [PATCH 44/46] renaming --- apps/messagegui/metadata.json | 2 +- apps/messages/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 7444050fd..c92fa90f5 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -1,6 +1,6 @@ { "id": "messagegui", - "name": "Messages", + "name": "Message UI", "version": "0.55", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index f94f01c26..89140efab 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -2,7 +2,7 @@ "id": "messages", "name": "Messages", "version": "0.01", - "description": "Library to handle message events", + "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", "type": "module", "tags": "tool,system", From a7364f716b488f0294d92a71e6dcf5baa0120989 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 15:03:00 +0000 Subject: [PATCH 45/46] Keep messages version rising so it'll actually update to the new version automatically --- apps/messages/ChangeLog | 2 +- apps/messages/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index b0a84783b..c984e4a0f 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1 +1 @@ -0.01: Moved messages library into standalone library \ No newline at end of file +0.55: Moved messages library into standalone library diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 89140efab..74c89b1b4 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.01", + "version": "0.55", "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", "type": "module", From e8163a6f14986d4ee47e897d94bba6daf234fb81 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 28 Nov 2022 15:04:16 +0000 Subject: [PATCH 46/46] tweaks to stop errors if removing >1 app --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index f3f54106b..f15e99fbe 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit f3f54106b3d7f84927ff734b715023a49a87cc6f +Subproject commit f15e99fbe25b2991719011e6da9bc9c7be401a7e