1
0
Fork 0

rep: UI & colour tweaks, and README

master
Rob Pilling 2023-06-18 22:26:11 +01:00
parent 057ee7df03
commit 444a909c5b
3 changed files with 85 additions and 69 deletions

12
apps/rep/README.md Normal file
View File

@ -0,0 +1,12 @@
# Description
A running/activity repetition app. Program your reps using the web interface, and then run through them with the app. The time left for the current rep is shown, along with the rep's description and total duration, and the next rep's description and duration.
You can rewind, fast-forward and play/pause with the control buttons.
# Todo
- [ ] Loading of reps, web interface
- [ ] Fastload: scoping, unregister layout handlers etc
- [ ] Swipe handlers as well as "<<" / ">>" buttons

View File

@ -1,8 +1,4 @@
// TODO: buzz on stage change, better setTimeout() {
// TODO: fastload: scoping, unregister layout handlers etc
// TODO: spaces vs tabs
// TODO: const colfg=g.theme.fg, colbg=g.theme.bg;
const Layout = require("Layout"); const Layout = require("Layout");
type Rep = { type Rep = {
@ -65,20 +61,20 @@ const reps: Rep[] = [
const fontSzMain = 64; const fontSzMain = 64;
const fontSzRep = 20; const fontSzRep = 20;
const fontSzRepDesc = 12; const fontSzRepDesc = 12;
const blue = "#86caf7";
const step = 5 * 1000;
// FIXME: `Layout_` name, e.g. Layout_.Align.Right let state: State | undefined;
let drawInterval: IntervalId | undefined;
let lastRepIndex: number | null = null;
// top: show current rep: name & duration
// middle: show time left on current rep
// bottom: show next rep
// ctrl: play/pause button. back & forward?
const layout = new Layout({ const layout = new Layout({
type: "v", type: "v",
c: [ c: [
{ {
id: "repIdx", id: "repIdx",
type: "txt", type: "txt",
label: "Rep 1", // TODO: update in render label: "Begin",
font: `Vector:${fontSzRepDesc}`, font: `Vector:${fontSzRepDesc}`,
}, },
{ {
@ -88,15 +84,13 @@ const layout = new Layout({
font: `Vector:${fontSzMain}` as FontNameWithScaleFactor, font: `Vector:${fontSzMain}` as FontNameWithScaleFactor,
fillx: 1, fillx: 1,
filly: 1, filly: 1,
render: (l: Layout_.RenderedHierarchy) => { render: (l: Layout.RenderedHierarchy) => {
let lbl; let lbl;
g.clearRect(l.x, l.y, l.x+l.w, l.y+l.h); g.clearRect(l.x, l.y, l.x+l.w, l.y+l.h);
if(state){ if(state){
// TODO: inefficient const [i, repElapsed] = state.currentRepPair();
const i = state.currentRepIndex();
const repElapsed = state.getElapsedForRep();
if(i !== null){ if(i !== null){
let thisDur = reps[i]!.dur; let thisDur = reps[i]!.dur;
@ -105,7 +99,7 @@ const layout = new Layout({
lbl = msToMinSec(remaining); lbl = msToMinSec(remaining);
const fract = repElapsed / thisDur; const fract = repElapsed / thisDur;
g.setColor("#86caf7") g.setColor(blue)
.fillRect( .fillRect(
l.x, l.x,
l.y, l.y,
@ -135,10 +129,11 @@ const layout = new Layout({
label: "Activity / Duration", label: "Activity / Duration",
}, },
{ {
id: `cur_name`, id: "cur_name",
type: "txt", type: "txt",
font: `Vector:${fontSzRep}`, font: `Vector:${fontSzRep}`,
label: "", label: "",
col: blue,
//pad: 4, //pad: 4,
fillx: 1, fillx: 1,
}, },
@ -148,7 +143,7 @@ const layout = new Layout({
label: "Next / Duration", label: "Next / Duration",
}, },
{ {
id: `next_name`, id: "next_name",
type: "txt", type: "txt",
font: `Vector:${fontSzRep}`, font: `Vector:${fontSzRep}`,
label: "", label: "",
@ -161,42 +156,50 @@ const layout = new Layout({
{ {
id: "prev", id: "prev",
type: "btn", type: "btn",
label: "<", label: "<<",
fillx: 1, fillx: 1,
cb: () => onPrev(), cb: () => {
buzzInteraction();
state?.rewind();
},
}, },
{ {
id: "play", id: "play",
type: "btn", type: "btn",
label: "Play", // TODO: change label: "Play",
fillx: 1, fillx: 1,
cb: () => onToggle(), cb: () => {
buzzInteraction();
if(!state)
state = new State();
state.toggle();
if(state.paused){
clearInterval(drawInterval!);
drawInterval = undefined;
}else{
drawInterval = setInterval(drawRep, 1000);
}
drawRep();
},
}, },
{ {
id: "next", id: "next",
type: "btn", type: "btn",
label: ">", label: ">>",
fillx: 1, fillx: 1,
cb: () => onNext(), cb: () => {
buzzInteraction();
state?.forward();
},
} }
] ]
} }
] ]
}, {lazy: true}); }, {lazy: true});
const onToggle = () => {
if(!state)
state = new State();
state.toggle();
drawRep();
};
const onPrev = () => {
};
const onNext = () => {
};
class State { class State {
paused: boolean = true; paused: boolean = true;
begin: number = Date.now(); // only valid if !paused begin: number = Date.now(); // only valid if !paused
@ -205,14 +208,9 @@ class State {
toggle() { toggle() {
if(this.paused){ if(this.paused){
this.begin = Date.now(); this.begin = Date.now();
// TODO: move draw out
drawInterval = setInterval(drawRep, 1000);
}else{ }else{
const diff = Date.now() - this.begin; const diff = Date.now() - this.begin;
this.accumulated += diff; this.accumulated += diff;
clearInterval(drawInterval!);
} }
this.paused = !this.paused; this.paused = !this.paused;
@ -223,10 +221,15 @@ class State {
} }
getElapsedForRep() { getElapsedForRep() {
return this.currentRepPair()[1];
}
currentRepPair(): [number | null, number] {
const elapsed = this.getElapsedTotal(); const elapsed = this.getElapsedTotal();
const i = this.currentRepIndex(); const i = this.currentRepIndex();
const repElapsed = elapsed - (i! > 0 ? reps[i!-1]!.accDur : 0);
return elapsed - (i! > 0 ? reps[i!-1]!.accDur : 0); return [i, repElapsed];
} }
currentRepIndex() { currentRepIndex() {
@ -237,20 +240,22 @@ class State {
return i; return i;
return null; return null;
} }
}
let state: State | undefined; forward() {
let drawInterval: IntervalId | undefined; this.accumulated += step;
}
rewind() {
this.accumulated -= step;
}
}
const repToLabel = (i: number, id: string) => { const repToLabel = (i: number, id: string) => {
const rep = reps[i]; const rep = reps[i];
if(rep){ if(rep)
layout[`${id}_name`]!.label = `${rep.label} / ${msToMinSec(rep.dur)}`; layout[`${id}_name`]!.label = `${rep.label} / ${msToMinSec(rep.dur)}`;
// FIXME: display time, i.e. hh:mm else
//layout[`${id}_dur`]!.label = ``;
}else{
emptyLabel(id); emptyLabel(id);
}
}; };
const emptyLabel = (id: string) => { const emptyLabel = (id: string) => {
@ -269,37 +274,36 @@ const drawRep = () => {
(layout["duration"] as any).lazyBuster ^= 1; (layout["duration"] as any).lazyBuster ^= 1;
if(state){ if(state){
// TODO: layout.clear(layout.next_name); layout.render(layout.next_name) const i = state.currentRepIndex();
if(i !== lastRepIndex){
buzzNewRep();
lastRepIndex = i;
}
layout["play"]!.label = state.paused ? "Play" : "Pause"; layout["play"]!.label = state.paused ? "Play" : "Pause";
const i = state.currentRepIndex();
if(i !== null){ if(i !== null){
layout["repIdx"]!.label = `Rep ${i+1}`;
repToLabel(i, "cur"); repToLabel(i, "cur");
repToLabel(i+1, "next"); repToLabel(i+1, "next");
}else{ }else{
layout["repIdx"]!.label = "Done";
emptyLabel("cur"); emptyLabel("cur");
emptyLabel("next"); emptyLabel("next");
} }
} }
layout.render(); layout.render();
/*
// TODO: figure out next rep change time? or every 5s, then countdown from 10-->0
// TODO: and then use that to Bangle.buzz() on next rep
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
drawRep();
}, 1000 - (Date.now() % 1000));
*/
}; };
const buzzInteraction = () => Bangle.buzz(20);
const buzzNewRep = () => Bangle.buzz(500);
Bangle.loadWidgets();
g.clear(); g.clear();
drawRep(); drawRep();
// TODO: swipe handlers Bangle.drawWidgets();
//Bangle.setUI }
// TODO: widgets

View File

@ -3,11 +3,11 @@
"name": "Rep", "name": "Rep",
"version":"0.01", "version":"0.01",
"description": "Steps through reps, to aid in run schedules", "description": "Steps through reps, to aid in run schedules",
"icon": "app.png", // TODO "icon": "app.png",
"tags": "run,running,fitness,outdoors", "tags": "run,running,fitness,outdoors",
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"interface": "interface.html", "interface": "interface.html",
"storage": [ "storage": [
{"name":"rep.app.js","url":"app.js"}, {"name":"rep.app.js","url":"app.js"},
{"name":"rep.img","url":"app-icon.js","evaluate":true} {"name":"rep.img","url":"app-icon.js","evaluate":true}