mirror of https://github.com/espruino/BangleApps
Merge pull request #3419 from bobrippling/feat/run-focus
runplus: add ability to focus an individual statpull/3430/head
commit
e39e4b6723
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
let runInterval;
|
||||
let karvonenActive = false;
|
||||
let screen = "main"; // main | karvonen | menu | zoom
|
||||
// Run interface wrapped in a function
|
||||
const ExStats = require("exstats");
|
||||
let B2 = process.env.HWVERSION===2;
|
||||
|
@ -7,9 +7,10 @@ let Layout = require("Layout");
|
|||
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;
|
||||
let isMenuDisplayed = false;
|
||||
const wu = require("widget_utils");
|
||||
|
||||
g.reset().clear();
|
||||
|
@ -52,11 +53,16 @@ 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") {
|
||||
// start/stop on the karvonen screen reverts us to the main screen
|
||||
setScreen("main");
|
||||
}
|
||||
|
||||
var running = !exs.state.active;
|
||||
var shouldResume = false;
|
||||
var promise = Promise.resolve();
|
||||
|
@ -64,10 +70,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 +86,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();
|
||||
|
@ -99,7 +105,7 @@ function onStartStop() {
|
|||
promise.then(() => {
|
||||
if (running) {
|
||||
if (shouldResume)
|
||||
exs.resume()
|
||||
exs.resume();
|
||||
else
|
||||
exs.start();
|
||||
} else {
|
||||
|
@ -111,23 +117,68 @@ function onStartStop() {
|
|||
});
|
||||
}
|
||||
|
||||
function zoom(statID) {
|
||||
if (screen !== "main") return;
|
||||
|
||||
setScreen("zoom");
|
||||
|
||||
const onTouch = () => {
|
||||
Bangle.removeListener("touch", onTouch);
|
||||
Bangle.removeListener("twist", onTwist);
|
||||
stat.removeListener("changed", draw);
|
||||
setScreen("main");
|
||||
};
|
||||
Bangle.on("touch", onTouch); // queued after layout's touchHandler (otherwise we'd be removed then instantly re-zoomed)
|
||||
|
||||
const onTwist = () => {
|
||||
Bangle.setLCDPower(1);
|
||||
};
|
||||
Bangle.on("twist", onTwist);
|
||||
|
||||
const draw = stat => {
|
||||
const R = Bangle.appRect;
|
||||
|
||||
g.reset()
|
||||
.clearRect(R)
|
||||
.setFontAlign(0, 0);
|
||||
|
||||
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(value, R.x+R.w/2, R.y+R.h*2/3);
|
||||
};
|
||||
layout.lazy = false; // restored when we go back to "main"
|
||||
|
||||
const stat = exs.stats[statID];
|
||||
stat.on("changed", draw);
|
||||
draw(stat);
|
||||
}
|
||||
|
||||
let lc = [];
|
||||
// Load stats in pair by pair
|
||||
for (let i=0;i<statIDs.length;i+=2) {
|
||||
let sa = exs.stats[statIDs[i+0]];
|
||||
let sb = exs.stats[statIDs[i+1]];
|
||||
let cba = zoom.bind(null, statIDs[i]);
|
||||
let cbb = zoom.bind(null, statIDs[i+1]);
|
||||
lc.push({ type:"h", filly:1, c:[
|
||||
sa?{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol }:{},
|
||||
sb?{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }:{}
|
||||
sa?{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol, cb: cba }:{},
|
||||
sb?{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol, cb: cbb }:{}
|
||||
]}, { type:"h", filly:1, c:[
|
||||
sa?{type:"txt", font:fontValue, label:sa.getString(), id:sa.id, fillx:1 }:{},
|
||||
sb?{type:"txt", font:fontValue, label:sb.getString(), id:sb.id, fillx:1 }:{}
|
||||
sa?{type:"txt", font:fontValue, label:sa.getString(), id:sa.id, fillx:1, cb: cba }:{},
|
||||
sb?{type:"txt", font:fontValue, label:sb.getString(), id:sb.id, fillx:1, cb: cbb }:{}
|
||||
]});
|
||||
if (sa) sa.on('changed', e=>layout[e.id].label = e.getString());
|
||||
if (sb) sb.on('changed', e=>layout[e.id].label = e.getString());
|
||||
}
|
||||
// At the bottom put time/GPS state/etc
|
||||
lc.push({ type:"h", filly:1, c:[
|
||||
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 }
|
||||
|
@ -135,7 +186,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();
|
||||
|
@ -165,47 +216,49 @@ Bangle.on("GPS", function(fix) {
|
|||
}
|
||||
});
|
||||
|
||||
// run() function used to start updating traditional run ui
|
||||
function run() {
|
||||
require("runplus_karvonen").stop();
|
||||
karvonenActive = false;
|
||||
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 (!isMenuDisplayed) layout.render();
|
||||
}, 1000);
|
||||
function setScreen(to) {
|
||||
if (screen === "karvonen") {
|
||||
require("runplus_karvonen").stop();
|
||||
wu.show();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
run();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Karvonen
|
||||
///////////////////////////////////////////////
|
||||
|
||||
function karvonen(){
|
||||
// stop updating and drawing the traditional run app UI
|
||||
if (runInterval) clearInterval(runInterval);
|
||||
runInterval = undefined;
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
require("runplus_karvonen").start(settings.HRM, exs.stats.bpm);
|
||||
karvonenActive = true;
|
||||
|
||||
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 (!isMenuDisplayed){
|
||||
if (LR==-1 && karvonenActive)
|
||||
run();
|
||||
if (LR==1 && !karvonenActive)
|
||||
karvonen();
|
||||
if (screen !== "menu"){
|
||||
if (LR < 0 && screen == "karvonen")
|
||||
setScreen("main");
|
||||
if (LR > 0 && screen !== "karvonen")
|
||||
setScreen("karvonen"); // stop updating and drawing the traditional run app UI
|
||||
}
|
||||
}
|
||||
|
||||
setScreen("main");
|
||||
|
||||
// Listen for swipes with the swipeHandler
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
|
|
|
@ -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"],
|
||||
|
|
Loading…
Reference in New Issue