From 5df489c587d14fb8220909f1cba710570edb5412 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 14 May 2024 22:26:43 +0100 Subject: [PATCH 01/11] runplus: use `screen` to represent menu/karvonen mode --- apps/runplus/app.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 6a9d39346..892b5bc5c 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -1,5 +1,5 @@ let runInterval; -let karvonenActive = false; +let screen = "main"; // main | karvonen | menu // Run interface wrapped in a function const ExStats = require("exstats"); let B2 = process.env.HWVERSION===2; @@ -9,7 +9,6 @@ let fontHeading = "6x8:2"; let fontValue = B2 ? "6x15:2" : "6x8:3"; let headingCol = "#888"; let fixCount = 0; -let isMenuDisplayed = false; const wu = require("widget_utils"); g.reset().clear(); @@ -64,10 +63,10 @@ function onStartStop() { if (running && exs.state.duration > 10000) { // if more than 10 seconds of duration, ask if we should resume? promise = promise. then(() => { - isMenuDisplayed = true; + screen = "menu"; return E.showPrompt("Resume run?",{title:"Run"}); }).then(r => { - isMenuDisplayed=false; + screen = "main"; layout.setUI(); // grab our input handling again layout.forgetLazyState(); layout.render(); @@ -80,11 +79,11 @@ function onStartStop() { // an overwrite before we start tracking exstats if (settings.record && WIDGETS["recorder"]) { if (running) { - isMenuDisplayed = true; + screen = "menu"; promise = promise. then(() => WIDGETS["recorder"].setRecording(true, { force : shouldResume?"append":undefined })). then(() => { - isMenuDisplayed = false; + screen = "main"; layout.setUI(); // grab our input handling again layout.forgetLazyState(); layout.render(); @@ -168,7 +167,7 @@ Bangle.on("GPS", function(fix) { // run() function used to start updating traditional run ui function run() { require("runplus_karvonen").stop(); - karvonenActive = false; + screen = "main"; wu.show(); Bangle.drawWidgets(); g.reset().clearRect(Bangle.appRect); @@ -179,7 +178,7 @@ function run() { if (!runInterval){ runInterval = setInterval(function() { layout.clock.label = locale.time(new Date(),1); - if (!isMenuDisplayed) layout.render(); + if (screen !== "menu") layout.render(); }, 1000); } } @@ -195,15 +194,15 @@ function karvonen(){ runInterval = undefined; g.reset().clearRect(Bangle.appRect); require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); - karvonenActive = true; + screen = "karvonen"; } // Define the function to go back and forth between the different UI's function swipeHandler(LR,_) { - if (!isMenuDisplayed){ - if (LR==-1 && karvonenActive) - run(); - if (LR==1 && !karvonenActive) + if (screen !== "menu"){ + if (LR < 0 && screen == "karvonen") + run(); // back to main screen + if (LR > 0 && screen !== "karvonen") karvonen(); } } From 8bc3b49e80474870fc436f8ea75aa341269b8106 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 14 May 2024 22:27:17 +0100 Subject: [PATCH 02/11] runplus: move karvonen start logic into `onStartStop()` --- apps/runplus/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 892b5bc5c..b420faf89 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -56,6 +56,8 @@ function setStatus(running) { // Called to start/stop running function onStartStop() { + if (screen === "karvonen") run(); // stop karvonen display + var running = !exs.state.active; var shouldResume = false; var promise = Promise.resolve(); @@ -134,7 +136,7 @@ lc.push({ type:"h", filly:1, c:[ // Now calculate the layout let layout = new Layout( { type:"v", c: lc -},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {run();} onStartStop();}), id:"button"}]}); +},{lazy:true, btns:[{ label:"---", cb: onStartStop, id:"button"}]}); delete lc; setStatus(exs.state.active); layout.render(); From 84730da57a83d29fc39388bdaf625249b794096c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 14 May 2024 22:28:15 +0100 Subject: [PATCH 03/11] runplus: factor out to `setScreen` --- apps/runplus/app.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index b420faf89..edbdcdde4 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -192,11 +192,15 @@ run(); function karvonen(){ // stop updating and drawing the traditional run app UI + setScreen("karvonen"); + require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); +} + +function setScreen(to) { if (runInterval) clearInterval(runInterval); runInterval = undefined; g.reset().clearRect(Bangle.appRect); - require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); - screen = "karvonen"; + screen = to; } // Define the function to go back and forth between the different UI's From 571ed1b344df2f54d7f2d8045625bcd861b34861 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 14 May 2024 22:58:02 +0100 Subject: [PATCH 04/11] runplus: add zoom ability to make stats easier to read while running, e.g. focus on pace --- apps/runplus/app.js | 133 ++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index edbdcdde4..86f1373b6 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -1,5 +1,5 @@ let runInterval; -let screen = "main"; // main | karvonen | menu +let screen = "main"; // main | karvonen | menu | zoom // Run interface wrapped in a function const ExStats = require("exstats"); let B2 = process.env.HWVERSION===2; @@ -7,6 +7,7 @@ let Layout = require("Layout"); let locale = require("locale"); let fontHeading = "6x8:2"; let fontValue = B2 ? "6x15:2" : "6x8:3"; +let zoomFont = "12x20:3"; let headingCol = "#888"; let fixCount = 0; const wu = require("widget_utils"); @@ -51,12 +52,17 @@ function setStatus(running) { layout.button.label = running ? "STOP" : "START"; layout.status.label = running ? "RUN" : "STOP"; layout.status.bgCol = running ? "#0f0" : "#f00"; - layout.render(); + if (screen === "main") layout.render(); } // Called to start/stop running function onStartStop() { - if (screen === "karvonen") run(); // stop karvonen display + switch (screen) { + case "karvonen": + // start/stop on the karvonen screen reverts us to the main screen + setScreen("main"); + break; + } var running = !exs.state.active; var shouldResume = false; @@ -100,7 +106,7 @@ function onStartStop() { promise.then(() => { if (running) { if (shouldResume) - exs.resume() + exs.resume(); else exs.start(); } else { @@ -112,23 +118,68 @@ function onStartStop() { }); } +function zoom(statID) { + if (screen !== "main") return; + + setScreen("zoom"); + + const onTouch = () => { + Bangle.removeListener("touch", onTouch); + Bangle.removeListener("twist", onTwist); + if (runInterval) clearInterval(runInterval); + runInterval = undefined; + setScreen("main"); + }; + Bangle.on("touch", onTouch); // queued after layout's touchHandler + + const onTwist = () => { + Bangle.setLCDPower(1); + }; + Bangle.on("twist", onTwist); + + const draw = () => { + const R = Bangle.appRect; + + g.reset() + .clearRect(R) + .setFontAlign(0, 0); + + layout.render(layout.bottom); + + const stat = exs.stats[statID]; + g + .setFont(zoomFont) + .setColor(headingCol) + .drawString(stat.title.toUpperCase(), R.x+R.w/2, R.y+R.h/3) + .setColor("#fff") + .drawString(stat.getString(), R.x+R.w/2, R.y+R.h*2/3); + }; + layout.lazy = false; // restored when we go back to "main" + + if (runInterval) clearInterval(runInterval); + runInterval = setInterval(draw, 1000); + draw(); +} + let lc = []; // Load stats in pair by pair for (let i=0;ilayout[e.id].label = e.getString()); if (sb) sb.on('changed', e=>layout[e.id].label = e.getString()); } // At the bottom put time/GPS state/etc -lc.push({ type:"h", filly:1, c:[ +lc.push({ type:"h", id:"bottom", filly:1, c:[ {type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" }, {type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }, {type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 } @@ -166,51 +217,51 @@ Bangle.on("GPS", function(fix) { } }); -// run() function used to start updating traditional run ui -function run() { - require("runplus_karvonen").stop(); - screen = "main"; - wu.show(); - Bangle.drawWidgets(); - g.reset().clearRect(Bangle.appRect); - layout.lazy = false; - layout.render(); - layout.lazy = true; - // We always call ourselves once a second to update - if (!runInterval){ - runInterval = setInterval(function() { - layout.clock.label = locale.time(new Date(),1); - if (screen !== "menu") layout.render(); - }, 1000); - } -} -run(); - -/////////////////////////////////////////////// -// Karvonen -/////////////////////////////////////////////// - -function karvonen(){ - // stop updating and drawing the traditional run app UI - setScreen("karvonen"); - require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); -} - function setScreen(to) { + switch (screen) { + case "karvonen": + require("runplus_karvonen").stop(); + wu.show(); + Bangle.drawWidgets(); + break; + } + if (runInterval) clearInterval(runInterval); runInterval = undefined; g.reset().clearRect(Bangle.appRect); + screen = to; + switch (screen) { + case "main": + layout.lazy = false; + layout.render(); + layout.lazy = true; + // We always call ourselves once a second to update + if (!runInterval){ + runInterval = setInterval(function() { + layout.clock.label = locale.time(new Date(),1); + if (screen !== "menu") layout.render(); + }, 1000); + } + break; + + case "karvonen": + require("runplus_karvonen").start(settings.HRM, exs.stats.bpm); + break; + } } // Define the function to go back and forth between the different UI's function swipeHandler(LR,_) { if (screen !== "menu"){ if (LR < 0 && screen == "karvonen") - run(); // back to main screen + setScreen("main"); if (LR > 0 && screen !== "karvonen") - karvonen(); + setScreen("karvonen"); // stop updating and drawing the traditional run app UI } } + +setScreen("main"); + // Listen for swipes with the swipeHandler Bangle.on("swipe", swipeHandler); From f304ce9a682935d98cfecdfc4733146fc76a39b6 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 15 May 2024 08:29:13 +0100 Subject: [PATCH 05/11] runplus: update version & README --- apps/runplus/ChangeLog | 1 + apps/runplus/README.md | 2 ++ apps/runplus/metadata.json | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index 8e9b7385b..4016a2463 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -26,3 +26,4 @@ Write to correct settings file, fixing settings not working. 0.23: Minor code improvements 0.24: Add indicators for lock,gps and pulse to karvonen screen 0.25: Fix step count bug when runs are resumed after a long time +0.26: Add ability to zoom in on a single stat by tapping it diff --git a/apps/runplus/README.md b/apps/runplus/README.md index ddcf34cb7..d930b55e5 100644 --- a/apps/runplus/README.md +++ b/apps/runplus/README.md @@ -4,6 +4,8 @@ Displays distance, time, steps, cadence, pace and heart rate for runners. Based It requires the input of your minimum and maximum heart rate in the settings for the app to work. You can come back back to the initial run screen anytime by swimping left. To use it, start the app and press the middle button so that the red STOP in the bottom right turns to a green `RUN`. +To focus on a single stat, tap on the stat and it will take up the full screen. Tap again to return to the main screen. + ## Display 1st screen * `DIST` - the distance travelled based on the GPS (if you have a GPS lock). diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index 415194f7a..67e44cf2a 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -1,8 +1,8 @@ { "id": "runplus", "name": "Run+", - "version": "0.25", - "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.", + "version": "0.26", + "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screens for heart rate interval training and individual stat focus.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", "supports": ["BANGLEJS2"], From a847a703049bf817fad6ba4242c1e45f6ad04474 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 15 May 2024 18:03:19 +0100 Subject: [PATCH 06/11] runplus: simplify switch -> if --- apps/runplus/app.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 86f1373b6..760863ae7 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -57,11 +57,9 @@ function setStatus(running) { // Called to start/stop running function onStartStop() { - switch (screen) { - case "karvonen": - // start/stop on the karvonen screen reverts us to the main screen - setScreen("main"); - break; + if (screen === "karvonen") { + // start/stop on the karvonen screen reverts us to the main screen + setScreen("main"); } var running = !exs.state.active; @@ -218,12 +216,10 @@ Bangle.on("GPS", function(fix) { }); function setScreen(to) { - switch (screen) { - case "karvonen": - require("runplus_karvonen").stop(); - wu.show(); - Bangle.drawWidgets(); - break; + if (screen === "karvonen") { + require("runplus_karvonen").stop(); + wu.show(); + Bangle.drawWidgets(); } if (runInterval) clearInterval(runInterval); From ae5b8263d5fc71fe4eefdd16df15fbe2827e8e05 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 15 May 2024 18:03:37 +0100 Subject: [PATCH 07/11] runplus: use theme fg for zoom --- apps/runplus/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 760863ae7..3d3190410 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -149,7 +149,7 @@ function zoom(statID) { .setFont(zoomFont) .setColor(headingCol) .drawString(stat.title.toUpperCase(), R.x+R.w/2, R.y+R.h/3) - .setColor("#fff") + .setColor(g.theme.fg) .drawString(stat.getString(), R.x+R.w/2, R.y+R.h*2/3); }; layout.lazy = false; // restored when we go back to "main" From f543b86eae8a7aa005bd8754b3fb5dbdc13418a0 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 15 May 2024 18:07:36 +0100 Subject: [PATCH 08/11] runplus: improve comment --- apps/runplus/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 3d3190410..df13eb76b 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -128,7 +128,7 @@ function zoom(statID) { runInterval = undefined; setScreen("main"); }; - Bangle.on("touch", onTouch); // queued after layout's touchHandler + Bangle.on("touch", onTouch); // queued after layout's touchHandler (otherwise we'd be removed then instantly re-zoomed) const onTwist = () => { Bangle.setLCDPower(1); From 768437219e7d664f5ec732cdafa654736079bcf6 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 16 May 2024 21:13:57 +0100 Subject: [PATCH 09/11] runplus: fix drawing of stat when zoomed --- apps/runplus/app.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index df13eb76b..1c63776b2 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -124,8 +124,7 @@ function zoom(statID) { const onTouch = () => { Bangle.removeListener("touch", onTouch); Bangle.removeListener("twist", onTwist); - if (runInterval) clearInterval(runInterval); - runInterval = undefined; + stat.removeListener("changed", draw); setScreen("main"); }; Bangle.on("touch", onTouch); // queued after layout's touchHandler (otherwise we'd be removed then instantly re-zoomed) @@ -135,7 +134,7 @@ function zoom(statID) { }; Bangle.on("twist", onTwist); - const draw = () => { + const draw = stat => { const R = Bangle.appRect; g.reset() @@ -144,7 +143,6 @@ function zoom(statID) { layout.render(layout.bottom); - const stat = exs.stats[statID]; g .setFont(zoomFont) .setColor(headingCol) @@ -154,9 +152,9 @@ function zoom(statID) { }; layout.lazy = false; // restored when we go back to "main" - if (runInterval) clearInterval(runInterval); - runInterval = setInterval(draw, 1000); - draw(); + const stat = exs.stats[statID]; + stat.on("changed", draw); + draw(stat); } let lc = []; From 9b252e912d8354251bbf951dd9e02fcdcc8b24ea Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 16 May 2024 21:30:12 +0100 Subject: [PATCH 10/11] runplus: handle long stat titles --- apps/runplus/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 1c63776b2..64cadcd5f 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -8,6 +8,7 @@ let locale = require("locale"); let fontHeading = "6x8:2"; let fontValue = B2 ? "6x15:2" : "6x8:3"; let zoomFont = "12x20:3"; +let zoomFontSmall = "12x20:2"; let headingCol = "#888"; let fixCount = 0; const wu = require("widget_utils"); @@ -144,7 +145,7 @@ function zoom(statID) { layout.render(layout.bottom); g - .setFont(zoomFont) + .setFont(stat.title.length > 5 ? zoomFontSmall : zoomFont) .setColor(headingCol) .drawString(stat.title.toUpperCase(), R.x+R.w/2, R.y+R.h/3) .setColor(g.theme.fg) From 5eb8f5625d1deb931fad9e18cff282723e201aec Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 16 May 2024 21:30:34 +0100 Subject: [PATCH 11/11] runplus: don't show cached value of a stat ... as it might be roughtly correct as of when the user paused (HRM) or totally stale (time, step count, etc) --- apps/runplus/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 64cadcd5f..3054ffa0c 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -144,12 +144,14 @@ function zoom(statID) { layout.render(layout.bottom); + const value = exs.state.active ? stat.getString() : "____"; + g .setFont(stat.title.length > 5 ? zoomFontSmall : zoomFont) .setColor(headingCol) .drawString(stat.title.toUpperCase(), R.x+R.w/2, R.y+R.h/3) .setColor(g.theme.fg) - .drawString(stat.getString(), R.x+R.w/2, R.y+R.h*2/3); + .drawString(value, R.x+R.w/2, R.y+R.h*2/3); }; layout.lazy = false; // restored when we go back to "main"