From 77edfe693691df6d21443d99ae71aea620973567 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Tue, 28 Nov 2023 19:30:12 +0100 Subject: [PATCH 01/67] Code cleanup + windows 11 support --- apps/presentor/app.js | 151 +++++++---------------------------- apps/presentor/metadata.json | 2 +- 2 files changed, 31 insertions(+), 122 deletions(-) diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 6b7450a0c..ec9d41258 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -1,80 +1,12 @@ // Presentor by 7kasper (Kasper Müller) -// Version 3.0 - -const SpecialReport = new Uint8Array([ - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x02, // USAGE (Mouse) - 0xa1, 0x01, // COLLECTION (Application) - 0x85, 0x01, // REPORT_ID (1) - 0x09, 0x01, // USAGE (Pointer) - 0xa1, 0x00, // COLLECTION (Physical) - 0x05, 0x09, // USAGE_PAGE (Button) - 0x19, 0x01, // USAGE_MINIMUM (Button 1) - 0x29, 0x05, // USAGE_MAXIMUM (Button 5) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x05, // REPORT_COUNT (5) - 0x75, 0x01, // REPORT_SIZE (1) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x95, 0x01, // REPORT_COUNT (1) - 0x75, 0x03, // REPORT_SIZE (3) - 0x81, 0x03, // INPUT (Cnst,Var,Abs) - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x30, // USAGE (X) - 0x09, 0x31, // USAGE (Y) - 0x09, 0x38, // USAGE (Wheel) - 0x15, 0x81, // LOGICAL_MINIMUM (-127) - 0x25, 0x7f, // LOGICAL_MAXIMUM (127) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x03, // REPORT_COUNT (3) - 0x81, 0x06, // INPUT (Data,Var,Rel) - 0x05, 0x0c, // USAGE_PAGE (Consumer Devices) - 0x0a, 0x38, 0x02, // USAGE (AC Pan) - 0x15, 0x81, // LOGICAL_MINIMUM (-127) - 0x25, 0x7f, // LOGICAL_MAXIMUM (127) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x01, // REPORT_COUNT (1) - 0x81, 0x06, // INPUT (Data,Var,Rel) - 0xc0, // END_COLLECTION - 0xc0, // END_COLLECTION - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x06, // USAGE (Keyboard) - 0xa1, 0x01, // COLLECTION (Application) - 0x85, 0x02, // REPORT_ID (2) - 0x05, 0x07, // USAGE_PAGE (Keyboard) - 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) - 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x75, 0x01, // REPORT_SIZE (1) - 0x95, 0x08, // REPORT_COUNT (8) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x01, // REPORT_COUNT (1) - 0x81, 0x01, // INPUT (Cnst,Ary,Abs) - 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) - 0x29, 0x73, // USAGE_MAXIMUM (Keyboard F24) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x73, // LOGICAL_MAXIMUM (115) - 0x95, 0x05, // REPORT_COUNT (5) - 0x75, 0x08, // REPORT_SIZE (8) - 0x81, 0x00, // INPUT (Data,Ary,Abs) - 0xc0 // END_COLLECTION -]); - -const MouseButton = { - NONE : 0, - LEFT : 1, - RIGHT : 2, - MIDDLE : 4, - BACK : 8, - FORWARD: 16 -}; - -const kb = require("ble_hid_keyboard"); +// Version 4.0 +// Imports +const bt = require("ble_hid_combo"); const Layout = require("Layout"); const Locale = require("locale"); + +// App Layout let mainLayout = new Layout({ 'type': 'v', filly: 1, @@ -174,7 +106,7 @@ let ptimers = []; function delay(t, v) { return new Promise((resolve) => { - setTimeout(resolve, t) + setTimeout(resolve, t); }); } @@ -289,56 +221,34 @@ function doPPart(r) { drawMainFrame(); } -NRF.setServices(undefined, { hid : SpecialReport }); -// TODO: figure out how to detect HID. +// Turn on Bluetooth as presentor. +NRF.setServices(undefined, { hid : bt.report }); NRF.on('HID', function() { - HIDenabled = true; + if (!HIDenabled) { + Bangle.buzz(200); + HIDenabled = true; + } }); - -function moveMouse(x,y,b,wheel,hwheel,callback) { - if (!HIDenabled) return; - if (!b) b = 0; - if (!wheel) wheel = 0; - if (!hwheel) hwheel = 0; - NRF.sendHIDReport([1,b,x,y,wheel,hwheel,0,0], function() { - if (callback) callback(); - }); -} +// +NRF.setAdvertising([ + {}, // include original Advertising packet + [ // second packet containing 'appearance' + 2, 1, 6, // standard Bluetooth flags + 3,3,0x12,0x18, // HID Service + 3,0x19,0xCA,0x03 // Appearance: Presentation Remote + ] +]); // function getSign(x) { // return ((x > 0) - (x < 0)) || +x; // } -function scroll(wheel,hwheel,callback) { - moveMouse(0,0,0,wheel,hwheel,callback); -} - -// Single click a certain button (immidiatly release). -function clickMouse(b, callback) { - if (!HIDenabled) return; - NRF.sendHIDReport([1,b,0,0,0,0,0,0], function() { - NRF.sendHIDReport([1,0,0,0,0,0,0,0], function() { - if (callback) callback(); - }); - }); -} - -function pressKey(keyCode, modifiers, callback) { - if (!HIDenabled) return; - if (!modifiers) modifiers = 0; - NRF.sendHIDReport([2, modifiers,0,keyCode,0,0,0,0], function() { - NRF.sendHIDReport([2,0,0,0,0,0,0,0], function() { - if (callback) callback(); - }); - }); -} - function handleAcc(acc) { let rRoll = acc.y * -50; let rPitch = acc.x * -100; if (mCal > 10) { //console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch)); - moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch); + bt.moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch); } else { //console.log("homeroll: " +homeRoll +"homepitch: " + homePitch); homeRoll = rRoll * 0.7 + homeRoll * 0.3; @@ -354,7 +264,7 @@ Bangle.on('lock', function(on) { }); function startHolding() { - pressKey(kb.KEY.F10); + bt.tapKey(bt.KEY.F10); holding = true; Bangle.buzz(); E.showMessage('Holding'); @@ -362,9 +272,9 @@ function startHolding() { Bangle.setLCDPower(1); } function stopHolding() { - clearTimeout(timeoutId); + clearTimeout(startHolding); if (holding) { - pressKey(kb.KEY.F10); + bt.tapKey(bt.KEY.F10); homePitch = 0; homeRoll = 0; holding = false; @@ -395,7 +305,7 @@ Bangle.on('drag', function(e) { //let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2); //let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2); let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY; - moveMouse(qX, qY, 0, 0, 0, function() { + bt.moveMouse(qX, qY, 0, 0, 0, function() { setTimeout(function() {clearToSend = true;}, 50); }); lastx = e.x; @@ -406,12 +316,12 @@ Bangle.on('drag', function(e) { if (!e.b) { // short press if (getTime() - cttl < 0.2) { - clickMouse(MouseButton.LEFT); + bt.clickButton(bt.BUTTON.LEFT); console.log("click left"); } // longer press in center else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { - clickMouse(MouseButton.RIGHT); + bt.clickButton(bt.BUTTON.RIGHT); console.log("click right"); } cttl = 0; @@ -430,11 +340,11 @@ Bangle.on('drag', function(e) { } else if(lastx > 40){ // E.showMessage('right'); //kb.tap(kb.KEY.RIGHT, 0); - scroll(-1); + bt.scroll(-1); } else if(lastx < -40){ // E.showMessage('left'); //kb.tap(kb.KEY.LEFT, 0); - scroll(1); + bt.scroll(1); } else if(lastx==0 && lasty==0 && holding == false){ // E.showMessage('press'); clickMouse(MouseButton.LEFT); @@ -452,7 +362,6 @@ Bangle.on('drag', function(e) { } }); - function onBtn() { if (trackPadMode) { trackPadMode = false; diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index 2d0a22102..f0372ecdb 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.08", + "version": "0.09", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From 7c8daaa6aa5e4404705742c0f4c446c55435b604 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Tue, 28 Nov 2023 19:34:38 +0100 Subject: [PATCH 02/67] also add changelog >.< --- apps/presentor/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 807f41e79..b87be0bb4 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -6,3 +6,4 @@ 0.06: Initial internal git(hub) release. Added icon and such. 0.07: Begin work on presentation parts. 0.08: Presentation parts! +0.09: Code cleanup and windows 11 support. From fc50f3d14f16ff005590e98433814d46ddf2df9a Mon Sep 17 00:00:00 2001 From: 7kasper Date: Tue, 28 Nov 2023 19:52:04 +0100 Subject: [PATCH 03/67] First readme fix. --- apps/presentor/README.md | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/apps/presentor/README.md b/apps/presentor/README.md index 8b22eb228..1b4ab1afc 100644 --- a/apps/presentor/README.md +++ b/apps/presentor/README.md @@ -1,2 +1,68 @@ # Presentor Use your Bangle to present! +This app basically turns your BangleJS watch into a presntor tool. +Very useful for presentations or when you are teaching a lesson. + +## Features: ++ Control your (powerpoint) slides by swiping left and right. ++ See pre-programmed presentation notes by swiping up and down. ++ See if you are on time in your presentation. ++ Get notified if you go over-time for a certain subject or the whole presentation. ++ Control cursor or spotlight in mouse mode (pressing the button). + +## Usage: +Here is a step by step guide (thanks Mu1) +1. If bluetooth connected to PC/laptop (check in bluetooth settings): +Select Bangle.js > Remove Device + +2. On your smartphone, in "Gadgetbridge", close connection by long-pressing "Bangle.js" + +3. Open banglejs.com/apps and select "Connect" (right upper corner) + +4. Select Bangle.js in blue bar in pop-up window and select "Connect" + +5. Search "Presentor" app and select diskette icon + +6. Select "Clear" to start making new notes. + +7. Fill the subjects, times in minutes (seconds optional) and Notes (optional) +The subject/times are on your watch displayed in a tall font, the notes in a very small font + +8. Select "Save" and exit the pop-up window + +9. Select "Disconnect" (right upper corner) + +10. Open the Presentor app. This will start the correct HID service as well. + +11. Open "Bluetooth & other devices" screen on PC/laptop and select "+" to add a device + +12. Select "Bluetooth" and select Bangle.js +If "Try connecting your device again." is shown, switch PC/laptop bluetooth off and on again + +13. Start your presentation. +Swipe up to start timer for first subject +Swipe down to pause +Push button to switch between mouse mode and timer/presentation mode + +14. Timer function +Next subject: Swipe up +Previous subject: Swipe down +Pause timer: Swipe down to begin + +15. Presentation function +Next presentation slide: Swipe right or just touch +Previous presentation slide: Swipe left + +16. Mouse mode +Swipe to move pointer over the presentation screen +Note: sometimes "Holding" shows in Bangle screen (see Holding mode) + +17. Holding mode +In timer/presentation mode, hold one finger on your Bangle screen, point your arm foreward +Mouse up/down on screen: Tilt your hand up/down +Mouse left/right on screen: Rotate your hand counterclockwise/clockwise + +18. End of presentation? +Switch bluetooth PC/laptop off or see step 1 + +19. Re-activate smartphone bluetooth (incl. Gadgetbridge) \ No newline at end of file From 4db81c95ef7f599f14cce8df500a568344096f3e Mon Sep 17 00:00:00 2001 From: 7kasper Date: Tue, 28 Nov 2023 20:08:39 +0100 Subject: [PATCH 04/67] Do some bugfixes on presentor app. --- apps/presentor/ChangeLog | 1 + apps/presentor/README.md | 26 +++++++++++++------------- apps/presentor/app.js | 25 +++++++++++++++++-------- apps/presentor/metadata.json | 2 +- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index b87be0bb4..031b921b9 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -7,3 +7,4 @@ 0.07: Begin work on presentation parts. 0.08: Presentation parts! 0.09: Code cleanup and windows 11 support. +0.10: Bugfixes. diff --git a/apps/presentor/README.md b/apps/presentor/README.md index 1b4ab1afc..00e13b310 100644 --- a/apps/presentor/README.md +++ b/apps/presentor/README.md @@ -1,6 +1,6 @@ # Presentor Use your Bangle to present! -This app basically turns your BangleJS watch into a presntor tool. +This app basically turns your BangleJS watch into a presentor tool. Very useful for presentations or when you are teaching a lesson. ## Features: @@ -27,6 +27,7 @@ Select Bangle.js > Remove Device 7. Fill the subjects, times in minutes (seconds optional) and Notes (optional) The subject/times are on your watch displayed in a tall font, the notes in a very small font +Note: If you don't fill in any notes you can still save and the presentor will be for input only and not give any timing details. 8. Select "Save" and exit the pop-up window @@ -40,27 +41,26 @@ The subject/times are on your watch displayed in a tall font, the notes in a ver If "Try connecting your device again." is shown, switch PC/laptop bluetooth off and on again 13. Start your presentation. -Swipe up to start timer for first subject -Swipe down to pause -Push button to switch between mouse mode and timer/presentation mode + - Swipe up to start timer for first subject + - Swipe down to pause + - Push button to switch between mouse mode and timer/presentation mode 14. Timer function -Next subject: Swipe up -Previous subject: Swipe down -Pause timer: Swipe down to begin + - Next subject: Swipe up + - Previous subject: Swipe down + - Pause timer: Swipe down to begin 15. Presentation function -Next presentation slide: Swipe right or just touch -Previous presentation slide: Swipe left + - Next presentation slide: Swipe right or just touch + - Previous presentation slide: Swipe left 16. Mouse mode -Swipe to move pointer over the presentation screen -Note: sometimes "Holding" shows in Bangle screen (see Holding mode) + - Swipe to move pointer over the presentation screen 17. Holding mode In timer/presentation mode, hold one finger on your Bangle screen, point your arm foreward -Mouse up/down on screen: Tilt your hand up/down -Mouse left/right on screen: Rotate your hand counterclockwise/clockwise + - Mouse up/down on screen: Tilt your hand up/down + - Mouse left/right on screen: Rotate your hand counterclockwise/clockwise 18. End of presentation? Switch bluetooth PC/laptop off or see step 1 diff --git a/apps/presentor/app.js b/apps/presentor/app.js index ec9d41258..bd527539f 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -272,7 +272,7 @@ function startHolding() { Bangle.setLCDPower(1); } function stopHolding() { - clearTimeout(startHolding); + clearTimeout(timeoutHolding); if (holding) { bt.tapKey(bt.KEY.F10); homePitch = 0; @@ -282,9 +282,10 @@ function stopHolding() { Bangle.removeListener('accel', handleAcc); Bangle.buzz(); drawMain(); - } else { - timeoutId = setTimeout(drawMain, 1000); - } + } + // else { + // timeoutId = setTimeout(drawMain, 1000); + // } clearTimeout(timeoutHolding); timeoutHolding = -1; } @@ -345,10 +346,12 @@ Bangle.on('drag', function(e) { // E.showMessage('left'); //kb.tap(kb.KEY.LEFT, 0); bt.scroll(1); - } else if(lastx==0 && lasty==0 && holding == false){ - // E.showMessage('press'); - clickMouse(MouseButton.LEFT); - } + } + // Todo re-implement? Seems bit buggy or unnecessary for now. + // else if(lastx==0 && lasty==0 && holding == false){ + // // E.showMessage('press'); + // bt.clickButton(bt.BUTTON.LEFT); + // } stopHolding(); lastx = 0; lasty = 0; @@ -368,9 +371,15 @@ function onBtn() { stopHolding(); drawMain(); } else { + stopHolding(); clearToSend = true; trackPadMode = true; E.showMessage('Mouse'); + // Also skip drawing thingy for now. + if (timeoutDraw != -1) { + clearTimeout(timeoutDraw); + timeoutDraw = -1; + } } Bangle.buzz(); } diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index f0372ecdb..984ba39c6 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.09", + "version": "0.10", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From 1c160d773732e4ee5d85520c18c72751a1be8511 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Tue, 28 Nov 2023 23:01:11 +0100 Subject: [PATCH 05/67] Little better readme --- apps/presentor/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/presentor/README.md b/apps/presentor/README.md index 00e13b310..9f2a411e1 100644 --- a/apps/presentor/README.md +++ b/apps/presentor/README.md @@ -3,15 +3,15 @@ Use your Bangle to present! This app basically turns your BangleJS watch into a presentor tool. Very useful for presentations or when you are teaching a lesson. -## Features: +### Features: + Control your (powerpoint) slides by swiping left and right. + See pre-programmed presentation notes by swiping up and down. + See if you are on time in your presentation. -+ Get notified if you go over-time for a certain subject or the whole presentation. ++ Get notified if you go over-time for a certain subject. + Control cursor or spotlight in mouse mode (pressing the button). -## Usage: -Here is a step by step guide (thanks Mu1) +### Usage: +Here is a step by step guide (thanks mu1) 1. If bluetooth connected to PC/laptop (check in bluetooth settings): Select Bangle.js > Remove Device @@ -65,4 +65,8 @@ In timer/presentation mode, hold one finger on your Bangle screen, point your ar 18. End of presentation? Switch bluetooth PC/laptop off or see step 1 -19. Re-activate smartphone bluetooth (incl. Gadgetbridge) \ No newline at end of file +19. Re-activate smartphone bluetooth (incl. Gadgetbridge) + +### Creator +[7kasper](https://github.com/7kasper) +Issues or feature requests are welcome on my [Github](https://github.com/7kasper/BangleApps) or on the [Espruino Forums](https://forum.espruino.com/conversations/371443/)! \ No newline at end of file From 5a400480e00cf728012018097759b6e3c399609b Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 00:18:59 +0100 Subject: [PATCH 06/67] Fix mousemove getting stuck. --- apps/presentor/ChangeLog | 1 + apps/presentor/app.js | 8 ++++++-- apps/presentor/metadata.json | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 031b921b9..9e95f156f 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -8,3 +8,4 @@ 0.08: Presentation parts! 0.09: Code cleanup and windows 11 support. 0.10: Bugfixes. +0.11: Fix mouse move getting stuck. diff --git a/apps/presentor/app.js b/apps/presentor/app.js index bd527539f..ae7b4b096 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -264,7 +264,7 @@ Bangle.on('lock', function(on) { }); function startHolding() { - bt.tapKey(bt.KEY.F10); + bt.tapKey(0, bt.MODIFY.CTRL, () => bt.tapKey(0, bt.MODIFY.CTRL)); holding = true; Bangle.buzz(); E.showMessage('Holding'); @@ -274,7 +274,8 @@ function startHolding() { function stopHolding() { clearTimeout(timeoutHolding); if (holding) { - bt.tapKey(bt.KEY.F10); + bt.tapKey(0, bt.MODIFY.CTRL); + // bt.tapKey(bt.KEY.F10); homePitch = 0; homeRoll = 0; holding = false; @@ -319,11 +320,13 @@ Bangle.on('drag', function(e) { if (getTime() - cttl < 0.2) { bt.clickButton(bt.BUTTON.LEFT); console.log("click left"); + clearToSend = true; } // longer press in center else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { bt.clickButton(bt.BUTTON.RIGHT); console.log("click right"); + clearToSend = true; } cttl = 0; lastx = 0; @@ -385,5 +388,6 @@ function onBtn() { } setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true}); +// Start App loadSettings(); drawMain(); \ No newline at end of file diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index 984ba39c6..f7d6b6063 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.10", + "version": "0.11", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From 825450f66338ac9499e0b157524574ddf9bbe122 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 00:31:20 +0100 Subject: [PATCH 07/67] Try drag mouse support --- apps/presentor/ChangeLog | 1 + apps/presentor/app.js | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 9e95f156f..19a4990bf 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -9,3 +9,4 @@ 0.09: Code cleanup and windows 11 support. 0.10: Bugfixes. 0.11: Fix mouse move getting stuck. +0.12: Added support for mouse dragging action (click then drag) diff --git a/apps/presentor/app.js b/apps/presentor/app.js index ae7b4b096..04f9de35a 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -90,6 +90,8 @@ let trackPadMode = false; let timeoutId = -1; let timeoutHolding = -1; let timeoutDraw = -1; +let timeoutSendMouse = -1; +let timeoutHoldMouse = -1; let homeRoll = 0; @@ -291,9 +293,20 @@ function stopHolding() { timeoutHolding = -1; } +function releaseMouseButtons() { + bt.releaseButton(bt.BUTTON.ALL); + clearTimeout(timeoutHoldMouse) + timeoutHoldMouse = -1; +} + Bangle.on('drag', function(e) { if (cttl == 0) { cttl = getTime(); } if (trackPadMode) { + // Upon other drag event: push holding further into the future. + if (timeoutHoldMouse != -1) { + clearTimeout(timeoutHoldMouse); + timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); + } if (lastx + lasty == 0) { lastx = e.x; lasty = e.y; @@ -308,24 +321,28 @@ Bangle.on('drag', function(e) { //let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2); let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY; bt.moveMouse(qX, qY, 0, 0, 0, function() { - setTimeout(function() {clearToSend = true;}, 50); + timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50); }); lastx = e.x; lasty = e.y; mttl = getTime(); console.log("Dx: " + (qX) + " Dy: " + (qY)); + } else if (timeoutSendMouse == -1) { // Can happen perhaps on single bluetooth failure. + timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50); } if (!e.b) { // short press if (getTime() - cttl < 0.2) { - bt.clickButton(bt.BUTTON.LEFT); + bt.holdButton(bt.BUTTON.LEFT); console.log("click left"); + timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); clearToSend = true; } // longer press in center else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { - bt.clickButton(bt.BUTTON.RIGHT); + bt.holdButton(bt.BUTTON.RIGHT); console.log("click right"); + timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); clearToSend = true; } cttl = 0; From d160a78231644b75e8eac6e56aa2ae0bb5e7470a Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 00:54:41 +0100 Subject: [PATCH 08/67] Next attempt at holding mouse --- apps/presentor/app.js | 23 +++++++++-------------- apps/presentor/metadata.json | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 04f9de35a..186f9b1ce 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -91,14 +91,13 @@ let timeoutId = -1; let timeoutHolding = -1; let timeoutDraw = -1; let timeoutSendMouse = -1; -let timeoutHoldMouse = -1; - let homeRoll = 0; let homePitch = 0; let mCal = 0; let mttl = 0; let cttl = 0; +let httl = 0; // BT helper. let clearToSend = true; @@ -293,19 +292,15 @@ function stopHolding() { timeoutHolding = -1; } -function releaseMouseButtons() { - bt.releaseButton(bt.BUTTON.ALL); - clearTimeout(timeoutHoldMouse) - timeoutHoldMouse = -1; -} - Bangle.on('drag', function(e) { if (cttl == 0) { cttl = getTime(); } if (trackPadMode) { - // Upon other drag event: push holding further into the future. - if (timeoutHoldMouse != -1) { - clearTimeout(timeoutHoldMouse); - timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); + // When we are dragging mouse release after small time amount otherwise keep dragging. + if (getTime() - httl < 0.2) { + httl = getTime(); + } else if (httl != 0) { + httl = 0; + bt.releaseButton(bt.BUTTON.ALL); } if (lastx + lasty == 0) { lastx = e.x; @@ -335,14 +330,14 @@ Bangle.on('drag', function(e) { if (getTime() - cttl < 0.2) { bt.holdButton(bt.BUTTON.LEFT); console.log("click left"); - timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); + httl = getTime(); clearToSend = true; } // longer press in center else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { bt.holdButton(bt.BUTTON.RIGHT); console.log("click right"); - timeoutHoldMouse = setTimeout(releaseMouseButtons, 200); + httl = getTime(); clearToSend = true; } cttl = 0; diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index f7d6b6063..f176a1738 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.11", + "version": "0.12", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From a0b42267ff34f51ec3a70bead552393e2a7dfb86 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 01:03:57 +0100 Subject: [PATCH 09/67] Testing ctrl keypress --- apps/presentor/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 186f9b1ce..330cd2195 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -265,7 +265,7 @@ Bangle.on('lock', function(on) { }); function startHolding() { - bt.tapKey(0, bt.MODIFY.CTRL, () => bt.tapKey(0, bt.MODIFY.CTRL)); + bt.tapKey(bt.KEY.A, bt.MODIFY.CTRL, () => bt.tapKey(bt.KEY.A, bt.MODIFY.CTRL)); holding = true; Bangle.buzz(); E.showMessage('Holding'); From ff4a57de768660eef4db7136fb360607e3de096a Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 01:16:42 +0100 Subject: [PATCH 10/67] Remove dragging and fix cntrl (hopefully) --- apps/presentor/ChangeLog | 3 ++- apps/presentor/app.js | 16 +++------------- apps/presentor/metadata.json | 2 +- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 19a4990bf..26f9ed6d7 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -9,4 +9,5 @@ 0.09: Code cleanup and windows 11 support. 0.10: Bugfixes. 0.11: Fix mouse move getting stuck. -0.12: Added support for mouse dragging action (click then drag) +0.12: Added support for mouse dragging action (click then drag). +0.13: Removed mouse dragging (too buggy/unuseful). \ No newline at end of file diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 330cd2195..27403c919 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -97,7 +97,6 @@ let homePitch = 0; let mCal = 0; let mttl = 0; let cttl = 0; -let httl = 0; // BT helper. let clearToSend = true; @@ -258,14 +257,14 @@ function handleAcc(acc) { } } Bangle.on('lock', function(on) { - if (on && holding) { + if (on && (holding || trackPadMode)) { Bangle.setLocked(false); Bangle.setLCDPower(1); } }); function startHolding() { - bt.tapKey(bt.KEY.A, bt.MODIFY.CTRL, () => bt.tapKey(bt.KEY.A, bt.MODIFY.CTRL)); + bt.tapKey(0xE0, () => bt.tapKey(0xE0)); holding = true; Bangle.buzz(); E.showMessage('Holding'); @@ -275,7 +274,7 @@ function startHolding() { function stopHolding() { clearTimeout(timeoutHolding); if (holding) { - bt.tapKey(0, bt.MODIFY.CTRL); + bt.tapKey(0xE0); // bt.tapKey(bt.KEY.F10); homePitch = 0; homeRoll = 0; @@ -295,13 +294,6 @@ function stopHolding() { Bangle.on('drag', function(e) { if (cttl == 0) { cttl = getTime(); } if (trackPadMode) { - // When we are dragging mouse release after small time amount otherwise keep dragging. - if (getTime() - httl < 0.2) { - httl = getTime(); - } else if (httl != 0) { - httl = 0; - bt.releaseButton(bt.BUTTON.ALL); - } if (lastx + lasty == 0) { lastx = e.x; lasty = e.y; @@ -330,14 +322,12 @@ Bangle.on('drag', function(e) { if (getTime() - cttl < 0.2) { bt.holdButton(bt.BUTTON.LEFT); console.log("click left"); - httl = getTime(); clearToSend = true; } // longer press in center else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { bt.holdButton(bt.BUTTON.RIGHT); console.log("click right"); - httl = getTime(); clearToSend = true; } cttl = 0; diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index f176a1738..babb4b14a 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.12", + "version": "0.13", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From 65fa387739fc35b1be7be83a0facab6f1ec95504 Mon Sep 17 00:00:00 2001 From: 7kasper Date: Wed, 29 Nov 2023 11:20:07 +0100 Subject: [PATCH 11/67] Fixes and add spotlight and default screen --- apps/presentor/ChangeLog | 3 +- apps/presentor/README.md | 12 ++++++-- apps/presentor/app.js | 57 ++++++++++++++++++++++++------------ apps/presentor/metadata.json | 2 +- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 26f9ed6d7..a42c6594d 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -10,4 +10,5 @@ 0.10: Bugfixes. 0.11: Fix mouse move getting stuck. 0.12: Added support for mouse dragging action (click then drag). -0.13: Removed mouse dragging (too buggy/unuseful). \ No newline at end of file +0.13: Removed mouse dragging (too buggy/unuseful). +0.14: Bugfix and add pointer mode. Also added default screen when no parts are there. \ No newline at end of file diff --git a/apps/presentor/README.md b/apps/presentor/README.md index 9f2a411e1..37d3e0009 100644 --- a/apps/presentor/README.md +++ b/apps/presentor/README.md @@ -56,16 +56,24 @@ If "Try connecting your device again." is shown, switch PC/laptop bluetooth off 16. Mouse mode - Swipe to move pointer over the presentation screen + - Tap to left click. + - Hold longer to right click. 17. Holding mode In timer/presentation mode, hold one finger on your Bangle screen, point your arm foreward - Mouse up/down on screen: Tilt your hand up/down - Mouse left/right on screen: Rotate your hand counterclockwise/clockwise -18. End of presentation? +18. Spotlight mode + - Dubble press button to go into presentor mode. + - Allows to move mouse but no clicking. + - Presses SHIFT+F10 on start (you can use app such as powertoys to set spotlight to this combo) + - Presses F10 at the end (will dis-engage spotlight for instance in powertoys) + +19. End of presentation? Switch bluetooth PC/laptop off or see step 1 -19. Re-activate smartphone bluetooth (incl. Gadgetbridge) +20. Re-activate smartphone bluetooth (incl. Gadgetbridge) ### Creator [7kasper](https://github.com/7kasper) diff --git a/apps/presentor/app.js b/apps/presentor/app.js index 27403c919..a501b3376 100644 --- a/apps/presentor/app.js +++ b/apps/presentor/app.js @@ -1,5 +1,5 @@ // Presentor by 7kasper (Kasper Müller) -// Version 4.0 +// Version 0.14 // Imports const bt = require("ble_hid_combo"); @@ -85,6 +85,7 @@ let lasty = 0; // Mouse states let holding = false; let trackPadMode = false; +let focusMode = false; // Timeout IDs. let timeoutId = -1; @@ -97,6 +98,7 @@ let homePitch = 0; let mCal = 0; let mttl = 0; let cttl = 0; +let bttl = 0; // BT helper. let clearToSend = true; @@ -198,6 +200,11 @@ function drawMain() { function doPPart(r) { pparti += r; + if (settings.pparts.length == 0) { + mainLayout.Subject.label = 'PRESENTOR'; + mainLayout.Notes.label = ''; + return; + } if (pparti < 0) { pparti = -1; mainLayout.Subject.label = 'PAUSED'; @@ -264,21 +271,22 @@ Bangle.on('lock', function(on) { }); function startHolding() { - bt.tapKey(0xE0, () => bt.tapKey(0xE0)); + bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT); holding = true; + focusMode = true; Bangle.buzz(); E.showMessage('Holding'); Bangle.on('accel', handleAcc); Bangle.setLCDPower(1); } function stopHolding() { - clearTimeout(timeoutHolding); if (holding) { - bt.tapKey(0xE0); + bt.tapKey(bt.KEY.F10); // bt.tapKey(bt.KEY.F10); homePitch = 0; homeRoll = 0; holding = false; + focusMode = false; mCal = 0; Bangle.removeListener('accel', handleAcc); Bangle.buzz(); @@ -318,17 +326,17 @@ Bangle.on('drag', function(e) { timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50); } if (!e.b) { - // short press - if (getTime() - cttl < 0.2) { - bt.holdButton(bt.BUTTON.LEFT); - console.log("click left"); - clearToSend = true; - } - // longer press in center - else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { - bt.holdButton(bt.BUTTON.RIGHT); - console.log("click right"); - clearToSend = true; + if (!focusMode) { + // short press + if (getTime() - cttl < 0.2) { + bt.clickButton(bt.BUTTON.LEFT); + console.log("click left"); + } + // longer press in center + else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) { + bt.clickButton(bt.BUTTON.RIGHT); + console.log("click right"); + } } cttl = 0; lastx = 0; @@ -372,9 +380,19 @@ Bangle.on('drag', function(e) { function onBtn() { if (trackPadMode) { - trackPadMode = false; - stopHolding(); - drawMain(); + if ((getTime() - bttl < 0.4 && !focusMode)) { + E.showMessage('Pointer'); + focusMode = true; + bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT); + } else { + trackPadMode = false; + stopHolding(); + drawMain(); + if (focusMode) { + bt.tapKey(bt.KEY.F10); + focusMode = false; + } + } } else { stopHolding(); clearToSend = true; @@ -384,7 +402,8 @@ function onBtn() { if (timeoutDraw != -1) { clearTimeout(timeoutDraw); timeoutDraw = -1; - } + } + bttl = getTime(); } Bangle.buzz(); } diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index babb4b14a..e44684fda 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "0.13", + "version": "0.14", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", From e96ffad55f8fd27ac9a496ab7bf3aaad3a472aea Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 30 Nov 2023 21:00:05 +0100 Subject: [PATCH 12/67] openstmap: fix if no map Display message if no map is installed --- apps/openstmap/ChangeLog | 3 ++- apps/openstmap/app.js | 7 ++++++- apps/openstmap/metadata.json | 2 +- apps/openstmap/openstmap.js | 8 +++++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 10b29fd3b..1b0edaf34 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -30,4 +30,5 @@ 0.23: Bugfix: Enable Compass if needed 0.24: Allow zooming by clicking the screen 0.25: Enable scaled image filtering on 2v19+ firmware -0.26: Ensure that when redrawing, we always cancel any in-progress track draw \ No newline at end of file +0.26: Ensure that when redrawing, we always cancel any in-progress track draw +0.27: Display message if no map is installed diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 1f4b0b8b7..d66936c29 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -268,7 +268,12 @@ function showMap() { }, btn: () => showMenu() }); } -showMap(); + +if (m.maps.length === 0) { + E.showPrompt(/*LANG*/'Please upload a map first.', {buttons : {/*LANG*/"Ok":true}}).then(v => load()); +} else { + showMap(); +} // Write settings on exit via button setWatch(() => writeSettings(), BTN, { repeat: true, edge: "rising" }); diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 29278cbc3..75359d3fa 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,7 +2,7 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.26", + "version": "0.27", "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", diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index 0a36b829e..9c7c9697d 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -33,9 +33,11 @@ m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{ m.maps.sort((a,b) => b.scale-a.scale); // sort by scale so highest resolution is drawn last // 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 +if (m.map) { + 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 +} // return number of tiles drawn exports.draw = function() { From 68b1a36780a180894f2dbf1fd71edffa1e3532f1 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 30 Nov 2023 22:20:48 +0100 Subject: [PATCH 13/67] widmp: Fix variable definitions --- apps/widmp/ChangeLog | 1 + apps/widmp/metadata.json | 2 +- apps/widmp/widget.js | 15 ++++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index a51ac080a..eb5876a36 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -6,3 +6,4 @@ 0.06: Darkmode, custom colours, and fix a bug with acting on mylocation changes 0.07: Use default Bangle formatter for booleans 0.08: Better formula for the moon's phase +0.09: Fix variable definitions diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json index 654b5a383..a334ec27e 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,7 +1,7 @@ { "id": "widmp", "name": "Moon Phase", - "version": "0.08", + "version": "0.09", "description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "icon": "widget.png", "type": "widget", diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index e5aa7fef2..89c072ca1 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -6,11 +6,11 @@ // https://github.com/deirdreobyrne/LunarPhase function moonPhase(sec) { - d = (4.847408287988257 + sec/406074.7465115577) % (2.0*Math.PI); - m = (6.245333801867877 + sec/5022682.784840698) % (2.0*Math.PI); - l = (4.456038755040014 + sec/378902.2499653011) % (2.0*Math.PI); - t = d+1.089809730923715e-01 * Math.sin(l)-3.614132757006379e-02 * Math.sin(m)+2.228248661252023e-02 * Math.sin(d+d-l)+1.353592753655652e-02 * Math.sin(d+d)+4.238560208195022e-03 * Math.sin(l+l)+1.961408105275610e-03 * Math.sin(d); - k = (1.0 - Math.cos(t))/2.0; + let d = (4.847408287988257 + sec/406074.7465115577) % (2.0*Math.PI); + let m = (6.245333801867877 + sec/5022682.784840698) % (2.0*Math.PI); + let l = (4.456038755040014 + sec/378902.2499653011) % (2.0*Math.PI); + let t = d+1.089809730923715e-01 * Math.sin(l)-3.614132757006379e-02 * Math.sin(m)+2.228248661252023e-02 * Math.sin(d+d-l)+1.353592753655652e-02 * Math.sin(d+d)+4.238560208195022e-03 * Math.sin(l+l)+1.961408105275610e-03 * Math.sin(d); + let k = (1.0 - Math.cos(t))/2.0; if ((t >= Math.PI) && (t < 2.0*Math.PI)) { k = -k; } @@ -19,7 +19,7 @@ function loadLocation() { // "mylocation.json" is created by the "My Location" app - location = require("Storage").readJSON("mylocation.json",1)||{"lat":50.1236,"lon":8.6553,"location":"Frankfurt"}; + let location = require("Storage").readJSON("mylocation.json",1)||{"lat":50.1236,"lon":8.6553,"location":"Frankfurt"}; southernHemisphere = (location.lat < 0); } @@ -63,12 +63,13 @@ function draw() { const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; + let leftFactor, rightFactor; loadLocation(); g.reset().setColor(g.theme.bg); g.fillRect(CenterX - Radius, CenterY - Radius, CenterX + Radius, CenterY + Radius); - millis = (new Date()).getTime(); + let millis = (new Date()).getTime(); if ((millis - lastCalculated) >= 7000000) { // if it's more than 7,000 sec since last calculation, re-calculate! phase = moonPhase(millis/1000); lastCalculated = millis; From 4f38e475a8f1a78f4a77529690a7142097f4a14e Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 30 Nov 2023 22:24:26 +0100 Subject: [PATCH 14/67] fix ChangeLog --- apps/widmp/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index eb5876a36..56cc64be1 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -6,4 +6,4 @@ 0.06: Darkmode, custom colours, and fix a bug with acting on mylocation changes 0.07: Use default Bangle formatter for booleans 0.08: Better formula for the moon's phase -0.09: Fix variable definitions +0.09: Fix variable declaration From e1eccad87e274bc7869f849dcb5964866c01c5d1 Mon Sep 17 00:00:00 2001 From: Flaparoo <629229+flaparoo@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:51:20 +0800 Subject: [PATCH 15/67] New app: coloursdemo --- apps/coloursdemo/ChangeLog | 1 + apps/coloursdemo/README.md | 22 +++++ apps/coloursdemo/coloursdemo-icon.js | 1 + apps/coloursdemo/coloursdemo.app.js | 128 +++++++++++++++++++++++++++ apps/coloursdemo/coloursdemo.png | Bin 0 -> 2234 bytes apps/coloursdemo/metadata.json | 17 ++++ apps/coloursdemo/screenshot.png | Bin 0 -> 5901 bytes 7 files changed, 169 insertions(+) create mode 100644 apps/coloursdemo/ChangeLog create mode 100644 apps/coloursdemo/README.md create mode 100644 apps/coloursdemo/coloursdemo-icon.js create mode 100644 apps/coloursdemo/coloursdemo.app.js create mode 100644 apps/coloursdemo/coloursdemo.png create mode 100644 apps/coloursdemo/metadata.json create mode 100644 apps/coloursdemo/screenshot.png diff --git a/apps/coloursdemo/ChangeLog b/apps/coloursdemo/ChangeLog new file mode 100644 index 000000000..d44ed23f0 --- /dev/null +++ b/apps/coloursdemo/ChangeLog @@ -0,0 +1 @@ +1.00: first release diff --git a/apps/coloursdemo/README.md b/apps/coloursdemo/README.md new file mode 100644 index 000000000..b0fdc6f6b --- /dev/null +++ b/apps/coloursdemo/README.md @@ -0,0 +1,22 @@ +# Colours Demo + +This is a simple app to demonstrate colours on a Bangle 2. + +The colours are "optimised" for the Bangle 2's 3-bit display. They only include values which use either the full, half or no primary RGB colour, which should reduce the artifacts due to dithering (the exception are light and dark grey). + +![](screenshot.png) + +Use this app for choosing colours for your own project, and copy the colour definitions from the source code. + + +## Use colours in other projects + +Copy-and-paste the colour constants to be used in your own app from `coloursdemo.app.js`. They are sandwiched between the "BEGIN" and "END" comments at the beginning of the file. + +With the constants available in your own code, you can for example set the foreground colour to yellow with: + + g.setColor(COLOUR_YELLOW); + +This works for any graphics call requiring a colour value (like `g.setBgColor()`). + + diff --git a/apps/coloursdemo/coloursdemo-icon.js b/apps/coloursdemo/coloursdemo-icon.js new file mode 100644 index 000000000..8c58b0b19 --- /dev/null +++ b/apps/coloursdemo/coloursdemo-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AC8sAAYqpmVdr2Irwvklkzq4qBx4ADxAvDM0EyxAABFwgABF4k5rsyGTksF5MzBwdjAAVdnIzCF69dF5FdEYUyF4YADGQSPVF5LwCRwIvHAAIvVllXF5DwCRwgAFNobwbxFeEISOIAAMzF6zwCsgqBDoMsmUzWQMzF5MyeC4lBEwM5nNAsgABGgMyX5JeWF4IsBFYYADnIvBHgJmBrouDBYIvZnIvHLwIABnBvCMwSOXeAQvImU4F4QADMwReXF5csFwwxDF7IlCYAqOEF44uYF5MzF5ReZR4LwBF4qOKnAvalgvBYAk6RxYvaeAs6EYK+lMAZOBlgtBAQS+jF4QoBSQQjBGRKOcF4YjCMgM4AAIyCBoaOcF4YwCAYIvCGQxeceAQvDGoIvFGQYveSAguJF8iOHAAYueF4iOqeAksRyz8CAAzwNR1RgDMQZeIADJ0JqwmCGQoFB0gAEq2A5wAG0ky54AFrowGFQVXAAIyGmVWF8VWF4QyGlmAF8QsDLYIyFFwovbGAIuDSoqOHF8CJCF4aOHF7q/CqyVEAoIuGF7hgEAAiOIF7xhDYgiOHF7oxDXwLyCRxAvfGAYAhF5QA/AH4AEA")) diff --git a/apps/coloursdemo/coloursdemo.app.js b/apps/coloursdemo/coloursdemo.app.js new file mode 100644 index 000000000..ffc86b97e --- /dev/null +++ b/apps/coloursdemo/coloursdemo.app.js @@ -0,0 +1,128 @@ +/* + * Demonstrate colours + */ + + +// BEGIN colour constants +const COLOUR_BLACK = 0x0000; // same as: g.setColor(0, 0, 0) +const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25) +const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5) +const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75) +const COLOUR_WHITE = 0xffff; // same as: g.setColor(1, 1, 1) + +const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0) +const COLOUR_GREEN = 0x07e0; // same as: g.setColor(0, 1, 0) +const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1) +const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0) +const COLOUR_MAGENTA = 0xf81f; // same as: g.setColor(1, 0, 1) +const COLOUR_CYAN = 0x07ff; // same as: g.setColor(0, 1, 1) + +const COLOUR_LIGHT_RED = 0xfc10; // same as: g.setColor(1, 0.5, 0.5) +const COLOUR_LIGHT_GREEN = 0x87f0; // same as: g.setColor(0.5, 1, 0.5) +const COLOUR_LIGHT_BLUE = 0x841f; // same as: g.setColor(0.5, 0.5, 1) +const COLOUR_LIGHT_YELLOW = 0xfff0; // same as: g.setColor(1, 1, 0.5) +const COLOUR_LIGHT_MAGENTA = 0xfc1f; // same as: g.setColor(1, 0.5, 1) +const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1) + +const COLOUR_DARK_RED = 0x8000; // same as: g.setColor(0.5, 0, 0) +const COLOUR_DARK_GREEN = 0x0400; // same as: g.setColor(0, 0.5, 0) +const COLOUR_DARK_BLUE = 0x0010; // same as: g.setColor(0, 0, 0.5) +const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0) +const COLOUR_DARK_MAGENTA = 0x8010; // same as: g.setColor(0.5, 0, 0.5) +const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5) + +const COLOUR_PINK = 0xf810; // same as: g.setColor(1, 0, 0.5) +const COLOUR_LIMEGREEN = 0x87e0; // same as: g.setColor(0.5, 1, 0) +const COLOUR_ROYALBLUE = 0x041f; // same as: g.setColor(0, 0.5, 1) +const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0) +const COLOUR_INDIGO = 0x801f; // same as: g.setColor(0.5, 0, 1) +const COLOUR_TURQUOISE = 0x07f0; // same as: g.setColor(0, 1, 0.5) +// END colour constants + + +// array of colours to be demoed: +// [ colour value, label colour, label ] +const demo = [ + [ COLOUR_LIGHT_RED, COLOUR_BLACK, 'LIGHT RED' ], + [ COLOUR_RED, COLOUR_WHITE, 'RED' ], + [ COLOUR_DARK_RED, COLOUR_WHITE, 'DARK RED' ], + + [ COLOUR_LIGHT_YELLOW, COLOUR_BLACK, 'LIGHT YELLOW' ], + [ COLOUR_YELLOW, COLOUR_BLACK, 'YELLOW' ], + [ COLOUR_DARK_YELLOW, COLOUR_WHITE, 'DARK YELLOW' ], + + [ COLOUR_LIGHT_GREEN, COLOUR_BLACK, 'LIGHT GREEN' ], + [ COLOUR_GREEN, COLOUR_BLACK, 'GREEN' ], + [ COLOUR_DARK_GREEN, COLOUR_WHITE, 'DARK GREEN' ], + + [ COLOUR_LIGHT_CYAN, COLOUR_BLACK, 'LIGHT CYAN' ], + [ COLOUR_CYAN, COLOUR_BLACK, 'CYAN' ], + [ COLOUR_DARK_CYAN, COLOUR_WHITE, 'DARK CYAN' ], + + [ COLOUR_LIGHT_BLUE, COLOUR_BLACK, 'LIGHT BLUE' ], + [ COLOUR_BLUE, COLOUR_WHITE, 'BLUE' ], + [ COLOUR_DARK_BLUE, COLOUR_WHITE, 'DARK BLUE' ], + + [ COLOUR_LIGHT_MAGENTA, COLOUR_BLACK, 'LIGHT MAGENTA' ], + [ COLOUR_MAGENTA, COLOUR_WHITE, 'MAGENTA' ], + [ COLOUR_DARK_MAGENTA, COLOUR_WHITE, 'DARK MAGENTA' ], + + [ COLOUR_LIMEGREEN, COLOUR_BLACK, 'LIMEGREEN' ], + [ COLOUR_TURQUOISE, COLOUR_BLACK, 'TURQUOISE' ], + [ COLOUR_ROYALBLUE, COLOUR_WHITE, 'ROYALBLUE' ], + + [ COLOUR_ORANGE, COLOUR_BLACK, 'ORANGE' ], + [ COLOUR_PINK, COLOUR_WHITE, 'PINK' ], + [ COLOUR_INDIGO, COLOUR_WHITE, 'INDIGO' ], + + [ COLOUR_LIGHT_GREY, COLOUR_BLACK, 'LIGHT GREY' ], + [ COLOUR_GREY, COLOUR_BLACK, 'GREY' ], + [ COLOUR_DARK_GREY, COLOUR_WHITE, 'DARK GREY' ], + + [ COLOUR_WHITE, COLOUR_BLACK, 'WHITE' ], + [ COLOUR_BLACK, COLOUR_WHITE, 'BLACK' ], +]; + +const columns = 3; +const rows = 10; + + +// initialise +g.clear(reset); +g.setFont('6x8').setFontAlign(-1, -1); + +// calc some values required to draw the grid +const colWidth = Math.floor(g.getWidth() / columns); +const rowHeight = Math.floor(g.getHeight() / rows); +const xStart = Math.floor((g.getWidth() - (columns * colWidth)) / 2); +var x = xStart; +var y = Math.floor((g.getHeight() - (rows * rowHeight)) / 2); + +// loop through the colours to be demoed +for (var idx in demo) { + var colour = demo[idx][0]; + var labelColour = demo[idx][1]; + var label = demo[idx][2]; + + // draw coloured box + g.setColor(colour).fillRect(x, y, x + colWidth - 1, y + rowHeight - 1); + + // label it + g.setColor(labelColour).drawString(g.wrapString(label, colWidth).join("\n"), x, y); + + x += colWidth; + if ((x + colWidth) >= g.getWidth()) { + x = xStart; + y += rowHeight; + } +} + +// there's an "unused" box left - cross it out +g.setColor(COLOUR_RED); +g.drawLine(x, y, x + colWidth - 1, y + rowHeight - 1); +g.drawLine(x, y + rowHeight - 1, x + colWidth - 1, y); + + +// exit on button press +setWatch(e => { Bangle.showClock(); }, BTN1); + diff --git a/apps/coloursdemo/coloursdemo.png b/apps/coloursdemo/coloursdemo.png new file mode 100644 index 0000000000000000000000000000000000000000..305ccc9b8515ede1b583f61bc1bd7c5394649a1b GIT binary patch literal 2234 zcmV;r2u1gaP)8$dg(dPPu;&6cB zFNccA;?WoUfsJuukawcQZqx}%IM3N0E@|Ec z-V9c{{j>a?u5+R=77#72WSD`*IDr~ zniIs%f0t0R#MdP0{%J0q44U92cYW8A=PUy zAA%Kc?E$X0Cz7_Zea$e}$I&JugUh~lqP!=3aV6pLu1L&>FzU56jRe+w0@56z+|Ckn_@9L*O^EkM` z=K4Nat;&8g5Enr4p?;tR`L0Hz`Nk6mpv+jzTh|&MYvg^mZ%+z6c;}89oQ(dO7Y;_q zszaI4CPR^HIG*st*-afmokbeZ%!C%QDAj0psQ~Tg&O+z4j&)T=)P=Z+r%&H*cy=Z|)C~SxBy^4G257LfLO)myzS0y`=2?WYg2k zl$dn;-VUoS3_{B4da`${*UY4}X33Ydz9m8ch)ZrErRcXz-NZ)3zz>Kdjwi8Osg%PT*fQ2>fX7F}^&XbhnE>XoSBi$A^sG>5@{pgTIi z|Cp2+ZIy4#Xm}!uQ+`Zw7}(bjs{jQb{5~oGyYCnzt*VPFJaI~O-3Y}>ZJOB-ZFk+v zerZ4jSbF|kRDi{vt&q{UF|P3WM|#QbJ32FGpeWK6c|J^U=~Dr$r%z1@U-Id7U~DyD zd8H#R0Jg7{0Q_r4cHd~CINW2BJ5&Jv#IY#=!tr-OU+h*JH6DO8w=YEV(tgcsNz0-Y zhH~GdM!t3p3NL;H{OhM6>-kr~SiW&It;QFLxBhj7%7cVFR6J_{Os>3u;&H^}=>p?Q zUxc>GVT!{MLrMKeir5^acnroBZpdzK0_#9m%<#hAPM|nEYP3{-ch&$jZEa6cJRUWy zsDWgs2Qpf_KzQvS2#1FtuYV8F77QkYKwlz_P@ELe7bzp@b-oa{vmFYKOa{o@v#f{d1_;iH*=(iZe5&0L5wV-q=DjsdWH zUxCc_?Vw*ip?n@=V>t?FS*dqU02bI>-=;Vzyr{N81<2ji2PxG}km8J9K2zK!0%bt~ za#^OCD={|tZ^_R+Y~-w-E?}%Oq;H6C^wYc|&^-E*tY~uvKwI&zjceR0z+%q^V3x}2 z$i@SZTqTBS9vzPNv$STML7CAdiba>>a1H5EZJy+g`A;cMtT&XpA_Ty=$^}W4Qrz{= z*rf-QS!yA@6pytz*qp$k4GAo6e4Hs3E!Uc*BJ!6!JMCB01hd34RymX+)&gUb!BB)F zWMx^hId=GrCPeN|!C*oDXr@^d*7H&*$tw9{*&yIiatE7am;KXO$K%kn$8<(;{z$#B zFchxY2&si`KgFVmVJRL-sYVe}>ty|(w0t52wh9SO4=7Q+dL0h*ZLNoUS6gEG z!;_mJtxyRBzBmcfxw)e|q-D{T>HC01cT=n)WE6S8IAz>U$;u5zwrn=|_Jq0E+Y$9GVz1tn4>`k_|(D&wK)KIKIWO z_}?p^YaI{jaBc=2k0H^jK$YA+{(2>Dg(NG6Q+t*913*M`lFw+71Elt&HS(BqN@~N^ zV(j{#697$H+fxjOhscsySlI~92@S25PD3wP%6&kZ{m6^TJOuc!w0X#*{w6?fq-`?f zmDWLSX&o2@40`_I>yHaRrvOA$9?qv(bZZRckx`I}aeSC)Mmy)A95N%A#71^X8-^I07*qo IM6N<$f?{4Svj6}9 literal 0 HcmV?d00001 diff --git a/apps/coloursdemo/metadata.json b/apps/coloursdemo/metadata.json new file mode 100644 index 000000000..8c34d7ea8 --- /dev/null +++ b/apps/coloursdemo/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "coloursdemo", + "name": "Colours Demo", + "shortName":"Colours Demo", + "version":"1.00", + "description": "Simple app to demonstrate colours", + "icon": "coloursdemo.png", + "screenshots": [{ "url": "screenshot.png" }], + "type": "app", + "tags": "tool", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + { "name":"coloursdemo.app.js", "url":"coloursdemo.app.js" }, + { "name":"coloursdemo.img", "url":"coloursdemo-icon.js", "evaluate":true } + ] +} diff --git a/apps/coloursdemo/screenshot.png b/apps/coloursdemo/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..350011b3f1826f3ea978b1f3d4cf678cbd3e2d5f GIT binary patch literal 5901 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 Date: Fri, 1 Dec 2023 08:52:56 +0000 Subject: [PATCH 16/67] Fix errant trailing spaces for long time output in some locales --- apps/locale/locales.js | 58 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/apps/locale/locales.js b/apps/locale/locales.js index f36c6725e..797b8bf8e 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -71,7 +71,7 @@ var locales = { distance: { "0": "yd", "1": "mi" }, temperature: '°C', ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short) abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -90,7 +90,7 @@ var locales = { distance: { 0: "ft", 1: "mi" }, temperature: "°F", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d, %Y", 1: "%m/%d/%y" }, abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -109,7 +109,7 @@ var locales = { distance: { 0: "ft", 1: "mi" }, temperature: "°F", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d, %Y", 1: "%Y-%m-%d" }, abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -128,7 +128,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %b %Y", 1: "%d/%m/%Y" }, // 28 Feb 2020" // "28/03/2020"(short) abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -147,7 +147,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %b %Y", 1: "%d/%m/%Y" }, // 28 Feb 2020" // "28/03/2020"(short) abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -165,7 +165,7 @@ var locales = { distance: { "0": "m", "1": "nm" }, temperature: '°C', ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short) abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -202,7 +202,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%Y/%m/%d", 1: "%y/%m/%d" }, abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -239,7 +239,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short) abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -256,7 +256,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %B %d, %Y", "1": "%Y-%m-%d" }, // Sunday, March 1, 2020 // 2012-12-20 abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -274,7 +274,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %B %Y", "1": "%d/%m/%Y" }, // 1 mars 2020 // 01/03/2020 abmonth: "janv,févr,mars,avril,mai,juin,juil,août,sept,oct,nov,déc", month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", @@ -292,7 +292,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "fm", 1: "em" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%b %d %Y", "1": "%Y-%m-%d" }, // feb 1 2020 // 2020-03-01 abmonth: "jan,feb,mars,apr,maj,juni,juli,aug,sep,okt,nov,dec", month: "januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december", @@ -310,7 +310,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%B %d %Y", "1": "%Y-%m-%d" }, // March 1 2020 // 2020-03-01 abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -328,7 +328,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%y" }, // Sunday, 1 March 2020 // 1/3/20 abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -346,7 +346,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%y" }, // Sunday, 1 March 2020 // 1/3/20 abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -364,7 +364,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%y" }, // Sonntag, 1. März 2020 // 01.03.20 abmonth: "Jän,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", month: "Jänner,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember", @@ -383,7 +383,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020 abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", month: "January,February,March,April,May,June,July,August,September,October,November,December", @@ -401,7 +401,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %d de %B de %Y", "1": "%d/%m/%y" }, // domingo, 1 de marzo de 2020 // 01/03/20 abmonth: "ene,feb,mar,abr,may,jun,jul,ago,sept,oct,nov,dic", month: "enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre", @@ -420,7 +420,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20 abmonth: "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", @@ -438,7 +438,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "ap", 1: "ip" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, // 17.00.00 // 17.00 + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, // 17.00.00 // 17.00 datePattern: { 0: "%A %d. %B %Y", "1": "%-d/%-m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020 abmonth: "tammik,helmik,maalisk,huhtik,toukok,kesäk,heinäk,elok,syysk,lokak,marrask,jouluk", month: "tammikuuta,helmikuuta,maaliskuuta,huhtikuuta,toukokuuta,kesäkuuta,heinäkuuta,elokuuta,syyskuuta,lokakuuta,marraskuuta,joulukuuta", @@ -492,7 +492,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00 + timePattern: { 0: "%HH:%MM.%SS", 1: "%HH:%MM" }, // 17:00.00 // 17:00 datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020 abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", @@ -510,7 +510,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00 + timePattern: { 0: "%HH:%MM.%SS", 1: "%HH:%MM" }, // 17:00.00 // 17:00 datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020 abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic", month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre", @@ -528,7 +528,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00 + timePattern: { 0: "%HH.%MM.%SS", 1: "%HH.%MM" }, // 17.00.00 // 17.00 datePattern: { 0: "%A, %d. %B %Y", "1": "%Y-%m-%d" }, // Sunntag, 1. Märze 2020 // 2020-03-01 abmonth: "Jen,Hor,Mär,Abr,Mei,Brá,Hei,Öig,Her,Wím,Win,Chr", month: "Jenner,Hornig,Märze,Abrille,Meije,Bráčet,Heiwet,Öigšte,Herbštmánet,Wímánet,Wintermánet,Chrištmánet", @@ -546,7 +546,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "öö", 1: "ös" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %w %Y %A", 1: "%d/%m/%Y" }, // 1 Mart 2020 Pazar // "01/03/2020" abmonth: "Oca,Sub,Mar,Nis,May,Haz,Tem,Agu,Eyl,Eki,Kas,Ara", month: "Ocak,Subat,Mart,Nisan,Mayis,Haziran,Temmuz,Agustos,Eylul,Ekim,Kasim,Aralik", @@ -564,7 +564,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "de", 1: "du" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%Y %b %d, %A", 1: "%Y.%m.%d" }, // 2020 Feb 28, Péntek" // "2020.03.01."(short) abmonth: "Jan,Feb,Már,Ápr,Máj,Jún,Júl,Aug,Szep,Okt,Nov,Dec", month: "Január,Február,Március,Április,Május,Június,Július,Augusztus,Szeptember,Október,November,December", @@ -582,7 +582,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "", 1: "" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A %d %B de %Y", "1": "%d/%m/%Y" }, // dimenge 1 de març de 2020 // 01/03/2020 abmonth: "gen.,febr.,març,abril,mai,junh,julh,ago.,set.,oct.,nov.,dec.", month: "genièr,febrièr,març,abril,mai,junh,julhet,agost,setembre,octòbre,novembre,decembre", @@ -600,7 +600,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %b %Y", 1: "%d/%m/%y" }, abmonth: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", month: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro", @@ -618,7 +618,7 @@ var locales = { distance: { "0": "m", "1": "km" }, temperature: '°C', ampm: { 0: "dop", 1: "odp" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d. %b %Y", 1: "%d.%m.%Y" }, // "30. led 2020" // "30.01.2020"(short) abmonth: "led,úno,bře,dub,kvě,čvn,čvc,srp,zář,říj,lis,pro", month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec", @@ -672,7 +672,7 @@ var locales = { distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %b %Y", 1: "%d/%m/%y" }, abmonth: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez", month: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro", @@ -764,7 +764,7 @@ var locales = { distance: { 0: "מ׳", 1: "ק״מ" }, temperature: "°C", ampm: {0:"am",1:"pm"}, - timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020 abmonth: "ינו,פבר,מרץ,אפר,מאי,יונ,יול,אוג,ספט,אוק,נוב,דצמ", month: "ינואר,פברואר,מרץ,אפריל,מאי,יוני,יולי,אוגוסט,ספטמבר,אוקטובר,נובמבר,דצמבר", From 9614c7f29a4be4707afc855687264d7100759732 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sat, 18 Nov 2023 01:13:41 +0100 Subject: [PATCH 17/67] recorder:Period 1s + Log GPS =>record on GPS event recorder: writeInterval becomes writeSetup recorder:change according to some Web IDE warnings Update apps/recorder/widget.js Co-authored-by: Rob Pilling recorder: use `typeof writeSetup === "number"` --- apps/recorder/ChangeLog | 2 ++ apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 20 ++++++++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 945ceeaf8..dadd3fbcb 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -43,3 +43,5 @@ 0.34: Avoid prompting when creating a new file (#3081) 0.35: Handle loading without a settings file (default record setting) 0.36: When recording with 1 second periods, log time with one decimal. +0.37: 1 second periods + gps log => log when gps event is received, not with + setInterval. diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 9d75c734e..b0f42e1b4 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.36", + "version": "0.37", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget,clkinfo", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 2de972aee..057130ff0 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -2,7 +2,7 @@ let storageFile; // file for GPS track let entriesWritten = 0; let activeRecorders = []; - let writeInterval; + let writeSetup; let loadSettings = function() { var settings = require("Storage").readJSON("recorder.json",1)||{}; @@ -194,11 +194,14 @@ } } + let writeOnGPS = function() {writeLog(settings.period);}; + // Called by the GPS app to reload settings and decide what to do let reload = function() { var settings = loadSettings(); - if (writeInterval) clearInterval(writeInterval); - writeInterval = undefined; + if (typeof writeSetup === "number") clearInterval(writeSetup); + writeSetup = undefined; + Bangle.removeListener('GPS', writeOnGPS); activeRecorders.forEach(rec => rec.stop()); activeRecorders = []; @@ -222,7 +225,12 @@ } // start recording... WIDGETS["recorder"].draw(); - writeInterval = setInterval(writeLog, settings.period*1000, settings.period); + if (settings.period===1 && settings.record.includes("gps")) { + Bangle.on('GPS', writeOnGPS); + writeSetup = true; + } else { + writeSetup = setInterval(writeLog, settings.period*1000, settings.period); + } } else { WIDGETS["recorder"].width = 0; storageFile = undefined; @@ -230,7 +238,7 @@ } // add the widget WIDGETS["recorder"]={area:"tl",width:0,draw:function() { - if (!writeInterval) return; + if (!writeSetup) return; 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); @@ -239,7 +247,7 @@ reload(); Bangle.drawWidgets(); // relayout all widgets },isRecording:function() { - return !!writeInterval; + return !!writeSetup; },setRecording:function(isOn, options) { /* options = { force : [optional] "append"/"new"/"overwrite" - don't ask, just do what's requested From dbf8995b14ca8dd5feca9be49e9a3b7da35395c9 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 3 Dec 2023 20:45:28 +0100 Subject: [PATCH 18/67] health: calculate distance from steps - multiplies step counts with stride length - distance menu under step counting is only available if stride length is set --- apps/health/ChangeLog | 1 + apps/health/app.js | 54 +++++++++++++----- apps/health/metadata.json | 2 +- apps/health/settings.js | 113 ++++++++++++++++++++++++++------------ 4 files changed, 118 insertions(+), 52 deletions(-) diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 02b53c56d..98a4a3426 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -29,3 +29,4 @@ 0.26: Implement API for activity fetching 0.27: Fix typo in daily summary graph code causing graph not to load Fix daily summaries for 31st of the month +0.28: Calculate distance from steps if new stride length setting is set diff --git a/apps/health/app.js b/apps/health/app.js index db21d9243..f3e08ec0d 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,3 +1,5 @@ +let settings; + function menuMain() { E.showMenu({ "": { title: /*LANG*/"Health Tracking" }, @@ -5,16 +7,30 @@ function menuMain() { /*LANG*/"Step Counting": () => menuStepCount(), /*LANG*/"Movement": () => menuMovement(), /*LANG*/"Heart Rate": () => menuHRM(), - /*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>menuMain()) + /*LANG*/"Settings": () => eval(require("Storage").read("health.settings.js"))(()=>{loadSettings();menuMain();}) }); } function menuStepCount() { - E.showMenu({ + const menu = { "": { title:/*LANG*/"Steps" }, /*LANG*/"< Back": () => menuMain(), - /*LANG*/"per hour": () => stepsPerHour(), - /*LANG*/"per day": () => stepsPerDay() + /*LANG*/"per hour": () => stepsPerHour(menuStepCount), + /*LANG*/"per day": () => stepsPerDay(menuStepCount) + }; + if (settings.strideLength) { + menu[/*LANG*/"distance"] = () => menuDistance(); + } + + E.showMenu(menu); +} + +function menuDistance() { + E.showMenu({ + "": { title:/*LANG*/"Distance" }, + /*LANG*/"< Back": () => menuStepCount(), + /*LANG*/"per hour": () => stepsPerHour(menuDistance, settings.strideLength), + /*LANG*/"per day": () => stepsPerDay(menuDistance, settings.strideLength) }); } @@ -36,23 +52,23 @@ function menuHRM() { }); } -function stepsPerHour() { +function stepsPerHour(back, mult) { E.showMessage(/*LANG*/"Loading..."); current_selection = "stepsPerHour"; var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); - setButton(menuStepCount); - barChart(/*LANG*/"HOUR", data); + setButton(back, mult); + barChart(/*LANG*/"HOUR", data, mult); } -function stepsPerDay() { +function stepsPerDay(back, mult) { E.showMessage(/*LANG*/"Loading..."); current_selection = "stepsPerDay"; var data = new Uint16Array(32); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); - setButton(menuStepCount); - barChart(/*LANG*/"DAY", data); - drawHorizontalLine(settings.stepGoal); + setButton(back, mult); + barChart(/*LANG*/"DAY", data, mult); + drawHorizontalLine(settings.stepGoal * (mult || 1)); } function hrmPerHour() { @@ -139,7 +155,11 @@ function get_data_length(arr) { return nlen; } -function barChart(label, dt) { +function barChart(label, dt, mult) { + if (mult !== undefined) { + // Calculate distance from steps + dt.forEach((val, i) => dt[i] = val*mult); + } data_len = get_data_length(dt); chart_index = Math.max(data_len - 5, -5); // choose initial index that puts the last day on the end chart_max_datum = max(dt); // find highest bar, for scaling @@ -180,7 +200,7 @@ function drawHorizontalLine(value) { g.setColor(g.theme.fg).drawLine(0, top ,g.getWidth(), top); } -function setButton(fn) { +function setButton(fn, mult) { Bangle.setUI({mode:"custom", back:fn, swipe:(lr,ud) => { @@ -194,12 +214,16 @@ function setButton(fn) { } drawBarChart(); if (current_selection == "stepsPerDay") { - drawHorizontalLine(settings.stepGoal); + drawHorizontalLine(settings.stepGoal * (mult || 1)); } }}); } +function loadSettings() { + settings = require("Storage").readJSON("health.json",1)||{}; +} + Bangle.loadWidgets(); Bangle.drawWidgets(); -var settings = require("Storage").readJSON("health.json",1)||{}; +loadSettings(); menuMain(); diff --git a/apps/health/metadata.json b/apps/health/metadata.json index 10a146bdd..4afe4b9d8 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -2,7 +2,7 @@ "id": "health", "name": "Health Tracking", "shortName": "Health", - "version": "0.27", + "version": "0.28", "description": "Logs health data and provides an app to view it", "icon": "app.png", "tags": "tool,system,health", diff --git a/apps/health/settings.js b/apps/health/settings.js index 4fb0f9e93..b6f5968e2 100644 --- a/apps/health/settings.js +++ b/apps/health/settings.js @@ -8,44 +8,85 @@ function setSettings() { require("Storage").writeJSON("health.json", settings); } - E.showMenu({ - "": { title: /*LANG*/"Health Tracking" }, - /*LANG*/"< Back": () => back(), + function settingsMenu() { + E.showMenu({ + "": { title: /*LANG*/"Health Tracking" }, - /*LANG*/"HRM Interval": { - value: settings.hrm, - min: 0, - max: 3, - format: v => [ - /*LANG*/"Off", - /*LANG*/"3 min", - /*LANG*/"10 min", - /*LANG*/"Always" - ][v], - onchange: v => { - settings.hrm = v; - setSettings(); - } - }, + /*LANG*/"< Back": () => back(), - /*LANG*/"Daily Step Goal": { - value: settings.stepGoal, - min: 0, - max: 20000, - step: 250, - onchange: v => { - settings.stepGoal = v; + /*LANG*/"HRM Interval": { + value: settings.hrm, + min: 0, + max: 3, + format: v => [ + /*LANG*/"Off", + /*LANG*/"3 min", + /*LANG*/"10 min", + /*LANG*/"Always" + ][v], + onchange: v => { + settings.hrm = v; + setSettings(); + } + }, + + /*LANG*/"Daily Step Goal": { + value: settings.stepGoal, + min: 0, + max: 20000, + step: 250, + onchange: v => { + settings.stepGoal = v; + setSettings(); + } + }, + + /*LANG*/"Step Goal Notification": { + value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false, + format: () => (settings.stepGoalNotification ? 'Yes' : 'No'), + onchange: () => { + settings.stepGoalNotification = !settings.stepGoalNotification; + setSettings(); + } + }, + + /*LANG*/"Stride length": () => strideLengthMenu() + }); + } + + function strideLengthMenu() { + const menu = { + "" : { title : /*LANG*/"Stride length" }, + + "< Back" : () => { setSettings(); - } - }, - /*LANG*/"Step Goal Notification": { - value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false, - format: () => (settings.stepGoalNotification ? 'Yes' : 'No'), - onchange: () => { - settings.stepGoalNotification = !settings.stepGoalNotification; - setSettings(); - } - } - }); + settingsMenu(); + }, + + "x 0.01" : { + value : settings.strideLength === undefined ? 0 : settings.strideLength, + min:0, + step:0.01, + format: v => require("locale").distance(v, 2), + onchange : v => { + settings.strideLength=v; + menu["x 0.1"].value = v; + }, + }, + "x 0.1" : { + value : settings.strideLength === undefined ? 0 : settings.strideLength, + min:0, + step:0.1, + format: v => require("locale").distance(v, 2), + onchange : v => { + settings.strideLength=v; + menu["x 0.01"].value = v; + }, + }, + }; + E.showMenu(menu); + } + + settingsMenu(); }) From 7957d8507c1cac5d88251b3a6ab51c74eedfa06b Mon Sep 17 00:00:00 2001 From: flaparoo <629229+flaparoo@users.noreply.github.com> Date: Mon, 4 Dec 2023 08:23:20 +0800 Subject: [PATCH 19/67] colourdemo: fix clear() call Co-authored-by: Rob Pilling --- apps/coloursdemo/coloursdemo.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/coloursdemo/coloursdemo.app.js b/apps/coloursdemo/coloursdemo.app.js index ffc86b97e..85066bd31 100644 --- a/apps/coloursdemo/coloursdemo.app.js +++ b/apps/coloursdemo/coloursdemo.app.js @@ -88,7 +88,7 @@ const rows = 10; // initialise -g.clear(reset); +g.clear(true); g.setFont('6x8').setFontAlign(-1, -1); // calc some values required to draw the grid From b5abec090e4a47b8a0d05adf7717429069754cf7 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 4 Dec 2023 15:45:08 +0000 Subject: [PATCH 20/67] hworldclock 0.34: Fix 'fast load' so clock doesn't always redraw when screen unlocked/locked --- apps/hworldclock/ChangeLog | 3 ++- apps/hworldclock/app.js | 2 +- apps/hworldclock/metadata.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index 991a648d7..bd761a1b1 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -17,4 +17,5 @@ 0.31: Tweaking the swipe option; Added mylocation as a dependency. Remove calls to Bangle.loadWidgets as they are not needed and create warnings 0.32: Added setting to show single timezone small, like where multiple ones are configured -0.33: Tidy up and fix clearInterval(undefined) errors \ No newline at end of file +0.33: Tidy up and fix clearInterval(undefined) errors +0.34: Fix 'fast load' so clock doesn't always redraw when screen unlocked/locked \ No newline at end of file diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index 9e4a5f18f..615b58e0a 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -443,7 +443,7 @@ Bangle.setUI({ drawTimeout = undefined; //if (BANGLEJS2) Bangle.removeListener("drag",onDrag); - Bangle.removeListener("onLock",onLock); + Bangle.removeListener("lock",onLock); }}); g.clear().setRotation(defaultRotation); // clean app screen and make sure the default rotation is set diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index f41b6ede5..b4efff806 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.33", + "version": "0.34", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", From e37b7c804a93aea349b70d48c64fc704260d65c7 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Mon, 4 Dec 2023 19:02:45 +0100 Subject: [PATCH 21/67] health: calculate distance from steps Simplify stride length setting, remove locale --- apps/health/settings.js | 121 +++++++++++++++------------------------- 1 file changed, 46 insertions(+), 75 deletions(-) diff --git a/apps/health/settings.js b/apps/health/settings.js index b6f5968e2..99e5a5175 100644 --- a/apps/health/settings.js +++ b/apps/health/settings.js @@ -9,84 +9,55 @@ require("Storage").writeJSON("health.json", settings); } - function settingsMenu() { - E.showMenu({ - "": { title: /*LANG*/"Health Tracking" }, + E.showMenu({ + "": { title: /*LANG*/"Health Tracking" }, - /*LANG*/"< Back": () => back(), + /*LANG*/"< Back": () => back(), - /*LANG*/"HRM Interval": { - value: settings.hrm, - min: 0, - max: 3, - format: v => [ - /*LANG*/"Off", - /*LANG*/"3 min", - /*LANG*/"10 min", - /*LANG*/"Always" - ][v], - onchange: v => { - settings.hrm = v; - setSettings(); - } - }, - - /*LANG*/"Daily Step Goal": { - value: settings.stepGoal, - min: 0, - max: 20000, - step: 250, - onchange: v => { - settings.stepGoal = v; - setSettings(); - } - }, - - /*LANG*/"Step Goal Notification": { - value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false, - format: () => (settings.stepGoalNotification ? 'Yes' : 'No'), - onchange: () => { - settings.stepGoalNotification = !settings.stepGoalNotification; - setSettings(); - } - }, - - /*LANG*/"Stride length": () => strideLengthMenu() - }); - } - - function strideLengthMenu() { - const menu = { - "" : { title : /*LANG*/"Stride length" }, - - "< Back" : () => { + /*LANG*/"HRM Interval": { + value: settings.hrm, + min: 0, + max: 3, + format: v => [ + /*LANG*/"Off", + /*LANG*/"3 min", + /*LANG*/"10 min", + /*LANG*/"Always" + ][v], + onchange: v => { + settings.hrm = v; setSettings(); - settingsMenu(); - }, + } + }, - "x 0.01" : { - value : settings.strideLength === undefined ? 0 : settings.strideLength, - min:0, - step:0.01, - format: v => require("locale").distance(v, 2), - onchange : v => { - settings.strideLength=v; - menu["x 0.1"].value = v; - }, - }, - "x 0.1" : { - value : settings.strideLength === undefined ? 0 : settings.strideLength, - min:0, - step:0.1, - format: v => require("locale").distance(v, 2), - onchange : v => { - settings.strideLength=v; - menu["x 0.01"].value = v; - }, - }, - }; - E.showMenu(menu); - } + /*LANG*/"Daily Step Goal": { + value: settings.stepGoal, + min: 0, + max: 20000, + step: 250, + onchange: v => { + settings.stepGoal = v; + setSettings(); + } + }, - settingsMenu(); + /*LANG*/"Step Goal Notification": { + value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false, + format: () => (settings.stepGoalNotification ? 'Yes' : 'No'), + onchange: () => { + settings.stepGoalNotification = !settings.stepGoalNotification; + setSettings(); + } + }, + + /*LANG*/"Stride length": { + value : settings.strideLength || 0.0, + min:0.01, + step:0.01, + onchange : v => { + settings.strideLength=v; + setSettings(); + }, + }, + }); }) From ef74b85ef9db9a02806a08c74de30da51af0cb5d Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Tue, 5 Dec 2023 21:03:32 +0100 Subject: [PATCH 22/67] health: calculate distance from steps bring back locale, rounding fixes --- apps/health/app.js | 27 ++++++++++++++++----------- apps/health/settings.js | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/health/app.js b/apps/health/app.js index f3e08ec0d..60b337321 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -26,11 +26,12 @@ function menuStepCount() { } function menuDistance() { + const distMult = 1*require("locale").distance(settings.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm' E.showMenu({ "": { title:/*LANG*/"Distance" }, /*LANG*/"< Back": () => menuStepCount(), - /*LANG*/"per hour": () => stepsPerHour(menuDistance, settings.strideLength), - /*LANG*/"per day": () => stepsPerDay(menuDistance, settings.strideLength) + /*LANG*/"per hour": () => stepsPerHour(menuDistance, distMult), + /*LANG*/"per day": () => stepsPerDay(menuDistance, distMult) }); } @@ -57,6 +58,10 @@ function stepsPerHour(back, mult) { current_selection = "stepsPerHour"; var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); + if (mult !== undefined) { + // Calculate distance from steps + data.forEach((d, i) => data[i] = d*mult+0.5); + } setButton(back, mult); barChart(/*LANG*/"HOUR", data, mult); } @@ -66,6 +71,10 @@ function stepsPerDay(back, mult) { current_selection = "stepsPerDay"; var data = new Uint16Array(32); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); + if (mult !== undefined) { + // Calculate distance from steps + data.forEach((d, i) => data[i] = d*mult+0.5); + } setButton(back, mult); barChart(/*LANG*/"DAY", data, mult); drawHorizontalLine(settings.stepGoal * (mult || 1)); @@ -80,7 +89,7 @@ function hrmPerHour() { data[h.hr]+=h.bpm; if (h.bpm) cnt[h.hr]++; }); - data.forEach((d,i)=>data[i] = d/cnt[i]); + data.forEach((d,i)=>data[i] = d/cnt[i]+0.5); setButton(menuHRM); barChart(/*LANG*/"HOUR", data); } @@ -94,7 +103,7 @@ function hrmPerDay() { data[h.day]+=h.bpm; if (h.bpm) cnt[h.day]++; }); - data.forEach((d,i)=>data[i] = d/cnt[i]); + data.forEach((d,i)=>data[i] = d/cnt[i]+0.5); setButton(menuHRM); barChart(/*LANG*/"DAY", data); } @@ -108,7 +117,7 @@ function movementPerHour() { data[h.hr]+=h.movement; cnt[h.hr]++; }); - data.forEach((d,i)=>data[i] = d/cnt[i]); + data.forEach((d,i)=>data[i] = d/cnt[i]+0.5); setButton(menuMovement); barChart(/*LANG*/"HOUR", data); } @@ -122,7 +131,7 @@ function movementPerDay() { data[h.day]+=h.movement; cnt[h.day]++; }); - data.forEach((d,i)=>data[i] = d/cnt[i]); + data.forEach((d,i)=>data[i] = d/cnt[i]+0.5); setButton(menuMovement); barChart(/*LANG*/"DAY", data); } @@ -155,11 +164,7 @@ function get_data_length(arr) { return nlen; } -function barChart(label, dt, mult) { - if (mult !== undefined) { - // Calculate distance from steps - dt.forEach((val, i) => dt[i] = val*mult); - } +function barChart(label, dt) { data_len = get_data_length(dt); chart_index = Math.max(data_len - 5, -5); // choose initial index that puts the last day on the end chart_max_datum = max(dt); // find highest bar, for scaling diff --git a/apps/health/settings.js b/apps/health/settings.js index 99e5a5175..2d5809cdb 100644 --- a/apps/health/settings.js +++ b/apps/health/settings.js @@ -54,6 +54,7 @@ value : settings.strideLength || 0.0, min:0.01, step:0.01, + format: v => require("locale").distance(v, 2), onchange : v => { settings.strideLength=v; setSettings(); From 086b1b701a01d900a21284225122deabd4d2b1e4 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:05:57 -0500 Subject: [PATCH 23/67] Create app.js --- apps/binaryclk/app.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 apps/binaryclk/app.js diff --git a/apps/binaryclk/app.js b/apps/binaryclk/app.js new file mode 100644 index 000000000..1d6c20684 --- /dev/null +++ b/apps/binaryclk/app.js @@ -0,0 +1,36 @@ +function draw() { + var dt = new Date(); + var h = dt.getHours(), m = dt.getMinutes(); + const t = []; + t[0] = Math.floor(h/10); + t[1] = Math.floor(h%10); + t[2] = Math.floor(m/10); + t[3] = Math.floor(m%10); + + g.reset(); + g.clearRect(Bangle.appRect); + + let i = 0; + const sq = 29; + const gap = 8; + const mgn = 20; + const pos = sq + gap; + + for (let r = 3; r >= 0; r--) { + for (let c = 0; c < 4; c++) { + if (t[c] & Math.pow(2, r)) { + g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); + } else { + g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); + } + } + i++; + } +} + +g.clear(); +draw(); +var secondInterval = setInterval(draw, 60000); +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); From 56b6188d1c9d1169638cd04ba8b7d2154a8ec0ad Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:10:40 -0500 Subject: [PATCH 24/67] Create app-icon.js --- apps/binaryclk/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/binaryclk/app-icon.js diff --git a/apps/binaryclk/app-icon.js b/apps/binaryclk/app-icon.js new file mode 100644 index 000000000..3cb526a4f --- /dev/null +++ b/apps/binaryclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AEUiAAQEEkECBRAX/C/4Xrd+hCDI4kgR/4X/C/4XIAF53/C/4X/A4gSDC4kgC5AAvR/4X/C/4A/ADoA==")) From a80c00dc8fc5013bec7dadcaf83e16d794a79d92 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:47:24 -0500 Subject: [PATCH 25/67] Create metadata.json --- apps/binaryclk/metadata.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 apps/binaryclk/metadata.json diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json new file mode 100644 index 000000000..89bba6211 --- /dev/null +++ b/apps/binaryclk/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "binaryclk", + "name": "Bin Clock", + "version": "0.01", + "description": "Clock face to binary time in 24 hr format", + "icon": "binaryclk.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"binaryclk.app.js","url":"app.js"}, + {"name":"binaryclk.img","url":"app-icon.js","evaluate":true} + ] +} From 99416d6bca7431b7638e5105a273f152d53d20a1 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:00:09 -0500 Subject: [PATCH 27/67] Update metadata.json --- apps/binaryclk/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index 89bba6211..88a77c589 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -3,7 +3,7 @@ "name": "Bin Clock", "version": "0.01", "description": "Clock face to binary time in 24 hr format", - "icon": "binaryclk.png", + "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", From 81e3afa3b8cc469bbf73542d037b18c6fc8c28ad Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:00:43 -0500 Subject: [PATCH 28/67] Add files via upload --- apps/binaryclk/app-icon.png | Bin 0 -> 407 bytes apps/binaryclk/screenshot.png | Bin 0 -> 2437 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/binaryclk/app-icon.png create mode 100644 apps/binaryclk/screenshot.png diff --git a/apps/binaryclk/app-icon.png b/apps/binaryclk/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b28ffdab4d9c135719b3a44fbe5fecdb3d23b373 GIT binary patch literal 407 zcmV;I0cie-P)Vsl)*Y$+OOJs(NnQ_BHqRcOxD)SpoRb zjs;Zw0^kt(H8_be@hT)D8&&mq=OKCSN3fe}vC%ZmTWU~)5|IyM zRrOe6opX<-5~r$1^EB@gYdH=Uz)1{lC2H=4z}+agoyNaC5A=P1uc|LOIpK+y)2uc_=&T1$fl!AhS8tIJVdGKu9yHt?i-oRc8P*6}MlN{|m69QK5jYiE*?N@rWySwbT z5zA+s{rGVEyd&j(EOPl5evg65qlfxwX>$3bo2t!GNFO7sEDYrnfA^4!Sr}FI6glTN z^ixGvYs*R|Yt;kOi?|4ELJSNe8+{eJj%io7v4T+Y<@Ht>%1>GEo*g1pD!Y)3sE6mf zR=|jf$z~kbH01n^JEVV#qeX+>bF0C``Fgb625zkAKb&6f1EtAj>t-RJr>UK-HN-=V z3$AVC2Z+?Ik4mXpw~^x|wj81avZRRem4YLVv-+LMW73bt%aZ$4jKnW!Ogb)iBx-ZPYSbW4?H=@6e`$tK?Lxw@h4?cJTiCx+s51@1J zde3DT_jNp5fw6pK`%6rf-T>312mZAgs4kXy7l06fZJLGBgNN1I1HI^9 zZ3kBN+~hRYF^gHg!;77PUjO4Qb3=D^rQ`4^CuT4~mTsX@A^yqX0v||F5PgSx8EEEPu3 z&n=~Mk9TAz_Uq#%;h~@L!pU&2qE^t!Jt0XPb__LV8k_{b1R$; z-*x3m;mCjZcGyl;nD>8k>!16%ZDYYH4> zwJ~VOXLLB@o4pON(iE>{Yi?qN^}_ z(t7*)-#(c%^2Fn@kxB6v=+C6qZ^!3#gpur|fn)rvQp`)(<{j=uK=QXk!&@7GWZof- zgu8%ba&vUI4Ip{rIXnQ6j31Zy;oShqHXG^8J(98IMr}1j3S(w7%6DTNMw4IaH#iVIICx)pN7nKI2v~ zOXKk6Zs195#}VN9_}fEHFbbPtL1(z|7c-(O(rx zufLfCmX~ZynS)5tIeOq4X(xSb3Z%^c%lfanwb{bGB4*b=NC^?V6>(m@4YB0s4Wq)K zPiIBnLa5}sm)<5afZSf+Qtl#Gz-Tr6j({?7E;5!B@!;%ZhQ!q~!Ffp2V{my&!>GB< zA>xmWnIqHjrT5GjKvQbACK0;{S-b0b*E?aeEF=x_rg4X|(UAAhRQGjv#?*xRoVvU- z*0bo>#EpX?E|_*0mCm`5YQ4NtB2~QqggUqi6gB@+eb9#@T7^1{p6PmOjU5ZN2q4Tt z*)N{7Vv7rFL8QzT#L@4Kj_m60Vu>xY-LFa#QKKGL%N+Jmjl2B|zetmRs`19$AH@EZ z*mf{p(%gvQAIjps=QF3(!wPkh-Ee43a)KBry^d=Y6<)_Jr65)bI&9WJuvN8Q^>4V^ zIQpuKb%eDAn}zib+N)c_+gJ!wzM?#pS=qEF@AVwM$5n%v&=tLZ76pp`P5_VBknqM% zt(Dg;uOyz`dQJ{~7jCel`LXlQo<-HJt`91YeTmHMb2+vaxBgM8hYGgiL>oNX3)B8U zbFQGwVL2}mMX Date: Fri, 8 Dec 2023 13:09:42 -0500 Subject: [PATCH 29/67] tab spacing app.js --- apps/binaryclk/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binaryclk/app.js b/apps/binaryclk/app.js index 1d6c20684..05543f3e7 100644 --- a/apps/binaryclk/app.js +++ b/apps/binaryclk/app.js @@ -1,5 +1,5 @@ function draw() { - var dt = new Date(); + var dt = new Date(); var h = dt.getHours(), m = dt.getMinutes(); const t = []; t[0] = Math.floor(h/10); From af6108de4eece0996c36acd70cd77ab94eb7328c Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:19:18 -0500 Subject: [PATCH 30/67] actually fixed spacing --- apps/binaryclk/app.js | 44 ++++++++++++++++++------------------ apps/binaryclk/metadata.json | 28 +++++++++++------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/binaryclk/app.js b/apps/binaryclk/app.js index 05543f3e7..0060edf98 100644 --- a/apps/binaryclk/app.js +++ b/apps/binaryclk/app.js @@ -1,31 +1,31 @@ function draw() { var dt = new Date(); - var h = dt.getHours(), m = dt.getMinutes(); - const t = []; - t[0] = Math.floor(h/10); - t[1] = Math.floor(h%10); - t[2] = Math.floor(m/10); - t[3] = Math.floor(m%10); + var h = dt.getHours(), m = dt.getMinutes(); + const t = []; + t[0] = Math.floor(h/10); + t[1] = Math.floor(h%10); + t[2] = Math.floor(m/10); + t[3] = Math.floor(m%10); - g.reset(); - g.clearRect(Bangle.appRect); + g.reset(); + g.clearRect(Bangle.appRect); - let i = 0; - const sq = 29; - const gap = 8; + let i = 0; + const sq = 29; + const gap = 8; const mgn = 20; - const pos = sq + gap; + const pos = sq + gap; - for (let r = 3; r >= 0; r--) { - for (let c = 0; c < 4; c++) { - if (t[c] & Math.pow(2, r)) { - g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); - } else { - g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); - } - } - i++; - } + for (let r = 3; r >= 0; r--) { + for (let c = 0; c < 4; c++) { + if (t[c] & Math.pow(2, r)) { + g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); + } else { + g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq); + } + } + i++; + } } g.clear(); diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index 88a77c589..b779d6939 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -1,16 +1,16 @@ { - "id": "binaryclk", - "name": "Bin Clock", - "version": "0.01", - "description": "Clock face to binary time in 24 hr format", - "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}], - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"binaryclk.app.js","url":"app.js"}, - {"name":"binaryclk.img","url":"app-icon.js","evaluate":true} - ] + "id": "binaryclk", + "name": "Bin Clock", + "version": "0.01", + "description": "Clock face to binary time in 24 hr format", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"binaryclk.app.js","url":"app.js"}, + {"name":"binaryclk.img","url":"app-icon.js","evaluate":true} + ] } From ea85f732dec069ff9b741096788af610437bdd84 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:39:14 -0500 Subject: [PATCH 31/67] Update metadata.json to poitn to app-icon.png --- apps/binaryclk/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index b779d6939..682e78704 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -3,7 +3,7 @@ "name": "Bin Clock", "version": "0.01", "description": "Clock face to binary time in 24 hr format", - "icon": "app.png", + "icon": "app-icon.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", From 46d93ba792f7d2018bdf97d741687c5a5d50c0b5 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:44:03 -0500 Subject: [PATCH 32/67] Update metadata.json --- apps/binaryclk/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index 682e78704..fdc56b21a 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -2,7 +2,7 @@ "id": "binaryclk", "name": "Bin Clock", "version": "0.01", - "description": "Clock face to binary time in 24 hr format", + "description": "Clock face to show binary time in 24 hr format", "icon": "app-icon.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", From 2347a01fa09b61bc476e988ea468613052d8a75a Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 8 Dec 2023 21:19:41 +0100 Subject: [PATCH 33/67] bad apple: Initial version of the demo. This adds famous movie with music. --- apps/bad/ChangeLog | 1 + apps/bad/README.md | 8 +++++ apps/bad/app-icon.js | 1 + apps/bad/app.png | Bin 0 -> 9604 bytes apps/bad/bad.app.js | 65 +++++++++++++++++++++++++++++++++++ apps/bad/bad.araw | Bin 0 -> 128078 bytes apps/bad/bad.vraw | Bin 0 -> 155360 bytes apps/bad/metadata.json | 16 +++++++++ apps/bad/prep/img_convert.py | 32 +++++++++++++++++ apps/bad/prep/run | 20 +++++++++++ apps/bad/prep/wav_divider.py | 13 +++++++ 11 files changed, 156 insertions(+) create mode 100644 apps/bad/ChangeLog create mode 100644 apps/bad/README.md create mode 100644 apps/bad/app-icon.js create mode 100644 apps/bad/app.png create mode 100644 apps/bad/bad.app.js create mode 100644 apps/bad/bad.araw create mode 100644 apps/bad/bad.vraw create mode 100644 apps/bad/metadata.json create mode 100755 apps/bad/prep/img_convert.py create mode 100755 apps/bad/prep/run create mode 100755 apps/bad/prep/wav_divider.py diff --git a/apps/bad/ChangeLog b/apps/bad/ChangeLog new file mode 100644 index 000000000..263d4078d --- /dev/null +++ b/apps/bad/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/bad/README.md b/apps/bad/README.md new file mode 100644 index 000000000..156b34cbf --- /dev/null +++ b/apps/bad/README.md @@ -0,0 +1,8 @@ +# Bad Apple demo ![](app.png) + +"Bad Apple" is like "Hello World" for graphics system. So this is it +for Bangle.js2. Watch have no speaker, so vibration motor is used, +instead, to produce (pretty quiet) sound. + +Tools for preparing bad.araw and bad.vraw are in prep/ directory. Full +3 minute demo should actually fit to the watch. \ No newline at end of file diff --git a/apps/bad/app-icon.js b/apps/bad/app-icon.js new file mode 100644 index 000000000..e153d6397 --- /dev/null +++ b/apps/bad/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIifiAFWj//Aod///gAgMH///+AFBn4FB/AQDAoYEB//8gEBAokDAoX+ApguCAAIFqGoYFNLIZHBApZxFAoyDCAoqJCSoqsBUIcPAoKtF4AFBJAS/DHIQAeA=")) diff --git a/apps/bad/app.png b/apps/bad/app.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e3dad820e4b28a6ee7c0ed4156284382e764b9 GIT binary patch literal 9604 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 literal 0 HcmV?d00001 diff --git a/apps/bad/bad.app.js b/apps/bad/bad.app.js new file mode 100644 index 000000000..453f0003c --- /dev/null +++ b/apps/bad/bad.app.js @@ -0,0 +1,65 @@ +/* sox Rear_Right.wav -r 4k -b 8 -c 1 -e unsigned-integer 0.raw vol 2 + aplay -r 4000 /tmp/0.raw +*/ + +/* https://forum.espruino.com/conversations/348912/ */ + +let pin = D19; + +function play(name, callback) { + function playChar(offs) { + var l = 10240; + var s = require("Storage").read(name, offs, l); + //print("Waveform " + name + " " + s.length); + if (!s.length) { + digitalWrite(pin,0); + if (callback) callback(); + return; + } + var w = new Waveform(s.length); + var b = w.buffer; + b.set(s); + //print("Buffer", s.length); + //for (var i=s.length-1;i>=0;i--)b[i]/=4; + w.startOutput(pin, 4000); + w.on("finish", function(buf) { + playChar(offs+l); + }); + } + analogWrite(pin, 0.1, {freq:40000}); + playChar(0); +} + +function video(name, callback) { + function frame() { + var s = require("Storage").read(name, offs, l); + if (!s) + return; + g.drawImage(s, 0, 0, { scale: 2 }); + g.flip(); + offs += l; + } + g.clear(); + var offs = 0; + //var l = 3875; for 176x176 + //var l = 515; for 64x64 + var l = 971; + setInterval(frame, 200); +} + +function run() { + clearInterval(i); + print("Running"); + play('bad.araw'); + t1 = getTime(); + video('bad.vraw'); + print("100 frames in ", getTime()-t1); + // 1.7s, unscaled + // 2.68s, scale 1.01 + // 5.73s, scale 2.00 + // 9.93s, scale 2, full screen + // 14.4s scaled. 176/64 +} + +print("Loaded"); +i = setInterval(run, 100); \ No newline at end of file diff --git a/apps/bad/bad.araw b/apps/bad/bad.araw new file mode 100644 index 0000000000000000000000000000000000000000..cfe5f4fdddbcbb02a554f46ec3ac1f3c179a9c69 GIT binary patch literal 128078 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

A1_+Z3wE$l3d0Lhbi>xIirv)XXhp1oYzNNO zIPJiOnqkX3?xZC-Y+ThUH|?fDQ^FTR0#9g2A^O$0=nc{OhWKy^?BEN`W8Ub9>aMSA zvjdTz^Z;TA1rkKmCZKc?Gwl_77z|mEzF{?F3y5s8)uRB3<$#DdmWV;Ma`4Pis*D8r z4RA|$)0OZ;@ueZ~NkCLpY7gt+*aKTl0%Rzvm3mI1Aw{NT)fkCd_P(HMT?$Nri1B3)E1HC3~BTQ9OV3{;^@}F5D7?hYrpku zxj`x~5mG^UIK;&b{rZ;SXjW|fdDoWQlGS-j+Ma5VN*{_%2VUB|c^Z$D>1Q__iaypek0>#}P+g zC?0mh#nOP?QMMjd5a(w)E>N)>FzK>2;DEE4Nh-)+J|VKah9E;}Zna7q_u{*v!*S#( z99VI}G<_n7mfj>e+UCk0C=d2jq${7PaoCX}ve0G?$PPl~xdr?ot=gYGyDgxU009i^ z+uRF51bmB#C*2RR2su%R#bojmerQ(DEvR5k- z7SIJYyU4O(Q%K>YvRY)5lS%*X74raB!NfI5@p&4pUdf}njHBbw7Hh!1EhXz;EQoI-w3BYl#Z<7#h)7HC=;sugA%zz3ZFPXPeP0oA z6!9&!BGB6Zt>9YJsY?9UYDIUi21Uff>W>wBplB9@5N4u3>%`mu^#RFL!{RSnTB(u6 z$LoxZj#m=Zhf)ZM^&5YS4kLVGE3hL&qg87_hEWKv zfEI|TaE?^QWC&UdN1nj#udR|6Un zPnp$f%|ceuLWVJO0#{gAnziMUTBAco{D2y1qHjgfw+7vD9Hk6m4LFEZ27q2r!d59_ zmx~}XP>xlU$0D2|KH6@;zF@9d)3w=EACaaWojKx9Iva)*T2JALV#s12OkSK%3n$NF z548Igx$jQY2h~Y7`(PNk0?txUDQnz{exEYT;R^Nb#8DbZYH6Z_&D>xE+L=*27&H4?PHn3`}MC65Cm2x8L=$ANJs3;K;S;{T1 zlME8CI3GP(pX2OfmNLqdr3}nc#&9D0;KiOJ!nZTbV=ky^B%mNt;?z4>fu}28gY6vTh|eLw-!X3WosQfcj)qxoU894 z?-sCit36YiPT@Ah8RkJ1Em_CX>a(V4uPI58d5%=ZK%Mbb zB27`A^bG79G>~c?G(&Wk=~qVpX?#WLvm~;YPE~4k2`^kE*cPyLP3}alEo?i59)fT0yPXL`dADKCL0Zz@|-63ERc*>(PB^Rtk_Q^+p$3GbfjQ z#5={IyII9|inZN>Lt|U}w&VK+yMXuHdT(rO>mCdRKdRzGw?6E%Z`wG15-@+>ByY)x z-jd1Dx`g{e8F|}=y*WTX0SC9%add}(5;yn+iDkrMJz!QStaUv6Tu7O>&QgKIn`wOIo)L~OQa+Zr-Y!x3mh#6V0V6C*Vp zQA0>jyNnKPkB@a_ATYzywb(oNGH_#_$hd~~KutQ;adG6~z6G!6ESjT$H{2SqbE~7D zL2ITqJK&dcdgB@1c55BqcZYxLI5xJ`7wa=?X}<9F`+&GLH7B+WOPht``Pr#3?O!Cb z=lQSRa&CQy01j^5hM$L78KugMms})&TTmV!iyuOE@#sy0!CZJ8i)+r;yl0vf(;L##C^D zZ-bb1algoDF%-cp`sG6@E7AzqxV2dn2UI@$0&{L>#o0_-(%sKm$`A&+`Y5!`S~C8$ zX62cbB|^~DI0v#@)&1w>%5Cw@0U*GJ*M0l8*div zg{07f;g{uyPUX$wwyV0`h4r|d39*FmDXgOv=SL~pQr@8TSv7}T$Z+<4=1^+U`Y1HR zl0{H;C!ruoZ$n0mITTDjgD0P$Pk}ige#{rxFR?)o(`eFH499I!Zt)3y2RN_`R0;t* zQ#C$2iPUeWq7AC1AxPP@&eVgH4F_0lk>4zbM$f|Go04OZ@&$a`RTA%!*W0B(pdNcrG`3LJag zSUq4qZI8Te7yZtqJnuPpV_V-Z6ZG;OkI=9JuZy;B4KW@S<)U3x@f4~arL=gNrEt}) zRh+IB2Nz6T%G>l?Bo~IMP|bk#CTXWJmmy&1)|q}wZrk+B*pO`|+A~Snhk3@fJP-(f za|TZz=C(n}w&Lq-8#s(JDR*Wawn@2Tg4nHxdJJJmYztWN+9@8BvRT>stvc}4km(qX zz_mIIt3FCu^@~~gU|FP`)(<4b9{W#a!G%NG+{;*X7b$l>LBj*P$hNE>OwUxEeR23z zupYwUcrvPb8YM5h%rsf7bye4WSNKU&+qU7g;!wIDb1GX|w*7Flz2;{fhHS03L+dpT z*tvC^luIGg#3+z$8|F^N{9D7&^^d^M@2ni8+zn_*$P<6sQL%UZ2E=EH?XFTbd;(I& zHxRa*p8Fetw-d{Neq7a` z2Pt1-zd(Ix-8USc3MsQ=FofVHmLlce4x4$b>!%?xGS<0g&i`o9Fqg&a#y3OQSw6mYJ zBo-tVGLc*B`o;8{ttei>I4kdz z@+@w9$w4wH7>*pHQtSGyT7#4W?l(b?xnyy-saR(Ry*WNkv+S;=P#5hsYe}K5->}v` zqm;t}+edNqcOKAV*M}$&{2b59eREvD1H|F_HT6A0$~vb~Hl1Uu$X63BRcv@%%G_6m zkbGSV?_J7$k@9L>yiZ?98DIS^N=@Ef;S3lKo6A%jq>L+2J(I_VGuXWVUiQH=N_n5l z##;`&U8(yx%v3$*8qcgj>ve}A@3vFFtrv2qlxM1DfXKFOt<7LEhZ=emoJ)B`J0bpu zZBovvhMS32W@Q!a-cnZmGS4m7m6^E8Rqr9eb>)8XKCP7UrpS{QpiH#OuHfvt6wKI7 zw#EITUtFXf%(`33VO1TlJ}WOh-TiRP#R;d(WhY<1+Ad6|r$2X?*U0L0r*Tgs|mnU$+jwi~}t z5Zvv>;z;i(%FOY$NtvIUJQq_<)r(t2)r&U^VzQTG1!mARK0)s#aOBJp|6iR%{Jnu=O*=6js#{RjC|PTSe|{_iHotaO0-o0IS`k4T?8z zHY|vq5UAg{>GCWBZ(agoH*QJGe++-wtyfXPs88Q@P3;La5nTorqV153r>vv|g5nsaK^DJd) zQN;9!nwowGpotb}@}w$SHY6VJJ zfi@8(EX1Mh;ksoS4C?D#lL3=k3$E}O!I5clh;i%+KC^y1J6v3Z)$i;0HkpWWT45G@`}y$8?2w-S$V-0 z*66jxEhF7@Mzn7*$ppvu@Wmdprx1vh`ZXa2WcQd=x>8dRQKpfJ;RU@UI5?7;c7y}d z!fZ~Iy~L_g)*}M>Tvrr|nxwE}kX#Vr?2f37?oKh+FvMb*K$47qvgctcyZwpdRmayH zb*-Zrc3LmK37AIx6tBC{Q?N$jzPquMxBeCGx-~pgtp3IBl~1H~4A=YX*t&jO*JK?# zcL4URIJQUN=U2*z+`*(QK{HKM8&ICD8L+2y8ncwCPx#WPD4IKgGSYH_m?jAvMF~9) z7ZVc1#)D7Gm#4t2cX(mSI#o~uc7=3I%5u7l`!faoJI8Q-Y8wuDzc8BzY_))N3K$&1 z%5rAik?w4vn~N~7MW4;#(0qm^i&zEUpmA7UM)eP8qJJd1x$PrK%V3M~Z4t(<1 z6AxfGh*&*Cs@CZ~X=M?}#_(z7h+$h6N%nb(Loj6J(%5$&}kbn8%T=S(W*14pte&A56=zAXYOjj@W(RK(V}OvGA?LZf?8jm0+a7s z+jLJ);wXRd5@y{~d_yTWB6ce{o0fA=LuYl=5+dW3PbPJYv)G-I+wykqoF?LZw|dD! zA!A}*vPHyFvOFTPiM)_Fg+POB0ex;mpY-;5K*(<}>l{b3LWu!$HDJxSRg}4bgA!ZU z8Zg(Wt74igAZ1ydD|gJQQqC2_9!~{Y)3G?aFt-6*>u9Zb0}hI4`aD{!`9l>Ndh(kU z+9fT2$wINip{8{opG|LF9Wc?_OCbfdxO~vPm4;8k*L)$$oGb*KELkk`TdO3rEJp=M zV|F5G)I?{p-tP$YC!SR@?&4fR7lqVvoQ{AC%Z9UzcSWjUe>&`6paoCyh&fj1P zZC1yfS=7thd85`bxkDW%cc`LVz*L_ZLdr5rX|(;w5HXHT!gwcQaK$!CWgdZpI3#+U zz>d~l90InqiV@eA$`}z418or~&;-gxGXb*fS&Kl;cXqC#%{g#}EOSekW1Ch>aQ=GU z0{gW9q}*Uu;MQ}`6F9i_@v-;BPKVTyl}wQdh|o+DR98fuqs6Kyqf;y>=ju`B}g@|7;w1ivURZ|wb6X_y0pEq66BB0|DLzNX<9&} z%tHrk652W`eySL9J0)sxCl+!is{T6q1{|d^T3Q1RVwFK6U@uk~4isfL5FsC>hI@D2zBx!;=$|zz|gc7qV6Y%h=?AH-hU_|?UqZW9ex@DV;nnXYrxK}H4 zHbizfNv>UzpnJm>(mZhcN-cCETtZ6QIYGvmO$x!K@E%qxv2jUkJD@D%rr4JnxggSkX`J@F`)q z#(e8s!fvP_fDHr|5OBYkkx=qSuJ#WYV=Tf#v|5T*h zBXNMVXbiNqMj`VfJ(k@;sNaHqaijf9huCnrQ2)K9@3jJIUlI-QMlOlTv;i zJK9>?SJiS7+h%1ZS(%l4m?bw=YQkHabUVl6Qr1IDSzw=4zd_(>WKOc}#A!6ovoE`S zDz1xb=d4`c?MYgn4k>TEsn3KnRX<`@-hD(mNrXyq9v_n@wi7$|-HEOH1~g5w>;YAi zQX#{a@kZq-7oiN9q#g?Et7M_aw26D4m~>mU4)|lDCE8IjQMPc-B%CFhNqIEa{Z3XV z-)#&2|LvVkZrnx?g(+yEUFHHoc$4&1a)g{9&1|yGb+R*oEV2oZBbbW-*#xj`WT6C! znOD_aU5}m`wK$gKF(59iZ=Q?Qe^uQa{!K6Y9y5*jfHltQ6Q!LM?(xz_>Lwpr5nYu$ zd@`gQFWZZq;e|vpSy{lAt5z> zAf=>+lsXXV=V$)sD=&}lo0?6g#j%@+@iW78ksvmpLp@%uC_*WOQ;V7MrtMPCF;vNx zFuN^d3lzRPg0EFZ2KPpiRBeyJreHaPST?)s4xcUyue>@jmLKrbR_sA?MlB}wp}0xq z+MA#6AN&f0ZR2Fb@10o}Qz@eeX_7sKEyn4|rtZ`7o49QrhWKWL@ zUp}2!cUm-`n&4Vk#$CKsr&@A6iiiUb4=q zjgM7y-#nD<;}J!evu8kzrj-`sQ`KQlM8k}D zBpYlOg$=^5sf5U1Q~Q|QHpbBn2ya{^-1BlPDlNOS)iGAdAVloH5?sNh}qRs*qZ5W+Y`d*N%PRf&myZ&Hr~P-29J&xmCBZUgS_g} zwrp+2Db2PjqbsIr0Gul#4hWa3V({-rJO^eacd~ZV(g-0_XYGyr& zRs@(lOMqqowCdyyT76e+bqF2NV4IE=XSEo z>6K#=oWxtsg0tXvii@DUsJXQ6O5V6!%DDP33rZQ+e=ABE*Z(L=88`3n->=p2&0oOZ z)bZi>H|zR~R{roOq@UsV$y3>SSWf=VsG4r{uH@7P3Ev!TWTd;Rsr2zwSo(N&TnZvO zOdYpa4HDF2%&x|W|F_))=Te&B^dUYhW{L-mnIqKjDe9Rh-B2sJ8%ujeIH(De8)|Ke znjjVfZEqPYEvsN!U%}v=I9uOmpd{k;`WB>kShelqEZ^yVGTiHw#6UU~FHBNKM%`Wi z`$kk)+#cTkD2T?*;}500WthJE(r~!@c2t9DxOzJmoQLZ_hK9e&@3UZAhMS)%-VTRb z1e|QQ!#HDNFSt%xNvW~bOwcy@*@C(e>dzq*nNWW^p&Ae0+iojbVT^Z0<;qj(C;2p2 zX^V@igtw{5PoK&dtxvnrkG71KkhrC@9U@_HY5?PAC5<_$F9uX1Q8q-gA)?1@N1LO? zSDS;fBl_58CPztvsuD`{RKJ8M*HpnegT$y|J!y3n=a0t4J?zI(>RWbGoYa}3(jZv- z;RCGwAS&URwsk!aXNL% zgNjnJd&0uf3VEk$4(qs;^`cqr!CoaqBA#joBRUvQK}D41k=DVSwOE%nty z21Pk`s;_61117?$R7Z}ID#`*mmsdFwj;<0WR|&JLM<@v^wLuYrQk#l;pf+z$dyO!u zTZ~7~6sa|xZl=d6KJkMS){vLKogTQJn!(NDSi)OAkDftEf~aiS4>0n&nA%ngd;-Npg&mFV&V%>R465y4$$L bYLK8FV|G=rdH@}x)Uhtiilx_UP7lNXDzz*m literal 0 HcmV?d00001 diff --git a/apps/bad/metadata.json b/apps/bad/metadata.json new file mode 100644 index 000000000..882b7f066 --- /dev/null +++ b/apps/bad/metadata.json @@ -0,0 +1,16 @@ +{ "id": "bad", + "name": "Bad Apple", + "version":"0.01", + "description": "Bad Apple demo", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "allow_emulator": false, + "tags": "game", + "storage": [ + {"name":"bad.app.js","url":"bad.app.js"}, + {"name":"bad.vraw","url":"bad.vraw"}, + {"name":"bad.araw","url":"bad.araw"}, + {"name":"bad.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bad/prep/img_convert.py b/apps/bad/prep/img_convert.py new file mode 100755 index 000000000..2edbbdef5 --- /dev/null +++ b/apps/bad/prep/img_convert.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + +from PIL import Image +import os + +def convert_image(input_path, output_width, output_height): + img = Image.open(input_path) + img_resized = img.resize((output_width, output_height), Image.ANTIALIAS) + img_gray = img_resized.convert('L') + img_1bpp = img_gray.point(lambda x: 0 if x < 128 else 255, '1') + return img_1bpp + +def convert_and_append_header(input_directory, size): + input_files = [f for f in os.listdir(input_directory) if f.startswith("image_") and f.endswith(".png")] + input_files.sort() + header_bytes = size.to_bytes(1, byteorder='big') + size.to_bytes(1, byteorder='big') + b'\x01' + + for i, input_file in enumerate(input_files): + input_path = os.path.join(input_directory, input_file) + img_1bpp = convert_image(input_path, size, size) + output_file = input_path + ".raw" + + with open(output_file, 'wb') as raw_file: + raw_file.write(header_bytes) + raw_file.write(img_1bpp.tobytes()) + +if __name__ == "__main__": + input_directory = "." # Replace with the path to your image directory + output_width = 88 + output_file_path = "output_with_header.raw" # Replace with the desired output file path + + convert_and_append_header(input_directory, output_width) diff --git a/apps/bad/prep/run b/apps/bad/prep/run new file mode 100755 index 000000000..907e711f3 --- /dev/null +++ b/apps/bad/prep/run @@ -0,0 +1,20 @@ +#!/bin/bash + +# aplay -r 4000 /tmp/0.raw +#bug: Terminal exists on b.js, it is dumb terminal, not vt100. + +rm image_*.png image_*.png.raw output.wav ../bad.araw ../bad.vraw + +I=bad.mp4 +S=1:18 +E=1:50 + +ffmpeg -i $I -ss $S -to $E -vn -acodec pcm_u8 -ar 4000 -ac 1 -y output.wav +./wav_divider.py +mv output.raw ../bad.araw + +ffmpeg -i $I -ss $S -to $E -r 5 -vf fps=5 image_%04d.png +./img_convert.py +cat *.png.raw > ../bad.vraw + +ls -al ../bad.* diff --git a/apps/bad/prep/wav_divider.py b/apps/bad/prep/wav_divider.py new file mode 100755 index 000000000..9ce08e28b --- /dev/null +++ b/apps/bad/prep/wav_divider.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 + +def divide_bytes(input_file, output_file): + with open(input_file, 'rb') as infile: + with open(output_file, 'wb') as outfile: + byte = infile.read(1) + while byte: + # Convert byte to integer, divide by 4, and write back as byte + new_byte = bytes([int.from_bytes(byte, byteorder='big') // 3]) + outfile.write(new_byte) + byte = infile.read(1) + +divide_bytes("output.wav", "output.raw") From 5c2ee99bb5e77ff0cabcfb7ebe97125d21779ddc Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:56:17 -0800 Subject: [PATCH 34/67] Update lcars.app.js with 10 new colors and random colors on startup --- apps/lcars/lcars.app.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 858ef51dd..650840d9e 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -16,15 +16,18 @@ let settings = { themeColor3BG: "#0094FF", disableAlarms: false, disableData: false, + randomColors: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { settings[key] = saved_settings[key]; } -/* - * Colors to use - */ + +//Colors to use +var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green', 'Brown', 'Turquoise', 'Magenta', 'Gold', 'Silver', 'Violet', 'Teal', 'Maroon', 'Lavender']; +var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F', '#8B4513', '#40E0D0', '#FF00FF', '#FFD700', '#C0C0C0', '#EE82EE', '#008080', '#800000', '#E6E6FA']; + let color1 = settings.themeColor3BG; let color2 = settings.themeColor1BG; let color3 = settings.themeColor2BG; @@ -32,6 +35,8 @@ let cWhite = "#FFFFFF"; let cBlack = "#000000"; let cGrey = "#424242"; +randomColors(); + /* * Global lcars variables */ @@ -187,6 +192,14 @@ let queueDraw = function() { }, timeout - (Date.now() % timeout)); }; +let randomColors = function(){ + if(settings.randomColors == true){ + let color1 = bg_code[Math.floor(Math.random() * bg_code.length)]; + let color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; + let color3 = bg_code[Math.floor(Math.random() * bg_code.length)]; + } +}; + /** * This function plots a data row in LCARS style. * Note: It can be called async and therefore, the text alignment and @@ -777,4 +790,4 @@ Bangle.loadWidgets(); // Clear the screen once, at startup and draw clock g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); -} \ No newline at end of file +} From 56130e2f745ef4c0260333c501b74f3255c9ddeb Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:56:29 -0800 Subject: [PATCH 35/67] Update lcars.settings.js with 10 new colors and random colors on startup --- apps/lcars/lcars.settings.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 81c71020f..9d5993bc8 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -15,6 +15,7 @@ themeColor3BG: "#0094FF", disableAlarms: false, disableData: false, + randomColors: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -28,8 +29,8 @@ var dataOptions = ["Steps", "Battery", "BattVolt", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"]; var speedOptions = ["kph", "mph"]; - var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green']; - var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F']; + var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green', 'Brown', 'Turquoise', 'Magenta', 'Gold', 'Silver', 'Violet', 'Teal', 'Maroon', 'Lavender']; + var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F', '#8B4513', '#40E0D0', '#FF00FF', '#FFD700', '#C0C0C0', '#EE82EE', '#008080', '#800000', '#E6E6FA']; E.showMenu({ '': { 'title': 'LCARS Clock' }, @@ -121,5 +122,13 @@ save(); }, }, + 'Random colors on open': { + value: settings.randomColors, + format: () => (settings.randomColors ? 'Yes' : 'No'), + onchange: () => { + settings.randomColors = !settings.randomColors; + save(); + }, + }, }); }) From 628c3f4a39d6748e34868f13d987ea7fde566fd8 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:58:01 -0800 Subject: [PATCH 36/67] Update ChangeLog --- apps/lcars/ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index d4a5eb53b..b5f765d22 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -26,4 +26,5 @@ 0.26: Use widget_utils. 0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395) 0.28: Battery Vref implemented correctly. -0.29: Support fastload. \ No newline at end of file +0.29: Support fastload. +0.30: Add 10 new colors to the settings and allows random colors on startup if enabled. From e8ace24ed43c6eb63b5b407536c8d61a0f223660 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:58:16 -0800 Subject: [PATCH 37/67] Update metadata.json --- apps/lcars/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 5fbb8dcf2..60ce9c3f7 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,7 +3,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.29", + "version":"0.30", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", From 42db617249971b54b58f82559f71ab4e8c3c3423 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:58:57 -0800 Subject: [PATCH 38/67] Update README.md --- apps/lcars/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 89918ae87..97b0647a3 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -19,6 +19,7 @@ the "sched" app must be installed on your device. * The lower orange line indicates the battery level. * Display graphs (day or month) for steps + hrm on the second screen. * Customizable theming colors in the settings menu of the app. + * Allow random colors on startup. * Enable or disable the alarm feature. * Enable or disbale the graphs for steps + hrm. From 39c9158b50e327d4a4904004f35fe6dc640bd904 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 14:06:55 -0800 Subject: [PATCH 39/67] Update lcars.app.js Fix Bug with 0.30 update --- apps/lcars/lcars.app.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 650840d9e..8d67dddda 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -35,8 +35,6 @@ let cWhite = "#FFFFFF"; let cBlack = "#000000"; let cGrey = "#424242"; -randomColors(); - /* * Global lcars variables */ @@ -192,7 +190,7 @@ let queueDraw = function() { }, timeout - (Date.now() % timeout)); }; -let randomColors = function(){ +function randomColors() { if(settings.randomColors == true){ let color1 = bg_code[Math.floor(Math.random() * bg_code.length)]; let color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; @@ -786,7 +784,8 @@ Bangle.setUI({mode:"clock",remove:function() { widget_utils.cleanup(); }}); Bangle.loadWidgets(); - + +randomColors();//Apply random colors if applied // Clear the screen once, at startup and draw clock g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); From d9ec4f354c42ba6c5acafdd64f44f2e6541df5c5 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 14:16:44 -0800 Subject: [PATCH 40/67] Fix bug with 0.30 beta --- apps/lcars/lcars.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 8d67dddda..026f02960 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -196,7 +196,7 @@ function randomColors() { let color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; let color3 = bg_code[Math.floor(Math.random() * bg_code.length)]; } -}; +} /** * This function plots a data row in LCARS style. From 482b30dfddc4308a2ca593a13063fd92770c4e0b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:33:39 -0800 Subject: [PATCH 41/67] Update lcars.app.js with final 0.30 version with more colors and random color functionality Update lcars.app.js with final 0.30 version with more colors and random color functionality --- apps/lcars/lcars.app.js | 150 +++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 25 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 026f02960..b540c7b64 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -25,12 +25,27 @@ for (const key in saved_settings) { //Colors to use -var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green', 'Brown', 'Turquoise', 'Magenta', 'Gold', 'Silver', 'Violet', 'Teal', 'Maroon', 'Lavender']; -var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F', '#8B4513', '#40E0D0', '#FF00FF', '#FFD700', '#C0C0C0', '#EE82EE', '#008080', '#800000', '#E6E6FA']; - -let color1 = settings.themeColor3BG; -let color2 = settings.themeColor1BG; -let color3 = settings.themeColor2BG; +var color_options = [ + 'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White', + 'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime', + 'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal', + 'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue', + 'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue' +]; + +var bg_code = [ + '#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF', + '#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700', + '#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000', + '#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4', + '#DAA520', '#00BFFF' +]; + + + +let color1; +let color2; +let color3; let cWhite = "#FFFFFF"; let cBlack = "#000000"; let cGrey = "#424242"; @@ -61,10 +76,105 @@ let convert24to16 = function(input) return "0x"+RGB565.toString(16); }; -let color1C = convert24to16(color1);//Converting colors to the correct format. -let color2C = convert24to16(color2); -let color3C = convert24to16(color3); +//Converting colors to the correct format. +/*let color1C; +let color2C; +let color3C; +let randomColors = function () { + console.log("called"); + + if (settings.randomColors) { + do { + color1 = bg_code[Math.floor(Math.random() * bg_code.length)]; + color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; + color3 = bg_code[Math.floor(Math.random() * bg_code.length)]; + } while (color1 === color2 || color2 === color3 || color1 === color3); + + console.log("random called"); + } else { + color1 = settings.themeColor3BG; + color2 = settings.themeColor1BG; + color3 = settings.themeColor2BG; + } + + //Converting colors to the correct format. + color1C = convert24to16(color1); + color2C = convert24to16(color2); + color3C = convert24to16(color3); +};*/ + +let randomColors = function () { + console.log("called"); + + if (settings.randomColors) { + do { + color1 = getRandomColor(); + color2 = getRandomColor(); + color3 = getRandomColor(); + } while (!areColorsDistinct(color1, color2, color3)); + + console.log("random called"); + } else { + color1 = settings.themeColor3BG; + color2 = settings.themeColor1BG; + color3 = settings.themeColor2BG; + } + + // Converting colors to the correct format. + color1C = convert24to16(color1); + color2C = convert24to16(color2); + color3C = convert24to16(color3); +}; + +// Function to get a random color from the bg_code array. +let getRandomColor = function () { + return bg_code[Math.floor(Math.random() * bg_code.length)]; +}; + +// Function to check if three colors are distinct enough. +let areColorsDistinct = function (color1, color2, color3) { + return ( + color1 !== color2 && + color2 !== color3 && + color1 !== color3 && + hasSufficientContrast(color1, color2) && + hasSufficientContrast(color2, color3) && + hasSufficientContrast(color1, color3) + ); +}; + +// Function to calculate contrast between two colors. +let hasSufficientContrast = function (color1, color2) { + const contrastThreshold = 0.10; // Adjust this threshold based on your preference. + + // Calculate the luminance values (for simplicity, assuming sRGB color space). + const luminance1 = getLuminance(color1); + const luminance2 = getLuminance(color2); + + // Calculate the contrast ratio. + const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05); + + // Check if the contrast ratio meets the threshold. + return contrastRatio >= contrastThreshold; +}; + +// Function to calculate luminance from a hex color. +let getLuminance = function (hexColor) { + const rgb = hexToRgb(hexColor); + return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b; +}; + +// Function to convert hex color to RGB. +let hexToRgb = function (hex) { + const bigint = parseInt(hex.slice(1), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return { r, g, b }; +}; + +randomColors();//Apply random colors if applied /* * Requirements and globals */ @@ -190,14 +300,6 @@ let queueDraw = function() { }, timeout - (Date.now() % timeout)); }; -function randomColors() { - if(settings.randomColors == true){ - let color1 = bg_code[Math.floor(Math.random() * bg_code.length)]; - let color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; - let color3 = bg_code[Math.floor(Math.random() * bg_code.length)]; - } -} - /** * This function plots a data row in LCARS style. * Note: It can be called async and therefore, the text alignment and @@ -235,7 +337,7 @@ let drawData = function(key, y, c){ let _drawData = function(key, y, c){ - key = key.toUpperCase() + key = key.toUpperCase(); let text = key; let value = "ERR"; let should_print= true; @@ -282,7 +384,7 @@ let _drawData = function(key, y, c){ value = Math.round(data.altitude); printRow(text, value, y, c); } - }) + }); } else if(key == "CORET"){ value = locale.temp(parseInt(E.getTemperature())); @@ -385,7 +487,7 @@ let drawPosition0 = function(){ drawHorizontalBgLine(color2, batStart, batX2, 171, 5); drawHorizontalBgLine(cGrey, batX2, 172, 171, 5); for(let i=0; i+batStart<=172; i+=parseInt(batWidth/4)){ - drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8) + drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8); } // Draw Infos @@ -614,7 +716,7 @@ let getWeather = function(){ let speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934; weather.wind = Math.round(wind[1] * speedFactor); - return weather + return weather; } catch(ex) { // Return default @@ -660,7 +762,7 @@ let getAlarmMinutes = function(){ let increaseAlarm = function(){ try{ let minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; - let alarm = require('sched') + let alarm = require('sched'); alarm.setAlarm(TIMER_IDX, { timer : (minutes+5)*60*1000, }); @@ -673,7 +775,7 @@ let decreaseAlarm = function(){ let minutes = getAlarmMinutes(); minutes -= 5; - let alarm = require('sched') + let alarm = require('sched'); alarm.setAlarm(TIMER_IDX, undefined); if(minutes > 0){ @@ -784,8 +886,6 @@ Bangle.setUI({mode:"clock",remove:function() { widget_utils.cleanup(); }}); Bangle.loadWidgets(); - -randomColors();//Apply random colors if applied // Clear the screen once, at startup and draw clock g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); From 6b43fb55d6913a5b0b1f1e798cc6d066c1f39945 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:34:00 -0800 Subject: [PATCH 42/67] Update ChangeLog --- apps/lcars/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index b5f765d22..e2c2b1faf 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -27,4 +27,4 @@ 0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395) 0.28: Battery Vref implemented correctly. 0.29: Support fastload. -0.30: Add 10 new colors to the settings and allows random colors on startup if enabled. +0.30: Add many new colors to the settings and allows random colors on startup if enabled. From 7a18c3b4443f37963672689d4350ccdd92f5304c Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:34:37 -0800 Subject: [PATCH 43/67] Update lcars.settings.js with newest colors --- apps/lcars/lcars.settings.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 9d5993bc8..53e6811df 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -29,9 +29,21 @@ var dataOptions = ["Steps", "Battery", "BattVolt", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"]; var speedOptions = ["kph", "mph"]; - var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green', 'Brown', 'Turquoise', 'Magenta', 'Gold', 'Silver', 'Violet', 'Teal', 'Maroon', 'Lavender']; - var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F', '#8B4513', '#40E0D0', '#FF00FF', '#FFD700', '#C0C0C0', '#EE82EE', '#008080', '#800000', '#E6E6FA']; + var color_options = [ + 'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White', + 'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime', + 'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal', + 'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue', + 'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue' +]; +var bg_code = [ + '#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF', + '#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700', + '#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000', + '#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4', + '#DAA520', '#00BFFF' +]; E.showMenu({ '': { 'title': 'LCARS Clock' }, '< Back': back, From 19ac4017bb884edaca4c1583302f8aaa8aaef089 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:42:48 -0800 Subject: [PATCH 44/67] Change the settings to be based off the array length, so it shows all the colors --- apps/lcars/lcars.settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 53e6811df..1803b916f 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -93,7 +93,7 @@ var bg_code = [ }, 'Theme Color 1': { value: 0 | bg_code.indexOf(settings.themeColor1BG), - min: 0, max: 11, + min: 0, max: color_options.length, format: v => color_options[v], onchange: v => { settings.themeColor1BG = bg_code[v]; @@ -102,7 +102,7 @@ var bg_code = [ }, 'Theme Color 2': { value: 0 | bg_code.indexOf(settings.themeColor2BG), - min: 0, max: 11, + min: 0, max: color_options.length, format: v => color_options[v], onchange: v => { settings.themeColor2BG = bg_code[v]; @@ -111,7 +111,7 @@ var bg_code = [ }, 'Theme Color 3': { value: 0 | bg_code.indexOf(settings.themeColor3BG), - min: 0, max: 11, + min: 0, max: color_options.length, format: v => color_options[v], onchange: v => { settings.themeColor3BG = bg_code[v]; From a5c24cd7b41a795a4e82dc8bf17a830c2d535689 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:44:59 -0800 Subject: [PATCH 45/67] delete debugging code --- apps/lcars/lcars.app.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index b540c7b64..9923fb445 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -77,35 +77,8 @@ let convert24to16 = function(input) }; //Converting colors to the correct format. -/*let color1C; -let color2C; -let color3C; let randomColors = function () { - console.log("called"); - - if (settings.randomColors) { - do { - color1 = bg_code[Math.floor(Math.random() * bg_code.length)]; - color2 = bg_code[Math.floor(Math.random() * bg_code.length)]; - color3 = bg_code[Math.floor(Math.random() * bg_code.length)]; - } while (color1 === color2 || color2 === color3 || color1 === color3); - - console.log("random called"); - } else { - color1 = settings.themeColor3BG; - color2 = settings.themeColor1BG; - color3 = settings.themeColor2BG; - } - - //Converting colors to the correct format. - color1C = convert24to16(color1); - color2C = convert24to16(color2); - color3C = convert24to16(color3); -};*/ - -let randomColors = function () { - console.log("called"); if (settings.randomColors) { do { @@ -114,7 +87,6 @@ let randomColors = function () { color3 = getRandomColor(); } while (!areColorsDistinct(color1, color2, color3)); - console.log("random called"); } else { color1 = settings.themeColor3BG; color2 = settings.themeColor1BG; From cbb2b155aec19c92a90e188e3d23124b0659bc12 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:49:36 -0800 Subject: [PATCH 46/67] Update lcars.settings.js --- apps/lcars/lcars.settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 1803b916f..db583741f 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -93,7 +93,7 @@ var bg_code = [ }, 'Theme Color 1': { value: 0 | bg_code.indexOf(settings.themeColor1BG), - min: 0, max: color_options.length, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor1BG = bg_code[v]; @@ -102,7 +102,7 @@ var bg_code = [ }, 'Theme Color 2': { value: 0 | bg_code.indexOf(settings.themeColor2BG), - min: 0, max: color_options.length, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor2BG = bg_code[v]; @@ -111,7 +111,7 @@ var bg_code = [ }, 'Theme Color 3': { value: 0 | bg_code.indexOf(settings.themeColor3BG), - min: 0, max: color_options.length, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor3BG = bg_code[v]; From b723512dce3db4f921da0660d569b8c99269c31b Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 10 Dec 2023 09:18:21 +0100 Subject: [PATCH 47/67] health: move strideLength to new app myprofile --- apps/health/ChangeLog | 2 +- apps/health/README.md | 2 +- apps/health/app.js | 5 +++-- apps/health/settings.js | 11 ----------- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 98a4a3426..99d8edf72 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -29,4 +29,4 @@ 0.26: Implement API for activity fetching 0.27: Fix typo in daily summary graph code causing graph not to load Fix daily summaries for 31st of the month -0.28: Calculate distance from steps if new stride length setting is set +0.28: Calculate distance from steps if myprofile is installed and stride length is set diff --git a/apps/health/README.md b/apps/health/README.md index b5f6191cc..3fcf394ba 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -13,7 +13,7 @@ To view data, run the `Health` app from your watch. Stores: * Heart rate -* Step count +* Step count (can calculate distance if myprofile is installed and stride length is set) * Movement ## Settings diff --git a/apps/health/app.js b/apps/health/app.js index 60b337321..0b4cef233 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,4 +1,5 @@ let settings; +const myprofile = require("Storage").readJSON("myprofile.json",1)||{}; function menuMain() { E.showMenu({ @@ -18,7 +19,7 @@ function menuStepCount() { /*LANG*/"per hour": () => stepsPerHour(menuStepCount), /*LANG*/"per day": () => stepsPerDay(menuStepCount) }; - if (settings.strideLength) { + if (myprofile.strideLength) { menu[/*LANG*/"distance"] = () => menuDistance(); } @@ -26,7 +27,7 @@ function menuStepCount() { } function menuDistance() { - const distMult = 1*require("locale").distance(settings.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm' + const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm' E.showMenu({ "": { title:/*LANG*/"Distance" }, /*LANG*/"< Back": () => menuStepCount(), diff --git a/apps/health/settings.js b/apps/health/settings.js index 2d5809cdb..88c8061c6 100644 --- a/apps/health/settings.js +++ b/apps/health/settings.js @@ -49,16 +49,5 @@ setSettings(); } }, - - /*LANG*/"Stride length": { - value : settings.strideLength || 0.0, - min:0.01, - step:0.01, - format: v => require("locale").distance(v, 2), - onchange : v => { - settings.strideLength=v; - setSettings(); - }, - }, }); }) From 7fc2ce03756ec6d9cbbb0ba75d6411ddafddcf0a Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 10 Dec 2023 09:39:13 +0100 Subject: [PATCH 48/67] Added myprofile --- apps/myprofile/README.md | 11 ++++++++ apps/myprofile/app.png | Bin 0 -> 1235 bytes apps/myprofile/metadata.json | 18 ++++++++++++ apps/myprofile/settings.js | 52 +++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 apps/myprofile/README.md create mode 100644 apps/myprofile/app.png create mode 100644 apps/myprofile/metadata.json create mode 100644 apps/myprofile/settings.js diff --git a/apps/myprofile/README.md b/apps/myprofile/README.md new file mode 100644 index 000000000..931d50ae2 --- /dev/null +++ b/apps/myprofile/README.md @@ -0,0 +1,11 @@ +# My Profile + +Configure your personal profile. All settings are optional and are only stored on the watch. + +## Available settings + +| Setting | Description | Displayed in | Stored in | Default value | How to measure | +| ------------- | ----------------------------- | ------------------- | --------- | ------------- | ----------------------------------------------------------------- | +| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | +| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | +| Stride length | distance travel with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | diff --git a/apps/myprofile/app.png b/apps/myprofile/app.png new file mode 100644 index 0000000000000000000000000000000000000000..4d95f2c69a6c169e9bec883457618cde06a68122 GIT binary patch literal 1235 zcmV;^1T6cBP)v8ufD;F=f5cAWHnBa^q;TLw;2)r(68^FC#zh|V)=uigcJR)Q z9T@48R^FYr-`jaJ?|aXdWng^E>Mv3zKc-wJ371*Qd z^$g|k8DUT}Mx6JoNP{r>W%5^b!6_n9^ zxa{xCGx-VS@MQetWjmxe{7qSNQjh$DX6U?#(^J5c%6r(L_x}O%w6hYV0`Ct+K7Jrx zBOLxZZ_@O%6AWr!@57sCl3)5oJ!|T;NieC_N>Z(r+t-Gic!( zmwFsPd7x#GOEtd*|4RHc<_qA?)pR7@PncE)f z7$R&2!gkc1lAaR@F2YYYucjo9Z1!$eub9ni?gs|UC660|Flc!-5NGHJR2YKbdy?No@=v!sKt-2DntKB~TL@aw3>=)T(am zA3GoqwU5zm+}e{WWDg%slWF13p+4W+K1MYY*=ej-q>AQ+D-8w_$9wq9df5PZew4^6 z@`@2-9o#KTwO9l-yD8L6t_{4#%#HU0uaZt}rYqcyiBmMs%wAurwyXPo-D$cHnH`t_oC z$1*u)Y;_@;cYX}njghTJFy*x7@{o;`)N|IT#=>^(QY0r1f(9K4*^O3YQ|vF@tG;E8 zYb+MUtJezRhv}?S)oQWZ?wMbW7M$;+1@+34u!ni9cpH#lifRlbBD}-Zt^_e*n`Rh(=&!7jOUo002ovPDHLkV1gUSP}Beb literal 0 HcmV?d00001 diff --git a/apps/myprofile/metadata.json b/apps/myprofile/metadata.json new file mode 100644 index 000000000..79add3b5a --- /dev/null +++ b/apps/myprofile/metadata.json @@ -0,0 +1,18 @@ +{ "id": "myprofile", + "name": "My Profile", + "shortName":"My Profile", + "icon": "app.png", + "type": "settings", + "version":"0.01", + "description": "Configure your personal profile. All settings are optional and only stored on the watch.", + "readme": "README.md", + "tags": "tool,utility", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"myprofile.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"myprofile.json"} + ] +} + diff --git a/apps/myprofile/settings.js b/apps/myprofile/settings.js new file mode 100644 index 000000000..576627212 --- /dev/null +++ b/apps/myprofile/settings.js @@ -0,0 +1,52 @@ +(function(back) { + const FILE = "myprofile.json"; + + const myprofile = Object.assign({ + minHrm: 60, + maxHrm: 200, + strideLength: 0, // 0 = not set + }, require('Storage').readJSON(FILE, true) || {}); + + function writeProfile() { + require('Storage').writeJSON(FILE, myprofile); + } + + // Show the menu + E.showMenu({ + "" : { "title" : /*LANG*/"My Profile" }, + + "< Back" : () => back(), + + /*LANG*/'HR max': { + format: v => /*LANG*/`${v} BPM`, + value: myprofile.maxHrm, + min: 30, max: 220, + onchange: v => { + myprofile.maxHrm = v; + writeProfile(); + } + }, + + /*LANG*/'HR min': { + format: v => /*LANG*/`${v} BPM`, + value: myprofile.minHrm, + min: 30, max: 220, + onchange: v => { + myprofile.minHrm = v; + writeProfile(); + } + }, + + /*LANG*/"Stride length": { + value: myprofile.strideLength, + min:0.00, + step:0.01, + format: v => v ? require("locale").distance(v, 2) : '-', + onchange: v => { + console.log(v); + myprofile.strideLength=v; + writeProfile(); + }, + }, + }); +}) From df662ef2added55835e7429c1a5c6a07fd772036 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 10 Dec 2023 10:09:25 +0100 Subject: [PATCH 49/67] myprofile: updated README --- apps/myprofile/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/myprofile/README.md b/apps/myprofile/README.md index 931d50ae2..f8e79bd6c 100644 --- a/apps/myprofile/README.md +++ b/apps/myprofile/README.md @@ -4,8 +4,8 @@ Configure your personal profile. All settings are optional and are only stored o ## Available settings -| Setting | Description | Displayed in | Stored in | Default value | How to measure | -| ------------- | ----------------------------- | ------------------- | --------- | ------------- | ----------------------------------------------------------------- | -| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | -| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | -| Stride length | distance travel with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | +| Setting | Description | Displayed in | Stored in | Default value | How to measure | +| ------------- | ------------------------------- | ------------------- | --------- | ------------- | ----------------------------------------------------------------- | +| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | +| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | +| Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | From b639dd41f999450435fc40aa1016fbf060145777 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 10 Dec 2023 10:30:55 +0100 Subject: [PATCH 50/67] myprofile updates --- apps/myprofile/README.md | 4 ++++ apps/myprofile/settings.js | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/myprofile/README.md b/apps/myprofile/README.md index f8e79bd6c..4b2fac9f3 100644 --- a/apps/myprofile/README.md +++ b/apps/myprofile/README.md @@ -9,3 +9,7 @@ Configure your personal profile. All settings are optional and are only stored o | HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | | HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | | Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | + +## Developer notes + +Feel free to add additional settings diff --git a/apps/myprofile/settings.js b/apps/myprofile/settings.js index 576627212..80979bd2c 100644 --- a/apps/myprofile/settings.js +++ b/apps/myprofile/settings.js @@ -43,7 +43,6 @@ step:0.01, format: v => v ? require("locale").distance(v, 2) : '-', onchange: v => { - console.log(v); myprofile.strideLength=v; writeProfile(); }, From c815168bda959b9c0c1812b088a7b20d9f9f398d Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Sun, 10 Dec 2023 19:49:37 +0100 Subject: [PATCH 51/67] sched/calendar interface: fix null timezone --- apps/calendar/interface.html | 6 ++++-- apps/sched/interface.html | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/calendar/interface.html b/apps/calendar/interface.html index 71b9a153c..8fa624a40 100644 --- a/apps/calendar/interface.html +++ b/apps/calendar/interface.html @@ -32,12 +32,14 @@ function readFile(input) { const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); const vtz = comp.getFirstSubcomponent('vtimezone'); - const tz = new ICAL.Timezone(vtz); + const tz = vtz != null ? new ICAL.Timezone(vtz) : null; // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { const event = new ICAL.Event(vevent); - event.startDate.zone = tz; + if (tz != null) { + event.startDate.zone = tz; + } holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists const holiday = eventToHoliday(event); diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 68547aa7e..62e45676b 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -20,7 +20,7 @@ function readFile(input) { const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); const vtz = comp.getFirstSubcomponent('vtimezone'); - const tz = new ICAL.Timezone(vtz); + const tz = vtz != null ? new ICAL.Timezone(vtz) : null; // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { @@ -73,7 +73,9 @@ function getAlarmDefaults() { } function eventToAlarm(event, tz, offsetMs) { - event.startDate.zone = tz; + if (tz != null) { + event.startDate.zone = tz; + } const dateOrig = event.startDate.toJSDate(); const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig; From 45e59c000b38e6d5a635ef2769d3fe9c4f3255d2 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Sun, 10 Dec 2023 23:50:09 -0500 Subject: [PATCH 52/67] Update screenshot --- apps/binaryclk/screenshot.png | Bin 2437 -> 3081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/binaryclk/screenshot.png b/apps/binaryclk/screenshot.png index 57f95d8abfffde9990e34439d9e2d7b46d3e1e1b..1fc824e2b4c858a3b206cfffd13d4f96bbe65a0c 100644 GIT binary patch literal 3081 zcmds(eK?bCAIGtb=B3@SHF@dQUB-wJSs@H5G2y{X)J9*1Hu^BO< zHbbk18zBkF+gdM@cd4Y6`||wp{PFztJpVq&d0f|d9iQuW9Ov&m&+qsAovC=7qY`8% zL`q6Z>DWjS31nV$oLD%w z_SJf$;O7s!9oOjFNFuizM+`ed1H{21YF1?b{`|!)5BD#bu9(hpFF3^0>0g~c!F*>m z`a&$zUNjgC3Z~PkvSUM6eQ$TZgOl+Y`Y}c0T=%nnmgLz>#Z`|HYaa{dsiIec-|EVb z-bF3@b*R1eX%f0x8=WMc6^*vkUE}?DNX^Y!MbLKb@-~=WIB|cmEFUGwp-e^ui4@N8 z`UKgi99sm}M#$FgBfR_?J3ImH@PmY8^PCkH>`Cv;)Y;&rQ z8ci~tyLRzMBb?i=B=K53m5%_Kw;EQaPFp>#T#2A=y_Z*>u zi?~fEk%{4C#qk#c=1);$ndzzOihG~a`5vTTkv}&lCnr#Hus60(FikFJM7mt3=kKIV za(ZV{3%O4SBLBy{uZ26;Vzi6zuQwa~VcyiAz-&4dj+kBRz>WJWws^upQzK{1{ih7G zdIKA6?~e*Ua3S?XQ<@QXET7xIm*Zk~?x~AxTk3oW*y^5#BwUj=vE$OJaN>ff*jj-S zp^gBJAecS6)8}0__FGU8+_c^=g^tZCvDN*y>G#@}hBRBVd`Y6u&1iGV_w}Wa`@y1b zZYP^mV$B8?l^c~>B5qEw1g7@prpzL^Z78b}b*RdgK80F} z5KSpRUe=8=l0`4I-n#n0URu$2_iulWAE%pure;t08vZyVOXs~X!= z*fi%L4Lksq>Qzzzi1an|q_DX?nIbmEII66f?vd(7`Ez%&E)I5m>opJnIojuC+)~|^ zlX}`wI9P1lc3&70j3bzHP{ybTybucHWn4W~KD zOQ;2%C1lYazazt#48G;oT80V;$yWEuLcq**$thyOa_%!s2>{i}NGJeZxxO`G0wo9e z$b&)_*`EVdZ!Z^v9TeRPP+BTG

lq$!=~-836yE7qbG4!toJSBmI%#ZW&nzt=+lv zylaTxD7WzC0AcS<{mM*)VmjUW#~nqsIMtX~184_<0P#-@LYSV~&8_jx%%cjgQGsHX zpa}1Tj8u&f`xb^3I4SPi0<@%rg9*~X+DSp|72i^xB_Mw89c#yV1UXXT>wq?1q>Spx8&1iesN}2d0 zr84=4s=5P|RUQFybv&)(MQMOMLT#yrzHe_#9smu3eD0UZ_?T>d0l{(y&beoGYUo_n z1B~^zGe>+!t`cYmjC3xDY;-;1E|-R}0Nv*6N}+HlG%%EgFMd@}>BU~jePWLW?DR|C zR?h;0r~~%$DMiiMO80bb;9|~xW`^~?As1xq;lL@UHb?i4V_NgC_kLIn>^;sKn-Cak zV^mp$k{X#4I7U5Q;2{O#JJCa{q`(n*aGiIkKj_2?K@&j=^uSpmGa9D5KdMvi3Yux8 zt}*{;KVO|Yt2}5mOdPt}$+yA>zO5ZAKreOnzl=4F>fGfsa{#%yF;=3Bhe06ogo~;$ zFKfJm+W6gMz@qo&bTYKo^b^=}3!d}rr%7}b*~6S}17OuTf%?GgUYc~vvDhC6v3uD0 zUY~1i6iTOH&Q&`U=TI;}8}KxU^R^lyxs7-tud4_@;@Eq>#s4D)=`HV_(w|0bB?gef z;i#xLL?a-ByTi$Wrr84q<+%ww3wSlO)?FIcWVR|;CqBwj2c|2FQLJfaf8-l;h7TPf zk@l&9&uDt_g>&ZBPfxq1oFi={r+UawM3PC_18uNf-txq>YQvouNO#Yr3k2RKikpdG zw}pkS{(O;_@#9SA!FuJI@6jW1jhh2yz_Do#3bqQRM67~;sJ%k{O$MYU!46PIXbzqp zevJj_Y|3Cxs2Ptm?2eIs8Movt?P8jAJJZ5z6eYCEuI@Ota$|?lkmF#NMRmo3PuKst zU+8UwZ#N}D+Xyqc{0XJ;Wm|2cwRm)%j?z5WoJO}u?E9>xZr~_EyP6lGquwmeP)dvE z;%2a&0>s)N=wo?WBlJqKVRT&!-GWShQIPp5wtn*RX)dBfY+iR5vQFZ>k;DmvM+U zoebzpMq^(R62581cd96kcINgj1W;W1G9s^q^wrCENHgx0<2r&{?Siy@|I63`EO&PW z##`Ri7!ZVfj~;`Ih_6QvPaME;kVe`g7j zot^dsqyUx3qRotbuyZ#;4oKyY8q-1HHRF~TuzCBY*$tR@x>DY)qa9G7V4lAiN}y&q zpq25!WKD2s@W(K{_5m!7F@SxB^}?0PsGI2hm97nxqPewU)_aXKl12%};^XN8_`W24 zcSiY@CDqUoEN!sXu1fAoOpQSRF8*N#E3?{dRPNQ{@|gFVJ=ec1ZI4-RcwaF-A3@52 zYCs4myeC~B+3uw)`cIDv1hwuWZ-Dw#UOC2tNVYxr+u!@sE?y#Vx0gERfU~c~k}m%P DZm^|= literal 2437 zcmeHJc~BEs9)^SjNaPrI1IiK@R$wdwgp4PL1P}(J;TRzVK+y)2uc_=&T1$fl!AhS8tIJVdGKu9yHt?i-oRc8P*6}MlN{|m69QK5jYiE*?N@rWySwbT z5zA+s{rGVEyd&j(EOPl5evg65qlfxwX>$3bo2t!GNFO7sEDYrnfA^4!Sr}FI6glTN z^ixGvYs*R|Yt;kOi?|4ELJSNe8+{eJj%io7v4T+Y<@Ht>%1>GEo*g1pD!Y)3sE6mf zR=|jf$z~kbH01n^JEVV#qeX+>bF0C``Fgb625zkAKb&6f1EtAj>t-RJr>UK-HN-=V z3$AVC2Z+?Ik4mXpw~^x|wj81avZRRem4YLVv-+LMW73bt%aZ$4jKnW!Ogb)iBx-ZPYSbW4?H=@6e`$tK?Lxw@h4?cJTiCx+s51@1J zde3DT_jNp5fw6pK`%6rf-T>312mZAgs4kXy7l06fZJLGBgNN1I1HI^9 zZ3kBN+~hRYF^gHg!;77PUjO4Qb3=D^rQ`4^CuT4~mTsX@A^yqX0v||F5PgSx8EEEPu3 z&n=~Mk9TAz_Uq#%;h~@L!pU&2qE^t!Jt0XPb__LV8k_{b1R$; z-*x3m;mCjZcGyl;nD>8k>!16%ZDYYH4> zwJ~VOXLLB@o4pON(iE>{Yi?qN^}_ z(t7*)-#(c%^2Fn@kxB6v=+C6qZ^!3#gpur|fn)rvQp`)(<{j=uK=QXk!&@7GWZof- zgu8%ba&vUI4Ip{rIXnQ6j31Zy;oShqHXG^8J(98IMr}1j3S(w7%6DTNMw4IaH#iVIICx)pN7nKI2v~ zOXKk6Zs195#}VN9_}fEHFbbPtL1(z|7c-(O(rx zufLfCmX~ZynS)5tIeOq4X(xSb3Z%^c%lfanwb{bGB4*b=NC^?V6>(m@4YB0s4Wq)K zPiIBnLa5}sm)<5afZSf+Qtl#Gz-Tr6j({?7E;5!B@!;%ZhQ!q~!Ffp2V{my&!>GB< zA>xmWnIqHjrT5GjKvQbACK0;{S-b0b*E?aeEF=x_rg4X|(UAAhRQGjv#?*xRoVvU- z*0bo>#EpX?E|_*0mCm`5YQ4NtB2~QqggUqi6gB@+eb9#@T7^1{p6PmOjU5ZN2q4Tt z*)N{7Vv7rFL8QzT#L@4Kj_m60Vu>xY-LFa#QKKGL%N+Jmjl2B|zetmRs`19$AH@EZ z*mf{p(%gvQAIjps=QF3(!wPkh-Ee43a)KBry^d=Y6<)_Jr65)bI&9WJuvN8Q^>4V^ zIQpuKb%eDAn}zib+N)c_+gJ!wzM?#pS=qEF@AVwM$5n%v&=tLZ76pp`P5_VBknqM% zt(Dg;uOyz`dQJ{~7jCel`LXlQo<-HJt`91YeTmHMb2+vaxBgM8hYGgiL>oNX3)B8U zbFQGwVL2}mMX Date: Mon, 11 Dec 2023 07:32:58 +0100 Subject: [PATCH 53/67] health: include step data for today --- apps/health/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/health/app.js b/apps/health/app.js index 0b4cef233..221673e45 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -72,6 +72,10 @@ function stepsPerDay(back, mult) { current_selection = "stepsPerDay"; var data = new Uint16Array(32); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); + // Include data for today + if (data[(new Date()).getDate()] === 0) { + data[(new Date()).getDate()] = Bangle.getHealthStatus("day").steps; + } if (mult !== undefined) { // Calculate distance from steps data.forEach((d, i) => data[i] = d*mult+0.5); From c7bfc08e969f04dbe62cb126f15fe84cf46d4adf Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 17 Sep 2023 01:29:05 +0200 Subject: [PATCH 54/67] spotrem: some refactoring shortening the code --- apps/spotrem/ChangeLog | 1 + apps/spotrem/app.js | 149 ++++++++++++++----------------------- apps/spotrem/metadata.json | 2 +- 3 files changed, 56 insertions(+), 96 deletions(-) diff --git a/apps/spotrem/ChangeLog b/apps/spotrem/ChangeLog index 56ded4e5c..d5e8c9351 100644 --- a/apps/spotrem/ChangeLog +++ b/apps/spotrem/ChangeLog @@ -9,3 +9,4 @@ when fastloading. 0.08: Issue newline before GB commands (solves issue with console.log and ignored commands) 0.09: Don't send the gadgetbridge wake command twice. Once should do since we issue newline before GB commands. +0.10: Some refactoring to shorten the code. diff --git a/apps/spotrem/app.js b/apps/spotrem/app.js index 48274df44..d03aede34 100644 --- a/apps/spotrem/app.js +++ b/apps/spotrem/app.js @@ -1,6 +1,6 @@ { /* -Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); +* Bluetooth.println(JSON.stringify({t:"intent", target:"", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], package:"", class:"", mimetype:"", data:"", extra:{someKey:"someValueOrString", anotherKey:"anotherValueOrString",...}})); */ let R; @@ -133,6 +133,17 @@ let backToGfx = function() { The functions for interacting with Android and the Spotify app */ +let createCommand = function(o) { + return ()=>{ + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(o)); + }; +}; + +let assembleSearchString = function() { + return (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\"")); +}; + simpleSearch = ""; let simpleSearchTerm = function() { // input a simple search term without tags, overrides search with tags (artist and track) require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);}); @@ -153,97 +164,44 @@ let albumSearchTerm = function() { // input album to search for require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);}); }; -let searchPlayWOTags = function() {//make a spotify search and play using entered terms - searchString = simpleSearch; - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let searchPlayWOTags = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:simpleSearch}, flags:["FLAG_ACTIVITY_NEW_TASK"]}); -let searchPlayWTags = function() {//make a spotify search and play using entered terms - searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\"")); - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let searchPlayWTags = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:assembleSearchString()}, flags:["FLAG_ACTIVITY_NEW_TASK"]}); -let playVreden = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let playVreden = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"]}); -let playVredenAlternate = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let playVredenAlternate = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]}); -let searchPlayVreden = function() {//Play the track "Vreden" by Sara Parkman via search and play - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let searchPlayVreden = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]}); -let openAlbum = function() {//Play EP "The Blue Room" by Coldplay - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let openAlbum = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]}); -let searchPlayAlbum = function() {//Play EP "The Blue Room" by Coldplay via search and play - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -}; +let searchPlayAlbum = createCommand({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]}); let spotifyWidget = function(action) { Bluetooth.println(""); Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"})); }; -let gadgetbridgeWake = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"})); -}; +let gadgetbridgeWake = createCommand({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"}); -let spotifyPlaylistDW = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDW = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM1 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM1 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM2 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM2 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM3 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM3 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM4 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM4 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM5 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM5 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDM6 = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDM6 = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistDD = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistDD = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); -let spotifyPlaylistRR = function() { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -}; +let spotifyPlaylistRR = createCommand({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]}); // Spotify Remote Menu let spotifyMenu = { @@ -256,11 +214,14 @@ let spotifyMenu = { "Exit Spotify Remote" : ()=>{load();} }; +let menuBackFunc = ()=>{ + if (backToMenu) E.showMenu(spotifyMenu); + if (!backToMenu) backToGfx(); +}; let controlMenu = { "" : { title : " Controls ", - back: () => {if (backToMenu) E.showMenu(spotifyMenu); - if (!backToMenu) backToGfx();} }, + back: menuBackFunc }, "Play" : ()=>{Bangle.musicControl("play");}, "Pause" : ()=>{Bangle.musicControl("pause");}, "Previous" : ()=>{spotifyWidget("PREVIOUS");}, @@ -271,32 +232,30 @@ let controlMenu = { let searchMenu = { "" : { title : " Search ", - back: () => {if (backToMenu) E.showMenu(spotifyMenu); - if (!backToMenu) backToGfx();} }, - "Search term w/o tags" : ()=>{simpleSearchTerm();}, - "Execute search and play w/o tags" : ()=>{searchPlayWOTags();}, - "Search term w tag \"artist\"" : ()=>{artistSearchTerm();}, - "Search term w tag \"track\"" : ()=>{trackSearchTerm();}, - "Search term w tag \"album\"" : ()=>{albumSearchTerm();}, - "Execute search and play with tags" : ()=>{searchPlayWTags();}, + back: menuBackFunc }, + "Search term w/o tags" : simpleSearchTerm, + "Execute search and play w/o tags" : searchPlayWOTags, + "Search term w tag \"artist\"" : artistSearchTerm, + "Search term w tag \"track\"" : trackSearchTerm, + "Search term w tag \"album\"" : albumSearchTerm, + "Execute search and play with tags" : searchPlayWTags, }; let savedMenu = { "" : { title : " Saved ", - back: () => {if (backToMenu) E.showMenu(spotifyMenu); - if (!backToMenu) backToGfx();} }, - "Play Discover Weekly" : ()=>{spotifyPlaylistDW();}, - "Play Daily Mix 1" : ()=>{spotifyPlaylistDM1();}, - "Play Daily Mix 2" : ()=>{spotifyPlaylistDM2();}, - "Play Daily Mix 3" : ()=>{spotifyPlaylistDM3();}, - "Play Daily Mix 4" : ()=>{spotifyPlaylistDM4();}, - "Play Daily Mix 5" : ()=>{spotifyPlaylistDM5();}, - "Play Daily Mix 6" : ()=>{spotifyPlaylistDM6();}, - "Play Daily Drive" : ()=>{spotifyPlaylistDD();}, - "Play Release Radar" : ()=>{spotifyPlaylistRR();}, - "Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();}, - "Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();}, - "Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();}, + back: menuBackFunc }, + "Play Discover Weekly" : spotifyPlaylistDW, + "Play Daily Mix 1" : spotifyPlaylistDM1, + "Play Daily Mix 2" : spotifyPlaylistDM2, + "Play Daily Mix 3" : spotifyPlaylistDM3, + "Play Daily Mix 4" : spotifyPlaylistDM4, + "Play Daily Mix 5" : spotifyPlaylistDM5, + "Play Daily Mix 6" : spotifyPlaylistDM6, + "Play Daily Drive" : spotifyPlaylistDD, + "Play Release Radar" : spotifyPlaylistRR, + "Play \"Vreden\" by Sara Parkman via uri-link" : playVreden, + "Open \"The Blue Room\" EP (no autoplay)" : openAlbum, + "Play \"The Blue Room\" EP via search&play" : searchPlayAlbum, }; Bangle.loadWidgets(); diff --git a/apps/spotrem/metadata.json b/apps/spotrem/metadata.json index 4e6463083..531a4e5c4 100644 --- a/apps/spotrem/metadata.json +++ b/apps/spotrem/metadata.json @@ -1,7 +1,7 @@ { "id": "spotrem", "name": "Remote for Spotify", - "version": "0.09", + "version": "0.10", "description": "Control spotify on your android device.", "readme": "README.md", "type": "app", From 16cc803b8632f07a71a9ec871be2209dc544dd16 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Tue, 12 Dec 2023 19:30:46 +0100 Subject: [PATCH 55/67] myprofile: Add birthday setting --- apps/myprofile/README.md | 14 ++-- apps/myprofile/settings.js | 131 +++++++++++++++++++++++++++---------- 2 files changed, 105 insertions(+), 40 deletions(-) diff --git a/apps/myprofile/README.md b/apps/myprofile/README.md index 4b2fac9f3..6879d84fc 100644 --- a/apps/myprofile/README.md +++ b/apps/myprofile/README.md @@ -4,12 +4,14 @@ Configure your personal profile. All settings are optional and are only stored o ## Available settings -| Setting | Description | Displayed in | Stored in | Default value | How to measure | -| ------------- | ------------------------------- | ------------------- | --------- | ------------- | ----------------------------------------------------------------- | -| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | -| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | -| Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | +| Setting | Description | Displayed in | Stored in | Default value | How to measure | +| ------------- | ------------------------------- | ------------------- | ------------ | ------------- | ----------------------------------------------------------------- | +| Birthday | Used to calculate age | year, month, day | 'YYYY-MM-DD' | 01.01.1970 | - | +| HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | +| HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | +| Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | ## Developer notes -Feel free to add additional settings +- Feel free to add additional settings. +- For values without reasonable defaults never assume that a value is set. Always check the value before using it. diff --git a/apps/myprofile/settings.js b/apps/myprofile/settings.js index 80979bd2c..ba2603d65 100644 --- a/apps/myprofile/settings.js +++ b/apps/myprofile/settings.js @@ -5,47 +5,110 @@ minHrm: 60, maxHrm: 200, strideLength: 0, // 0 = not set + birthday: '1970-01-01', }, require('Storage').readJSON(FILE, true) || {}); function writeProfile() { require('Storage').writeJSON(FILE, myprofile); } - // Show the menu - E.showMenu({ - "" : { "title" : /*LANG*/"My Profile" }, + const ageMenu = () => { + const date = new Date(myprofile.birthday); - "< Back" : () => back(), + E.showMenu({ + "" : { "title" : /*LANG*/"Birthday" }, - /*LANG*/'HR max': { - format: v => /*LANG*/`${v} BPM`, - value: myprofile.maxHrm, - min: 30, max: 220, - onchange: v => { - myprofile.maxHrm = v; - writeProfile(); - } - }, - - /*LANG*/'HR min': { - format: v => /*LANG*/`${v} BPM`, - value: myprofile.minHrm, - min: 30, max: 220, - onchange: v => { - myprofile.minHrm = v; - writeProfile(); - } - }, - - /*LANG*/"Stride length": { - value: myprofile.strideLength, - min:0.00, - step:0.01, - format: v => v ? require("locale").distance(v, 2) : '-', - onchange: v => { - myprofile.strideLength=v; - writeProfile(); + "< Back" : () => { + if (date != new Date(myprofile.birthday)) { + if (date > new Date()) { + E.showPrompt(/*LANG*/"Birthday must not be in future!", { + buttons : {"Ok":true}, + }).then(() => ageMenu()); + } else { + // Birthday changed + const age = (new Date()).getFullYear() - date.getFullYear(); + const newMaxHRM = 220-age; + E.showPrompt(/*LANG*/`Set HR max to ${newMaxHRM} calculated from age?`).then(function(v) { + myprofile.birthday = date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, '0') + "-" + date.getDate().toString().padStart(2, '0'); + if (v) { + myprofile.maxHrm = newMaxHRM; + writeProfile(); + } + mainMenu(); + }); + } + } else { + mainMenu(); + } }, - }, - }); + + /*LANG*/"Day": { + value: date ? date.getDate() : null, + min: 1, + max: 31, + wrap: true, + onchange: v => { + date.setDate(v); + } + }, + /*LANG*/"Month": { + value: date ? date.getMonth() + 1 : null, + format: v => require("date_utils").month(v), + onchange: v => { + date.setMonth((v+11)%12); + } + }, + /*LANG*/"Year": { + value: date ? date.getFullYear() : null, + min: 1900, + max: (new Date()).getFullYear(), + onchange: v => { + date.setFullYear(v); + } + }, + }); + }; + + const mainMenu = () => { + E.showMenu({ + "" : { "title" : /*LANG*/"My Profile" }, + + "< Back" : () => back(), + + /*LANG*/"Birthday" : () => ageMenu(), + + /*LANG*/'HR max': { + format: v => /*LANG*/`${v} BPM`, + value: myprofile.maxHrm, + min: 30, max: 220, + onchange: v => { + myprofile.maxHrm = v; + writeProfile(); + } + }, + + /*LANG*/'HR min': { + format: v => /*LANG*/`${v} BPM`, + value: myprofile.minHrm, + min: 30, max: 220, + onchange: v => { + myprofile.minHrm = v; + writeProfile(); + } + }, + + /*LANG*/"Stride length": { + value: myprofile.strideLength, + min:0.00, + step:0.01, + format: v => v ? require("locale").distance(v, 2) : '-', + onchange: v => { + myprofile.strideLength=v; + writeProfile(); + }, + }, + }); + }; + + mainMenu(); }) From 9f3d78cbeb5b7682c506faa0b301b22cc71dfd11 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Tue, 12 Dec 2023 21:14:28 +0100 Subject: [PATCH 56/67] myprofile: Added height setting --- apps/myprofile/README.md | 1 + apps/myprofile/settings.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/myprofile/README.md b/apps/myprofile/README.md index 6879d84fc..b8bb0daf0 100644 --- a/apps/myprofile/README.md +++ b/apps/myprofile/README.md @@ -9,6 +9,7 @@ Configure your personal profile. All settings are optional and are only stored o | Birthday | Used to calculate age | year, month, day | 'YYYY-MM-DD' | 01.01.1970 | - | | HR max | maximum heart rate | BPM | BPM | 60 | Use maximum value when exercising.
If unsure set to 220-age. | | HR min | minimum heart rate | BPM | BPM | 200 | Measure your heart rate after waking up | +| Height | Body height | local length unit | meter | 0 (=not set) | - | | Stride length | distance traveled with one step | local length unit | meter | 0 (=not set) | Walk 10 steps and divide the travelled distance by 10 | ## Developer notes diff --git a/apps/myprofile/settings.js b/apps/myprofile/settings.js index ba2603d65..20907d383 100644 --- a/apps/myprofile/settings.js +++ b/apps/myprofile/settings.js @@ -6,6 +6,7 @@ maxHrm: 200, strideLength: 0, // 0 = not set birthday: '1970-01-01', + height: 0, // 0 = not set }, require('Storage').readJSON(FILE, true) || {}); function writeProfile() { @@ -20,20 +21,20 @@ "< Back" : () => { if (date != new Date(myprofile.birthday)) { + // Birthday changed if (date > new Date()) { E.showPrompt(/*LANG*/"Birthday must not be in future!", { buttons : {"Ok":true}, }).then(() => ageMenu()); } else { - // Birthday changed const age = (new Date()).getFullYear() - date.getFullYear(); const newMaxHRM = 220-age; E.showPrompt(/*LANG*/`Set HR max to ${newMaxHRM} calculated from age?`).then(function(v) { myprofile.birthday = date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, '0') + "-" + date.getDate().toString().padStart(2, '0'); if (v) { myprofile.maxHrm = newMaxHRM; - writeProfile(); } + writeProfile(); mainMenu(); }); } @@ -77,6 +78,29 @@ /*LANG*/"Birthday" : () => ageMenu(), + /*LANG*/'Height': { + value: myprofile.height, + min: 0, max: 300, + step:0.01, + format: v => v ? require("locale").distance(v, 2) : '-', + onchange: v => { + if (v !== myprofile.height) { + // height changed + myprofile.height = v; + setTimeout(() => { + const newStrideLength = myprofile.height * 0.414; + E.showPrompt(/*LANG*/`Set Stride length to ${require("locale").distance(newStrideLength, 2)} calculated from height?`).then(function(v) { + if (v) { + myprofile.strideLength = newStrideLength; + } + writeProfile(); + mainMenu(); + }); + }, 1); + } + } + }, + /*LANG*/'HR max': { format: v => /*LANG*/`${v} BPM`, value: myprofile.maxHrm, From 8d2f4c9570e9f384d0d7d96734f669ad1e0225cf Mon Sep 17 00:00:00 2001 From: Leon Matthes Date: Wed, 13 Dec 2023 21:30:52 +0100 Subject: [PATCH 57/67] CalClock: No buzz during quiet hours Tweak 30-minute buzz --- apps/calclock/ChangeLog | 1 + apps/calclock/calclock.js | 6 ++++-- apps/calclock/metadata.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog index b157136a5..ee9daa17e 100644 --- a/apps/calclock/ChangeLog +++ b/apps/calclock/ChangeLog @@ -5,3 +5,4 @@ 0.05: Show calendar colors & improved all day events. 0.06: Improved multi-line locations & titles 0.07: Buzz 30, 15 and 1 minute before an event +0.08: No buzz during quiet hours & tweaked 30-minute buzz diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js index 622b77612..5266bf2f4 100644 --- a/apps/calclock/calclock.js +++ b/apps/calclock/calclock.js @@ -119,10 +119,12 @@ function fullRedraw() { function buzzForEvents() { let nextEvent = next[0]; if (!nextEvent) return; - if (nextEvent.allDay) return; + // No buzz for all day events or events before 7am + // TODO: make this configurable + if (nextEvent.allDay || (new Date(nextEvent.timestamp * 1000)).getHours() < 7) return; let minToEvent = Math.round((nextEvent.timestamp - getTime()) / 60.0); switch (minToEvent) { - case 30: require("buzz").pattern(","); break; + case 30: require("buzz").pattern(":"); break; case 15: require("buzz").pattern(", ,"); break; case 1: require("buzz").pattern(": : :"); break; } diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json index 5cb47956b..4334a4476 100644 --- a/apps/calclock/metadata.json +++ b/apps/calclock/metadata.json @@ -2,7 +2,7 @@ "id": "calclock", "name": "Calendar Clock", "shortName": "CalClock", - "version": "0.07", + "version": "0.08", "description": "Show the current and upcoming events synchronized from Gadgetbridge", "icon": "calclock.png", "type": "clock", From aea3033f52047d4de6756ed3e599db7a6d7f5464 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 14 Dec 2023 07:13:03 +0100 Subject: [PATCH 58/67] chess: Use Bangle.setBacklight() instead of Bangle.setLCDTimeout() to turn off the LCD while the computer is thinking. --- apps/chess/ChangeLog | 1 + apps/chess/app.js | 13 +++++++------ apps/chess/metadata.json | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/chess/ChangeLog b/apps/chess/ChangeLog index fb08248ff..064c7285e 100644 --- a/apps/chess/ChangeLog +++ b/apps/chess/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Bugfixes +0.03: Use Bangle.setBacklight() diff --git a/apps/chess/app.js b/apps/chess/app.js index 3d584b261..d2141c128 100644 --- a/apps/chess/app.js +++ b/apps/chess/app.js @@ -6,7 +6,6 @@ Bangle.loadWidgets(); // load before first appRect call const FIELD_WIDTH = Bangle.appRect.w/8; const FIELD_HEIGHT = Bangle.appRect.h/8; const SETTINGS_FILE = "chess.json"; -const DEFAULT_TIMEOUT = Bangle.getOptions().lockTimeout; const ICON_SIZE=45; const ICON_BISHOP = require("heatshrink").decompress(atob("lstwMB/4Ac/wFE4IED/kPAofgn4FDGon8j4QEBQgQE4EHBQcACwfAgF/BQYWD8EAHAX+NgI4C+AQEwAQDDYIhDDYMDCAQKBGQQsHHogKDCAJODCAI3CHoQKCHoIQDHoIQCFgoQBFgfgIQYmBEIQECKgIrCBYQKDC4OBg/8iCvEAC+AA=")); const ICON_PAWN = require("heatshrink").decompress(atob("lstwMB/4At/AFEGon4h4FDwE/AgX8CAngCAkAv4bDgYbECAf4gAhD4AhD/kAg4mDCAkACAYbBEIYQBG4gbDEII9DFhXAgEfBQYWDEwJUC/wKBGQXwCAgEBE4RCBCAYmBCAQmCCAQmBCAbdCCAIbCQ4gAYwA=")); @@ -192,6 +191,7 @@ Bangle.drawWidgets(); // drag selected field Bangle.on('drag', (ev) => { + if (showmenu) return; const newx = curfield[0]+ev.dx; const newy = curfield[1]+ev.dy; if (newx >= 0 && newx <= 7*FIELD_WIDTH) { @@ -230,7 +230,7 @@ Bangle.on('touch', (button, xy) => { drawSelectedField(); if (!finished) { // do computer move - Bangle.setLCDTimeout(0.1); // this can take some time, turn off to save power + Bangle.setBacklight(false); // this can take some time, turn off to save power showMessage(/*LANG*/"Calculating.."); setTimeout(() => { const compMove = state.findmove(settings.computer_level+1); @@ -240,15 +240,15 @@ Bangle.on('touch', (button, xy) => { } Bangle.setLCDPower(true); Bangle.setLocked(false); - Bangle.setLCDTimeout(DEFAULT_TIMEOUT/1000); // restore + Bangle.setBacklight(true); if (!showmenu) { showAlert(result.string); } - }, 200); // execute after display update + }, 300); // execute after display update } }; move(posFrom, posTo,cb); - }, 200); // execute after display update + }, 100); // execute after display update } // piece_sel === 0 startfield[0] = startfield[1] = undefined; piece_sel = 0; @@ -277,7 +277,9 @@ setWatch(() => { E.showMenu({ "" : { title : /*LANG*/"Chess settings" }, "< Back" : () => closeMenu(), + /*LANG*/"Exit" : () => load(), /*LANG*/"New Game" : () => { + finished = false; state = engine.p4_fen2state(engine.P4_INITIAL_BOARD); writeSettings(); closeMenu(); @@ -296,6 +298,5 @@ setWatch(() => { writeSettings(); } }, - /*LANG*/"Exit" : () => load(), }); }, BTN, { repeat: true, edge: "falling" }); diff --git a/apps/chess/metadata.json b/apps/chess/metadata.json index 3c2ea69ac..d6d6fd70a 100644 --- a/apps/chess/metadata.json +++ b/apps/chess/metadata.json @@ -2,7 +2,7 @@ "id": "chess", "name": "Chess", "shortName": "Chess", - "version": "0.02", + "version": "0.03", "description": "Chess game based on the [p4wn engine](https://p4wn.sourceforge.net/). Drag on the touchscreen to move the green cursor onto a piece, select it with a single touch and drag the now red cursor around. Release the piece with another touch to finish the move. The button opens a menu.", "icon": "app.png", "tags": "game", From 6d56a8512b2f5ac656641b01888d0dc251290ea1 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Fri, 15 Dec 2023 20:12:53 +0100 Subject: [PATCH 59/67] widhrzone: Read maximum HRM from myprofile drop own settings --- apps/widhrzone/ChangeLog | 2 ++ apps/widhrzone/metadata.json | 7 +++---- apps/widhrzone/settings.js | 26 -------------------------- apps/widhrzone/widget.js | 14 ++++++-------- 4 files changed, 11 insertions(+), 38 deletions(-) create mode 100644 apps/widhrzone/ChangeLog delete mode 100644 apps/widhrzone/settings.js diff --git a/apps/widhrzone/ChangeLog b/apps/widhrzone/ChangeLog new file mode 100644 index 000000000..1239cd63a --- /dev/null +++ b/apps/widhrzone/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Read maximum HRM from myprofile diff --git a/apps/widhrzone/metadata.json b/apps/widhrzone/metadata.json index 9616e61fa..6d4f7bf5a 100644 --- a/apps/widhrzone/metadata.json +++ b/apps/widhrzone/metadata.json @@ -2,7 +2,7 @@ "id": "widhrzone", "name": "Heart rate zone widget", "shortName": "HRzone widget", - "version": "0.01", + "version": "0.02", "description": "Widget that displays the current out of five heart rate training zones 1. HEALTH (50-60% of max. HR, Recovery, grey), 2. FAT-B (60-70% of max. HR, burns fat, blue), 3. AROBIC (70-80% of max. HR, Endurance, green), 4. ANAROB (80-90% of max. HR, Speed, yellow), 5. MAX (90-100% of max. HR, red). Only visible when heart rate monitor is active and inside one of the zones. Requires to set the maximum heart rate in settings (if unsure set to 220-age).", "icon": "widget.png", "type": "widget", @@ -10,8 +10,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "screenshots" : [ { "url":"screenshot.png" } ], "storage": [ - {"name":"widhrzone.wid.js","url":"widget.js"}, - {"name":"widhrzone.settings.js","url":"settings.js"} + {"name":"widhrzone.wid.js","url":"widget.js"} ], - "data": [{"name":"widhrzone.json"}] + "dependencies": {"myprofile":"app"} } diff --git a/apps/widhrzone/settings.js b/apps/widhrzone/settings.js deleted file mode 100644 index 7165baea5..000000000 --- a/apps/widhrzone/settings.js +++ /dev/null @@ -1,26 +0,0 @@ -(function(back) { - const CONFIGFILE = "widhrzone.json"; - // Load settings - const settings = Object.assign({ - maxHrm: 200, - }, require("Storage").readJSON(CONFIGFILE,1) || {}); - - function writeSettings() { - require('Storage').writeJSON(CONFIGFILE, settings); - } - - // Show the menu - E.showMenu({ - "" : { "title" : "HRzone widget" }, - "< Back" : () => back(), - /*LANG*/'HR max': { - format: v => v, - value: settings.maxHrm, - min: 30, max: 220, - onchange: v => { - settings.maxHrm = v; - writeSettings(); - } - }, - }); -}); diff --git a/apps/widhrzone/widget.js b/apps/widhrzone/widget.js index 428c75c7f..a2cedd731 100644 --- a/apps/widhrzone/widget.js +++ b/apps/widhrzone/widget.js @@ -1,20 +1,18 @@ (() => { - const config = Object.assign({ - maxHrm: 200, - }, require("Storage").readJSON("widhrzone.json",1) || {}); + const myprofile = require("Storage").readJSON("myprofile.json",1)||{}; require("FontTeletext5x9Ascii").add(Graphics); const calczone = (bpm) => { - if (bpm <= config.maxHrm*0.5) { + if (bpm <= myprofile.maxHrm*0.5) { return 0; - } else if (bpm <= config.maxHrm*0.60) { + } else if (bpm <= myprofile.maxHrm*0.60) { return 1; - } else if (bpm <= config.maxHrm*0.70) { + } else if (bpm <= myprofile.maxHrm*0.70) { return 2; - } else if (bpm <= config.maxHrm*0.80) { + } else if (bpm <= myprofile.maxHrm*0.80) { return 3; - } else if (bpm <= config.maxHrm*0.90) { + } else if (bpm <= myprofile.maxHrm*0.90) { return 4; } else { // > 0.9 return 5; From d6393a6c435ba959e168c03483ff686f27913f6c Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:36:00 -0500 Subject: [PATCH 60/67] Remove unneeded squares --- apps/binaryclk/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/binaryclk/app.js b/apps/binaryclk/app.js index 0060edf98..8b030ccff 100644 --- a/apps/binaryclk/app.js +++ b/apps/binaryclk/app.js @@ -26,8 +26,11 @@ function draw() { } i++; } + g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq); + g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq); } + g.clear(); draw(); var secondInterval = setInterval(draw, 60000); From 68743c6ba54ab8d2286302ce227c8cc9ca2e1d48 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:14:36 -0500 Subject: [PATCH 61/67] Delete apps/binaryclk/screenshot.png --- apps/binaryclk/screenshot.png | Bin 3081 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/binaryclk/screenshot.png diff --git a/apps/binaryclk/screenshot.png b/apps/binaryclk/screenshot.png deleted file mode 100644 index 1fc824e2b4c858a3b206cfffd13d4f96bbe65a0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3081 zcmds(eK?bCAIGtb=B3@SHF@dQUB-wJSs@H5G2y{X)J9*1Hu^BO< zHbbk18zBkF+gdM@cd4Y6`||wp{PFztJpVq&d0f|d9iQuW9Ov&m&+qsAovC=7qY`8% zL`q6Z>DWjS31nV$oLD%w z_SJf$;O7s!9oOjFNFuizM+`ed1H{21YF1?b{`|!)5BD#bu9(hpFF3^0>0g~c!F*>m z`a&$zUNjgC3Z~PkvSUM6eQ$TZgOl+Y`Y}c0T=%nnmgLz>#Z`|HYaa{dsiIec-|EVb z-bF3@b*R1eX%f0x8=WMc6^*vkUE}?DNX^Y!MbLKb@-~=WIB|cmEFUGwp-e^ui4@N8 z`UKgi99sm}M#$FgBfR_?J3ImH@PmY8^PCkH>`Cv;)Y;&rQ z8ci~tyLRzMBb?i=B=K53m5%_Kw;EQaPFp>#T#2A=y_Z*>u zi?~fEk%{4C#qk#c=1);$ndzzOihG~a`5vTTkv}&lCnr#Hus60(FikFJM7mt3=kKIV za(ZV{3%O4SBLBy{uZ26;Vzi6zuQwa~VcyiAz-&4dj+kBRz>WJWws^upQzK{1{ih7G zdIKA6?~e*Ua3S?XQ<@QXET7xIm*Zk~?x~AxTk3oW*y^5#BwUj=vE$OJaN>ff*jj-S zp^gBJAecS6)8}0__FGU8+_c^=g^tZCvDN*y>G#@}hBRBVd`Y6u&1iGV_w}Wa`@y1b zZYP^mV$B8?l^c~>B5qEw1g7@prpzL^Z78b}b*RdgK80F} z5KSpRUe=8=l0`4I-n#n0URu$2_iulWAE%pure;t08vZyVOXs~X!= z*fi%L4Lksq>Qzzzi1an|q_DX?nIbmEII66f?vd(7`Ez%&E)I5m>opJnIojuC+)~|^ zlX}`wI9P1lc3&70j3bzHP{ybTybucHWn4W~KD zOQ;2%C1lYazazt#48G;oT80V;$yWEuLcq**$thyOa_%!s2>{i}NGJeZxxO`G0wo9e z$b&)_*`EVdZ!Z^v9TeRPP+BTG

lq$!=~-836yE7qbG4!toJSBmI%#ZW&nzt=+lv zylaTxD7WzC0AcS<{mM*)VmjUW#~nqsIMtX~184_<0P#-@LYSV~&8_jx%%cjgQGsHX zpa}1Tj8u&f`xb^3I4SPi0<@%rg9*~X+DSp|72i^xB_Mw89c#yV1UXXT>wq?1q>Spx8&1iesN}2d0 zr84=4s=5P|RUQFybv&)(MQMOMLT#yrzHe_#9smu3eD0UZ_?T>d0l{(y&beoGYUo_n z1B~^zGe>+!t`cYmjC3xDY;-;1E|-R}0Nv*6N}+HlG%%EgFMd@}>BU~jePWLW?DR|C zR?h;0r~~%$DMiiMO80bb;9|~xW`^~?As1xq;lL@UHb?i4V_NgC_kLIn>^;sKn-Cak zV^mp$k{X#4I7U5Q;2{O#JJCa{q`(n*aGiIkKj_2?K@&j=^uSpmGa9D5KdMvi3Yux8 zt}*{;KVO|Yt2}5mOdPt}$+yA>zO5ZAKreOnzl=4F>fGfsa{#%yF;=3Bhe06ogo~;$ zFKfJm+W6gMz@qo&bTYKo^b^=}3!d}rr%7}b*~6S}17OuTf%?GgUYc~vvDhC6v3uD0 zUY~1i6iTOH&Q&`U=TI;}8}KxU^R^lyxs7-tud4_@;@Eq>#s4D)=`HV_(w|0bB?gef z;i#xLL?a-ByTi$Wrr84q<+%ww3wSlO)?FIcWVR|;CqBwj2c|2FQLJfaf8-l;h7TPf zk@l&9&uDt_g>&ZBPfxq1oFi={r+UawM3PC_18uNf-txq>YQvouNO#Yr3k2RKikpdG zw}pkS{(O;_@#9SA!FuJI@6jW1jhh2yz_Do#3bqQRM67~;sJ%k{O$MYU!46PIXbzqp zevJj_Y|3Cxs2Ptm?2eIs8Movt?P8jAJJZ5z6eYCEuI@Ota$|?lkmF#NMRmo3PuKst zU+8UwZ#N}D+Xyqc{0XJ;Wm|2cwRm)%j?z5WoJO}u?E9>xZr~_EyP6lGquwmeP)dvE z;%2a&0>s)N=wo?WBlJqKVRT&!-GWShQIPp5wtn*RX)dBfY+iR5vQFZ>k;DmvM+U zoebzpMq^(R62581cd96kcINgj1W;W1G9s^q^wrCENHgx0<2r&{?Siy@|I63`EO&PW z##`Ri7!ZVfj~;`Ih_6QvPaME;kVe`g7j zot^dsqyUx3qRotbuyZ#;4oKyY8q-1HHRF~TuzCBY*$tR@x>DY)qa9G7V4lAiN}y&q zpq25!WKD2s@W(K{_5m!7F@SxB^}?0PsGI2hm97nxqPewU)_aXKl12%};^XN8_`W24 zcSiY@CDqUoEN!sXu1fAoOpQSRF8*N#E3?{dRPNQ{@|gFVJ=ec1ZI4-RcwaF-A3@52 zYCs4myeC~B+3uw)`cIDv1hwuWZ-Dw#UOC2tNVYxr+u!@sE?y#Vx0gERfU~c~k}m%P DZm^|= From 58407d581914cea9e84f8758473f6bb9d122f947 Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:15:43 -0500 Subject: [PATCH 62/67] update screenshot to match update --- apps/binaryclk/screenshot.png | Bin 0 -> 3318 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/binaryclk/screenshot.png diff --git a/apps/binaryclk/screenshot.png b/apps/binaryclk/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..8b54ed66fbda68f6f48421e1a4c814e537706f27 GIT binary patch literal 3318 zcmeHKYc$l|8lN#3*D;Y>uS=q-PNt9?1|tm9e)9LDloBw;3Z^x7~Ao+$fd zc8BfIW}Z;?h9rCJrVD{~Y`vHFQaf&8`htY@=iXJI5m88w*ouKac~vj%2cfwg@%JER zc&^#VGe|mQbD896el+XK`gUx`aR!^QeyzJ?czAdcxk7xQJ0^DHejbCt7^@vZn!!RP zM_OB}H@9PUYI07R^lprdl{>eo3r^o?GEHAx=l9>GHNf+_CLW8B8wf3+EW6qqxAn!v zaJNa-fu@}e(se=4`ALwRrr>>lHR9<&A?n*6K|{uDn?e~wGzeT_S>cTw#qgw|Yi($5 zd9_Ag2D8FpDzgtXD~A33xJ;x``UIXTg3R(WFC)I_j|uzh=}se4%9Je|G-VN?E_mNq zt?|@V7!-Phdu5$4yvp(94iGP{nR+tB>Vh)|UVYqdGVd6^4y}um!Zq`MdrqQoy`PuO z85vKEsmTkbh~|6QPxPIxz(NyKwOR)~y?0_Y3B%`~@7~|GdSYaR(}(0#g3)=7c1yMm zfh>#b!O@8kuc1+IG7!d6xY6M$Ia0_a{x=OKO5JM$R7L#)|LDh5qdH z=ShR88{BaeTT@e$IoA)WShbjXm5$DJ8)1Y~T4m=pWbx~>*pTx@(sMy08|zU#R@o|O z>YYdCGK!C<>RY?7G?pp5t>Hc%3=s;X+(k)wkHy#W%9hD18jdJ`=#ECi{q=+RRv%&(S7NImxrOS zbBBS0g}Ow0PV_tRyAOo%oY;f|jYieJ^P{WrS1CPM4i6GIyH!yo*+*fWz+$2`TD^;tV2N;G6PuD%I2FZ^TM{A188YmqUrGaX;(iO`6V$>kaJ zj&#K1DbXGQJ<2UFvu76rt_!xEQZ_F^5hOPFQEk|xl}}*kAdWs9&xGmKBL~aEYd*zf z4;3?Ep)Zs@8b`=q9==s=YD?cva$rcC3s)UjF_d(ItluW675tqo7vWF&*luDYdnMXyf~xJ-!0t4|xEe=czOp(b#Fnx=J0oE5L>LqqHOUO9d~)_ng} z;Il4pFsZV4?I#F76HfUBF>Ev(|C*;7cdP+IjyQFa&_DYfy^tk&*TpQOW4af=Lu{gs zZ>U@{6@bz|X?DSk1t8OXXMvg1C$tkEd`-OQ8j#ugm`9sFrW<~5OsX^*NE9qN zk~XoV&_PWeWsxXDWrOum>Y9tZBq*AFuefd!`5k*q|CyvaD_farLr_16ubQM6WDR_ zUk?AzW4OKoMr_I4>sULHAzxG&cgJa(+$@qapStx9hmLlp0>ZpGL`Iphb-Pif@NXvu zrPNFgX>wteFk^Q4*?!q?fHp7V%?|a00SUdU*8-HA9GH5b1qD~~vS58_C0cw*O7-M~ zbjeAFzEDLAA?FRgi*>CDvU|wFBk^o(hi!~uniTKA~dZSowouq&3~nGSo!_l{H1yNXHoBE@`j;4W_OW& zX0B1(ibZoF?b&0Xye@7{Umz#Tc8S>ejBr*9fK>T?Riu+nbw`Pfw=YOdE7DJ;xOz1s zF3X4gEUDU!h6N^E1RMen$K+9a7d#~TbK k4n7H06KL^-xGLBtltLS=eKN+d1U@n#oQ)% Date: Sun, 17 Dec 2023 21:17:04 -0500 Subject: [PATCH 63/67] change to v0.02 remove unneeded squares --- apps/binaryclk/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binaryclk/metadata.json b/apps/binaryclk/metadata.json index fdc56b21a..a81a39b7a 100644 --- a/apps/binaryclk/metadata.json +++ b/apps/binaryclk/metadata.json @@ -1,7 +1,7 @@ { "id": "binaryclk", "name": "Bin Clock", - "version": "0.01", + "version": "0.02", "description": "Clock face to show binary time in 24 hr format", "icon": "app-icon.png", "screenshots": [{"url":"screenshot.png"}], From 0a5610354758d429298f6c430788163a8595ccee Mon Sep 17 00:00:00 2001 From: shansou504 <123512155+shansou504@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:24:43 -0500 Subject: [PATCH 64/67] Create ChangeLog --- apps/binaryclk/ChangeLog | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 apps/binaryclk/ChangeLog diff --git a/apps/binaryclk/ChangeLog b/apps/binaryclk/ChangeLog new file mode 100644 index 000000000..a78cbe479 --- /dev/null +++ b/apps/binaryclk/ChangeLog @@ -0,0 +1,2 @@ +0.01: Added app +0.02: Removed unneeded squares From 63bea40cc458360954331de386776f6ae1026095 Mon Sep 17 00:00:00 2001 From: Nicolas Palix Date: Mon, 18 Dec 2023 09:52:22 +0100 Subject: [PATCH 65/67] Update fr_FR.json --- lang/fr_FR.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lang/fr_FR.json b/lang/fr_FR.json index ac3785e9d..552329dd6 100644 --- a/lang/fr_FR.json +++ b/lang/fr_FR.json @@ -83,16 +83,16 @@ "Connect device\nto add to\nwhitelist": "Connecter le dispositif\nà ajouter à\nliste blanche", "LCD Timeout": "Temporisation de l'écran LCD", "LCD Brightness": "Luminosité de l'écran LCD", - "Wake on BTN2": "Wake sur BTN2", - "Wake on BTN3": "Wake sur BTN3", + "Wake on BTN2": "Réveil sur BTN2", + "Wake on BTN3": "Réveil sur BTN3", "Wake on FaceUp": "Réveillez-vous sur FaceUp", "Wake on Touch": "Réveil au toucher", - "Twist Threshold": "Seuil de torsion", - "Wake on Twist": "Réveil sur Twist", + "Twist Threshold": "Seuil de rotation", + "Wake on Twist": "Réveil sur rotation", "Reset to Defaults": "Réinitialisation des valeurs par défaut", "Utilities": "Utilitaires", - "Flattening battery - this can take hours.\nLong-press button to cancel": "Mise à plat de la batterie - cela peut prendre des heures.\nAppuyez longuement sur le bouton pour annuler", - "Flatten Battery": "Aplatir la batterie", + "Flattening battery - this can take hours.\nLong-press button to cancel": "Décharger la batterie - cela peut prendre des heures.\nAppuyez longuement sur le bouton pour annuler", + "Flatten Battery": "Décharger la batterie", "Storage": "Stockage", "Reset Settings": "Réinitialiser les paramètres", "Log": "Journal de bord", @@ -146,7 +146,7 @@ "Show": "Afficher", "Hide": "Cacher", "Messages": "Messages", - "BACK": "BACK", + "BACK": "RETOUR", "Error in settings": "Erreur dans les paramètres", "Timer": "Minuterie", "On": "Sur", @@ -197,4 +197,4 @@ "ten to *$2": "*$2 heures moins dix", "five to *$2": "*$2 heures moins cinq" } -} \ No newline at end of file +} From 53e36755c851b5c29af018dc46bae9ee856ee865 Mon Sep 17 00:00:00 2001 From: Nicolas Palix Date: Mon, 18 Dec 2023 14:58:48 +0100 Subject: [PATCH 66/67] Update fr_FR.json: Fix 'back' --- lang/fr_FR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/fr_FR.json b/lang/fr_FR.json index 552329dd6..51b3bac8c 100644 --- a/lang/fr_FR.json +++ b/lang/fr_FR.json @@ -155,7 +155,7 @@ "steps": "étapes", "Settings": "Paramètres", "ALARM": "ALARME", - "back": "dos", + "back": "retour", "Yes": "Oui", "Steps": "Étapes", "Year": "Année", From ba082f670c5661894a83c4bb666fcea555d85aec Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 19 Dec 2023 12:30:24 +0000 Subject: [PATCH 67/67] widdst 0.05: Tweaks to ensure Gadgetbridge can't overwrite timezone on 2v19.106 and later --- apps/widdst/ChangeLog | 3 ++- apps/widdst/boot.js | 26 +++++++++++++------------- apps/widdst/metadata.json | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/widdst/ChangeLog b/apps/widdst/ChangeLog index c0b9aafc6..d1ad50fe2 100644 --- a/apps/widdst/ChangeLog +++ b/apps/widdst/ChangeLog @@ -1,4 +1,5 @@ 0.01: Initial version 0.02: Checks for correct firmware; E.setDST(...) moved to boot.js 0.03: Convert Yes/No On/Off in settings to checkboxes -0.04: Give the boot file the highest priority to ensure it runs before sched (fix #2663) \ No newline at end of file +0.04: Give the boot file the highest priority to ensure it runs before sched (fix #2663) +0.05: Tweaks to ensure Gadgetbridge can't overwrite timezone on 2v19.106 and later \ No newline at end of file diff --git a/apps/widdst/boot.js b/apps/widdst/boot.js index b0a844532..0e90eafa4 100644 --- a/apps/widdst/boot.js +++ b/apps/widdst/boot.js @@ -1,15 +1,15 @@ -(() => { - - if (E.setDST) { - var dstSettings = require('Storage').readJSON('widdst.json',1)||{}; - if (dstSettings.has_dst) { - E.setDST(60*dstSettings.dst_size, 60*dstSettings.tz, dstSettings.dst_start.dow_number, dstSettings.dst_start.dow, - dstSettings.dst_start.month, dstSettings.dst_start.day_offset, 60*dstSettings.dst_start.at, - dstSettings.dst_end.dow_number, dstSettings.dst_end.dow, dstSettings.dst_end.month, dstSettings.dst_end.day_offset, - 60*dstSettings.dst_end.at); - } else { - E.setDST(0,0,0,0,0,0,0,0,0,0,0,0); - } +if (E.setDST) { + let dstSettings = require('Storage').readJSON('widdst.json',1)||{}; + if (dstSettings.has_dst) { + E.setDST(60*dstSettings.dst_size, 60*dstSettings.tz, dstSettings.dst_start.dow_number, dstSettings.dst_start.dow, + dstSettings.dst_start.month, dstSettings.dst_start.day_offset, 60*dstSettings.dst_start.at, + dstSettings.dst_end.dow_number, dstSettings.dst_end.dow, dstSettings.dst_end.month, dstSettings.dst_end.day_offset, + 60*dstSettings.dst_end.at); + /* on 2v19.106 and later, E.setTimeZone overwrites E.setDST so we + manually disable E.setTimeZone here to stop Gadgetbridge resetting the timezone */ + E.setTimeZone = function(){}; + } else { + E.setDST(0,0,0,0,0,0,0,0,0,0,0,0); } +} -})() diff --git a/apps/widdst/metadata.json b/apps/widdst/metadata.json index 913bc0658..006e03416 100644 --- a/apps/widdst/metadata.json +++ b/apps/widdst/metadata.json @@ -1,6 +1,6 @@ { "id": "widdst", "name": "Daylight Saving", - "version":"0.04", + "version":"0.05", "description": "Widget to set daylight saving rules. Requires Espruino 2v15 or later - see the instructions below for more information.", "icon": "icon.png", "type": "widget",