BangleApps/apps/rep/app.ts

282 lines
4.9 KiB
TypeScript
Raw Normal View History

2023-06-18 21:26:11 +00:00
{
2023-05-26 07:00:53 +00:00
const L = require("Layout");
2023-05-17 11:46:44 +00:00
2023-05-18 21:39:11 +00:00
type Rep = {
dur: number,
label: string,
accDur: number,
2023-05-18 21:39:11 +00:00
};
2023-05-25 22:18:18 +00:00
const reps: Rep[] = (require("Storage")
.readJSON("rep.json") as Rep[] | undefined /* TODO */)
.map(((r: Rep, i: number, a: Rep[]): Rep => {
const r2 = r as Rep;
2023-04-29 11:30:23 +00:00
2023-05-25 22:18:18 +00:00
r2.dur = r2.dur * 60 * 1000;
2023-05-18 21:39:11 +00:00
2023-05-25 22:18:18 +00:00
r2.accDur = i > 0
? a[i-1]!.accDur + r.dur
: r.dur;
2023-05-21 14:13:18 +00:00
2023-05-25 22:18:18 +00:00
return r as Rep;
}) as any);
2023-04-29 11:30:23 +00:00
2023-05-18 21:39:11 +00:00
const fontSzMain = 64;
const fontSzRep = 20;
2023-05-21 14:13:18 +00:00
const fontSzRepDesc = 12;
2023-06-18 21:26:11 +00:00
const blue = "#86caf7";
const step = 5 * 1000;
2023-05-18 21:39:11 +00:00
2023-06-18 21:26:11 +00:00
let state: State | undefined;
let drawInterval: IntervalId | undefined;
let lastRepIndex: number | null = null;
2023-05-17 11:46:44 +00:00
2023-05-25 22:19:15 +00:00
const renderDuration = (l: Layout.RenderedHierarchy) => {
let lbl;
g.clearRect(l.x, l.y, l.x+l.w, l.y+l.h);
if(state){
const [i, repElapsed] = state.currentRepPair();
if(i !== null){
let thisDur = reps[i]!.dur;
const remaining = thisDur - repElapsed;
lbl = msToMinSec(remaining);
const fract = repElapsed / thisDur;
g.setColor(blue)
.fillRect(
l.x,
l.y,
l.x + fract * l.w,
l.y + l.h
);
}else{
lbl = msToMinSec(repElapsed);
}
}else{
lbl = "RDY";
}
2023-05-25 22:19:30 +00:00
g.setFont(l.font!); // don't chain - might be undefined (TODO: typings)
2023-05-25 22:19:15 +00:00
g.setColor(l.col || g.theme.fg)
.setFontAlign(0, 0)
.drawString(
lbl,
l.x+(l.w>>1),
l.y+(l.h>>1)
);
};
2023-05-26 07:00:53 +00:00
const layout = new L({
2023-05-17 11:46:44 +00:00
type: "v",
c: [
{
2023-05-25 22:21:04 +00:00
type: "h",
c: [
{
id: "duration",
lazyBuster: 1,
type: "custom",
font: `Vector:${fontSzMain}` as FontNameWithScaleFactor,
fillx: 1,
filly: 1,
render: renderDuration,
},
{
id: "repIdx",
type: "txt",
font: "6x8",
2023-05-26 07:00:53 +00:00
label: "---",
r: Layout.Rotation.Deg90,
2023-05-25 22:21:04 +00:00
},
]
2023-05-17 11:46:44 +00:00
},
2023-05-18 21:39:11 +00:00
{
2023-05-21 14:13:18 +00:00
type: "txt",
font: `Vector:${fontSzRepDesc}`,
label: "Activity / Duration",
2023-05-18 21:39:11 +00:00
},
2023-05-17 11:46:44 +00:00
{
2023-06-18 21:26:11 +00:00
id: "cur_name",
2023-05-21 14:13:18 +00:00
type: "txt",
font: `Vector:${fontSzRep}`,
label: "",
2023-06-18 21:26:11 +00:00
col: blue,
2023-05-21 14:13:18 +00:00
//pad: 4,
fillx: 1,
},
{
type: "txt",
font: `Vector:${fontSzRepDesc}`,
label: "Next / Duration",
},
{
2023-06-18 21:26:11 +00:00
id: "next_name",
2023-05-21 14:13:18 +00:00
type: "txt",
font: `Vector:${fontSzRep}`,
label: "",
//pad: 4,
fillx: 1,
2023-05-17 11:46:44 +00:00
},
{
type: "h",
2023-05-21 14:13:18 +00:00
c: [
{
id: "prev",
type: "btn",
2023-06-18 21:26:11 +00:00
label: "<<",
2023-05-21 14:13:18 +00:00
fillx: 1,
2023-06-18 21:26:11 +00:00
cb: () => {
buzzInteraction();
state?.rewind();
},
2023-05-17 11:46:44 +00:00
},
2023-05-21 14:13:18 +00:00
{
id: "play",
type: "btn",
2023-06-18 21:26:11 +00:00
label: "Play",
2023-05-21 14:13:18 +00:00
fillx: 1,
2023-06-18 21:26:11 +00:00
cb: () => {
buzzInteraction();
if(!state)
state = new State();
state.toggle();
if(state.paused){
clearInterval(drawInterval!);
drawInterval = undefined;
}else{
drawInterval = setInterval(drawRep, 1000);
}
drawRep();
},
2023-05-21 14:13:18 +00:00
},
{
id: "next",
type: "btn",
2023-06-18 21:26:11 +00:00
label: ">>",
2023-05-21 14:13:18 +00:00
fillx: 1,
2023-06-18 21:26:11 +00:00
cb: () => {
buzzInteraction();
state?.forward();
},
2023-05-21 14:13:18 +00:00
}
]
2023-05-17 11:46:44 +00:00
}
]
}, {lazy: true});
class State {
paused: boolean = true;
begin: number = Date.now(); // only valid if !paused
accumulated: number = 0;
2023-05-21 14:13:18 +00:00
toggle() {
if(this.paused){
this.begin = Date.now();
}else{
const diff = Date.now() - this.begin;
this.accumulated += diff;
}
2023-05-21 14:13:18 +00:00
this.paused = !this.paused;
2023-05-21 14:13:18 +00:00
}
getElapsedTotal() {
return (this.paused ? 0 : Date.now() - this.begin) + this.accumulated;
}
2023-05-21 14:13:18 +00:00
getElapsedForRep() {
2023-06-18 21:26:11 +00:00
return this.currentRepPair()[1];
}
currentRepPair(): [number | null, number] {
const elapsed = this.getElapsedTotal();
const i = this.currentRepIndex();
2023-06-18 21:26:11 +00:00
const repElapsed = elapsed - (i! > 0 ? reps[i!-1]!.accDur : 0);
2023-05-21 14:13:18 +00:00
2023-06-18 21:26:11 +00:00
return [i, repElapsed];
}
2023-04-29 11:30:23 +00:00
currentRepIndex() {
const elapsed = this.getElapsedTotal();
let ent;
for(let i = 0; ent = reps[i]; i++)
if(elapsed < ent.accDur)
return i;
return null;
2023-05-18 21:39:11 +00:00
}
2023-06-18 21:26:11 +00:00
forward() {
this.accumulated += step;
}
rewind() {
this.accumulated -= step;
}
}
2023-05-18 21:39:11 +00:00
const repToLabel = (i: number, id: string) => {
const rep = reps[i];
2023-06-18 21:26:11 +00:00
if(rep)
layout[`${id}_name`]!.label = `${rep.label} / ${msToMinSec(rep.dur)}`;
2023-06-18 21:26:11 +00:00
else
2023-05-18 21:39:11 +00:00
emptyLabel(id);
};
const emptyLabel = (id: string) => {
2023-05-21 14:13:18 +00:00
layout[`${id}_name`]!.label = "<none> / 0m";
2023-05-18 21:39:11 +00:00
};
2023-05-21 14:13:18 +00:00
const pad2 = (s: number) => ('0' + s.toFixed(0)).slice(-2);
2023-05-18 21:39:11 +00:00
const msToMinSec = (ms: number) => {
2023-05-21 14:13:18 +00:00
const sec = Math.round(ms / 1000);
2023-05-18 21:39:11 +00:00
const min = Math.round(sec / 60);
2023-05-21 14:13:18 +00:00
return min.toFixed(0) + ":" + pad2(sec % 60);
};
2023-05-17 11:46:44 +00:00
2023-05-21 14:13:18 +00:00
const drawRep = () => {
(layout["duration"] as any).lazyBuster ^= 1;
if(state){
2023-06-18 21:26:11 +00:00
const i = state.currentRepIndex();
if(i !== lastRepIndex){
buzzNewRep();
lastRepIndex = i;
}
2023-05-21 14:13:18 +00:00
layout["play"]!.label = state.paused ? "Play" : "Pause";
2023-05-21 14:13:18 +00:00
if(i !== null){
2023-06-18 21:26:11 +00:00
layout["repIdx"]!.label = `Rep ${i+1}`;
2023-05-21 14:13:18 +00:00
repToLabel(i, "cur");
repToLabel(i+1, "next");
}else{
2023-06-18 21:26:11 +00:00
layout["repIdx"]!.label = "Done";
2023-05-21 14:13:18 +00:00
emptyLabel("cur");
emptyLabel("next");
}
2023-05-17 11:46:44 +00:00
}
layout.render();
2023-04-29 11:30:23 +00:00
};
2023-06-18 21:26:11 +00:00
const buzzInteraction = () => Bangle.buzz(20);
const buzzNewRep = () => Bangle.buzz(500);
Bangle.loadWidgets();
2023-05-17 11:46:44 +00:00
g.clear();
drawRep();
2023-06-18 21:26:11 +00:00
Bangle.drawWidgets();
}