forked from FOSS/BangleApps
rep: UI & colour tweaks, and README
parent
057ee7df03
commit
444a909c5b
|
@ -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
|
138
apps/rep/app.ts
138
apps/rep/app.ts
|
@ -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
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue