From 444a909c5b608aad9256b1adeb56eef923c26fc3 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 18 Jun 2023 22:26:11 +0100 Subject: [PATCH] rep: UI & colour tweaks, and README --- apps/rep/README.md | 12 ++++ apps/rep/app.ts | 138 +++++++++++++++++++++-------------------- apps/rep/metadata.json | 4 +- 3 files changed, 85 insertions(+), 69 deletions(-) create mode 100644 apps/rep/README.md diff --git a/apps/rep/README.md b/apps/rep/README.md new file mode 100644 index 000000000..9da08942c --- /dev/null +++ b/apps/rep/README.md @@ -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 diff --git a/apps/rep/app.ts b/apps/rep/app.ts index 149926922..1a516fdb9 100644 --- a/apps/rep/app.ts +++ b/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"); type Rep = { @@ -65,20 +61,20 @@ const reps: Rep[] = [ const fontSzMain = 64; const fontSzRep = 20; 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({ type: "v", c: [ { id: "repIdx", type: "txt", - label: "Rep 1", // TODO: update in render + label: "Begin", font: `Vector:${fontSzRepDesc}`, }, { @@ -88,15 +84,13 @@ const layout = new Layout({ font: `Vector:${fontSzMain}` as FontNameWithScaleFactor, fillx: 1, filly: 1, - render: (l: Layout_.RenderedHierarchy) => { + render: (l: Layout.RenderedHierarchy) => { let lbl; g.clearRect(l.x, l.y, l.x+l.w, l.y+l.h); if(state){ - // TODO: inefficient - const i = state.currentRepIndex(); - const repElapsed = state.getElapsedForRep(); + const [i, repElapsed] = state.currentRepPair(); if(i !== null){ let thisDur = reps[i]!.dur; @@ -105,7 +99,7 @@ const layout = new Layout({ lbl = msToMinSec(remaining); const fract = repElapsed / thisDur; - g.setColor("#86caf7") + g.setColor(blue) .fillRect( l.x, l.y, @@ -135,10 +129,11 @@ const layout = new Layout({ label: "Activity / Duration", }, { - id: `cur_name`, + id: "cur_name", type: "txt", font: `Vector:${fontSzRep}`, label: "", + col: blue, //pad: 4, fillx: 1, }, @@ -148,7 +143,7 @@ const layout = new Layout({ label: "Next / Duration", }, { - id: `next_name`, + id: "next_name", type: "txt", font: `Vector:${fontSzRep}`, label: "", @@ -161,42 +156,50 @@ const layout = new Layout({ { id: "prev", type: "btn", - label: "<", + label: "<<", fillx: 1, - cb: () => onPrev(), + cb: () => { + buzzInteraction(); + state?.rewind(); + }, }, { id: "play", type: "btn", - label: "Play", // TODO: change + label: "Play", 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", type: "btn", - label: ">", + label: ">>", fillx: 1, - cb: () => onNext(), + cb: () => { + buzzInteraction(); + state?.forward(); + }, } ] } ] }, {lazy: true}); -const onToggle = () => { - if(!state) - state = new State(); - state.toggle(); - drawRep(); -}; - -const onPrev = () => { -}; - -const onNext = () => { -}; - class State { paused: boolean = true; begin: number = Date.now(); // only valid if !paused @@ -205,14 +208,9 @@ class State { toggle() { if(this.paused){ this.begin = Date.now(); - - // TODO: move draw out - drawInterval = setInterval(drawRep, 1000); }else{ const diff = Date.now() - this.begin; this.accumulated += diff; - - clearInterval(drawInterval!); } this.paused = !this.paused; @@ -223,10 +221,15 @@ class State { } getElapsedForRep() { + return this.currentRepPair()[1]; + } + + currentRepPair(): [number | null, number] { const elapsed = this.getElapsedTotal(); 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() { @@ -237,20 +240,22 @@ class State { return i; return null; } -} -let state: State | undefined; -let drawInterval: IntervalId | undefined; + forward() { + this.accumulated += step; + } + + rewind() { + this.accumulated -= step; + } +} const repToLabel = (i: number, id: string) => { const rep = reps[i]; - if(rep){ + if(rep) layout[`${id}_name`]!.label = `${rep.label} / ${msToMinSec(rep.dur)}`; - // FIXME: display time, i.e. hh:mm - //layout[`${id}_dur`]!.label = ``; - }else{ + else emptyLabel(id); - } }; const emptyLabel = (id: string) => { @@ -269,37 +274,36 @@ const drawRep = () => { (layout["duration"] as any).lazyBuster ^= 1; 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"; - const i = state.currentRepIndex(); if(i !== null){ + layout["repIdx"]!.label = `Rep ${i+1}`; repToLabel(i, "cur"); repToLabel(i+1, "next"); }else{ + layout["repIdx"]!.label = "Done"; emptyLabel("cur"); emptyLabel("next"); } } 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(); drawRep(); -// TODO: swipe handlers -//Bangle.setUI - -// TODO: widgets +Bangle.drawWidgets(); +} diff --git a/apps/rep/metadata.json b/apps/rep/metadata.json index 8820be486..a62d5e7e5 100644 --- a/apps/rep/metadata.json +++ b/apps/rep/metadata.json @@ -3,11 +3,11 @@ "name": "Rep", "version":"0.01", "description": "Steps through reps, to aid in run schedules", - "icon": "app.png", // TODO + "icon": "app.png", "tags": "run,running,fitness,outdoors", "supports" : ["BANGLEJS2"], "readme": "README.md", - "interface": "interface.html", + "interface": "interface.html", "storage": [ {"name":"rep.app.js","url":"app.js"}, {"name":"rep.img","url":"app-icon.js","evaluate":true}