Add option to pause/resume workout (Bangle.js 1 only)

master
Erovia 2024-02-22 15:39:13 +00:00
parent ba03c98c2c
commit 42389fd9b4
5 changed files with 87 additions and 52 deletions

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Add rep info to time screen 0.02: Add rep info to time screen
0.03: Add option to pause/resume workout (Bangle.js 1 only)

View File

@ -50,7 +50,7 @@ For example `w6d1(r:6|w:3|x2)` means:
--- ---
### Show the time: ### Show extra info:
If you ever need to peek at the time, just press the middle (or only) physical button on the watch: If you ever need to peek at the time, just press the middle (or only) physical button on the watch:
![](c25k-scrn7.png) ![](c25k-scrn7.png)
@ -59,6 +59,15 @@ This view also shows `current rep / total reps` at the top.
--- ---
### Pause/resume workout:
**This is currently only available on Bangle.js 1.**
Press the top button to pause or to resume the active programme:
![](c25k-scrn8.png)
---
## Disclaimer ## Disclaimer
This app was hacked together in a day with no JS knowledge. This app was hacked together in a day with no JS knowledge.
@ -69,4 +78,4 @@ It *should* work fine on the Bangle.js 2, but I couldn't test it on real hardwar
--- ---
Made with <3 by [Erovia](https://github.com/Erovia) Made with <3 by [Erovia](https://github.com/Erovia/BangleApps/tree/c25k)

View File

@ -3,27 +3,26 @@ var day = 1;
var time; var time;
var loop; // To store how many times we will have to do a countdown var loop; // To store how many times we will have to do a countdown
var rep = 0; // The current rep counter var rep; // The current rep counter
var counter = 0; // The seconds counter var counter; // The seconds counter
var currentMode; // Either "run" or "walk" var currentMode; // Either "run" or "walk"
var mainInterval; // Ticks every second, checking if a new countdown is needed var mainInterval; // Ticks every second, checking if a new countdown is needed
var activityInterval; // Ticks every second, doing the countdown var activityInterval; // Ticks every second, doing the countdown
var buttonWatch; // Watch for button presses var extraInfoWatch; // Watch for button presses to show additional info
var paused = false; // Track pause state
var pauseOrResumeWatch; // Watch for button presses to pause/resume countdown
var defaultFontSize = (process.env.HWVERSION == 2) ? 7 : 8; // Default font size, Banglejs 2 has smaller
var activityBgColour; // Background colour of current activity
function outOfTime() { function outOfTime() {
// Buzz 3 times on state transitions buzz();
Bangle.buzz(500)
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
.then(() => Bangle.buzz(500))
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
.then(() => Bangle.buzz(500));
// Once we're done // Once we're done
if (loop == 0) { if (loop == 0) {
clearWatch(buttonWatch); // Don't watch for button presses anymore clearWatch(extraInfoWatch); // Don't watch for button presses anymore
if (pauseOrResumeWatch) clearWatch(pauseOrResumeWatch); // Don't watch for button presses anymore
g.setBgColor("#75C0E0"); // Blue background for the "Done" text g.setBgColor("#75C0E0"); // Blue background for the "Done" text
g.clear(); drawText("Done", defaultFontSize); // Write "Done" to screen
g.drawString("Done", g.getWidth()/2, g.getHeight()/2); // Write "Done" to screen
g.reset(); g.reset();
setTimeout(E.showMenu, 5000, mainmenu); // Show the main menu again after 5secs setTimeout(E.showMenu, 5000, mainmenu); // Show the main menu again after 5secs
clearInterval(mainInterval); // Stop the main interval from starting a new activity clearInterval(mainInterval); // Stop the main interval from starting a new activity
@ -31,54 +30,69 @@ function outOfTime() {
} }
} }
function countDown() { // Buzz 3 times on state transitions
var text = ""; function buzz() {
var textsize = (process.env.HWVERSION == 2) ? 7 : 8; // Default font size, Banglejs 2 has smaller screen Bangle.buzz(500)
if (time) { .then(() => new Promise(resolve => setTimeout(resolve, 200)))
var w = week -1; .then(() => Bangle.buzz(500))
var d = day - 1; .then(() => new Promise(resolve => setTimeout(resolve, 200)))
var total = ("walk" in PLAN[w][d]) ? PLAN[w][d].repetition : 1; .then(() => Bangle.buzz(500));
text += rep + "/" + total + "\n"; // Show the current/total rep count when time is shown }
textsize -= (process.env.HWVERSION == 2) ? 2 : 1; // Use smaller font size to fit everything nicely on the screen
} function drawText(text, size){
text += (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text
if (time) text += "\n" + time;
g.clear(); g.clear();
g.setFontAlign(0, 0); // center font g.setFontAlign(0, 0); // center font
g.setFont("6x8", textsize); g.setFont("6x8", size);
g.drawString(text, g.getWidth() / 2, g.getHeight() / 2); // draw the current mode and seconds g.drawString(text, g.getWidth() / 2, g.getHeight() / 2);
Bangle.setLCDPower(1); // keep the watch LCD lit up }
counter--; // Reduce the seconds function countDown() {
if (!paused) {
var text = "";
var size = defaultFontSize;
if (time) {
var w = week -1;
var d = day - 1;
var total = ("walk" in PLAN[w][d]) ? PLAN[w][d].repetition : 1;
text += rep + "/" + total + "\n"; // Show the current/total rep count when time is shown
size -= (process.env.HWVERSION == 2) ? 2 : 1; // Use smaller font size to fit everything nicely on the screen
}
text += (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text
if (time) text += "\n" + time;
drawText(text, size); // draw the current mode and seconds
Bangle.setLCDPower(1); // keep the watch LCD lit up
// If the current activity is done counter--; // Reduce the seconds
if (counter < 0) {
clearInterval(activityInterval); // If the current activity is done
activityInterval = undefined; if (counter < 0) {
outOfTime(); clearInterval(activityInterval);
return; activityInterval = undefined;
outOfTime();
return;
}
} }
} }
function startTimer(w, d) { function startTimer(w, d) {
// If something is already running, do nothing // If something is already running, do nothing
if (activityInterval) { if (activityInterval) return;
return;
}
// Switches between the two modes // Switches between the two modes
if (!currentMode || currentMode === "walk") { if (!currentMode || currentMode === "walk") {
currentMode = "run"; currentMode = "run";
rep++; // Increase the rep counter every time a "run" activity starts rep++; // Increase the rep counter every time a "run" activity starts
counter = PLAN[w][d].run * 60; counter = PLAN[w][d].run * 60;
g.setBgColor("#ff5733"); activityBgColour = "#ff5733"; // Red background for running
} }
else { else {
currentMode = "walk"; currentMode = "walk";
counter = PLAN[w][d].walk * 60; counter = PLAN[w][d].walk * 60;
g.setBgColor("#4da80a"); activityBgColour = "#4da80a"; // Green background for walking
} }
g.setBgColor(activityBgColour);
countDown(); countDown();
if (!activityInterval) { if (!activityInterval) {
loop--; // Reduce the number of iterations loop--; // Reduce the number of iterations
@ -103,9 +117,7 @@ function populatePlan() {
// Ever line will have the following format: // Ever line will have the following format:
// w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps}) // w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})
var name = "w" + (i + 1) + "d" + (j + 1); var name = "w" + (i + 1) + "d" + (j + 1);
if (process.env.HWVERSION == 2) { if (process.env.HWVERSION == 2) name += "\n"; // Print in 2 lines to accomodate the Bangle.js 2 screen
name += "\n"; // Print in 2 lines to accomodate the Bangle.js 2 screen
}
name += "(r:" + PLAN[i][j].run; name += "(r:" + PLAN[i][j].run;
if ("walk" in PLAN[i][j]) name += "|w:" + PLAN[i][j].walk; if ("walk" in PLAN[i][j]) name += "|w:" + PLAN[i][j].walk;
if ("repetition" in PLAN[i][j]) name += "|x" + PLAN[i][j].repetition; if ("repetition" in PLAN[i][j]) name += "|x" + PLAN[i][j].repetition;
@ -129,15 +141,27 @@ function startActivity() {
var w = week - 1; var w = week - 1;
var d = day - 1; var d = day - 1;
if ("walk" in PLAN[w][d]) { loop = ("walk" in PLAN[w][d]) ? PLAN[w][d].repetition * 2 : 1;
loop = PLAN[w][d].repetition * 2; rep = 0;
E.showMenu(); // Hide the main menu
extraInfoWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press
if (process.env.HWVERSION == 1) pauseOrResumeWatch = setWatch(pauseOrResumeActivity, BTN1, {repeat: true}); // Pause or resume on button press (Bangle.js 1 only)
buzz();
mainInterval = setInterval(function() {startTimer(w, d);}, 1000); // Check every second if we need to do something
}
// Pause or resume current activity
function pauseOrResumeActivity() {
paused = !paused;
buzz();
if (paused) {
g.setBgColor("#fdd835"); // Yellow background for pause screen
drawText("Paused", (process.env.HWVERSION == 2) ? defaultFontSize - 3 : defaultFontSize - 2); // Although the font size is configured here, this feature does not work on Bangle.js 2 as the only physical button is tied to the extra info screen already
} }
else { else {
loop = 1; g.setBgColor(activityBgColour);
} }
E.showMenu(); // Hide the main menu
buttonWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press
mainInterval = setInterval(function() {startTimer(w, d);}, 1000); // Check every second if we need to do something
} }
const PLAN = [ const PLAN = [

BIN
apps/c25k/c25k-scrn8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -2,7 +2,7 @@
"id": "c25k", "id": "c25k",
"name": "C25K", "name": "C25K",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.03",
"description": "Unofficial app for the Couch to 5k training plan", "description": "Unofficial app for the Couch to 5k training plan",
"readme": "README.md", "readme": "README.md",
"type": "app", "type": "app",
@ -23,6 +23,7 @@
{"url": "c25k-scrn4.png"}, {"url": "c25k-scrn4.png"},
{"url": "c25k-scrn5.png"}, {"url": "c25k-scrn5.png"},
{"url": "c25k-scrn6.png"}, {"url": "c25k-scrn6.png"},
{"url": "c25k-scrn7.png"} {"url": "c25k-scrn7.png"},
{"url": "c25k-scrn8.png"}
] ]
} }