diff --git a/apps/rest/ChangeLog b/apps/rest/ChangeLog new file mode 100644 index 000000000..5453557bc --- /dev/null +++ b/apps/rest/ChangeLog @@ -0,0 +1 @@ +0.01: First Release diff --git a/apps/rest/README.md b/apps/rest/README.md new file mode 100644 index 000000000..59f031cc3 --- /dev/null +++ b/apps/rest/README.md @@ -0,0 +1,16 @@ +# Rest - Workout Timer + +An app to keep track of time when not lifting things and keep track of your sets when lifting things. + +![screenshot](screenshot1.png) + +## Usage + +Install the app. Set the number of sets and the rest between sets. Once you tap "GO" the app is only +operated using the physical button on the watch, to avoid accidental touches during workout. + +The watch will vibrate to let you know when your rest time is up. + +## Credits + +Created by: devsnd diff --git a/apps/rest/app-icon.js b/apps/rest/app-icon.js new file mode 100644 index 000000000..fcc93857f --- /dev/null +++ b/apps/rest/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkA///+czAAk/BIILM+eIAAwMCme7AAwLCCw4ABEQIWHAAIuJGAX7C5M//AXJx87C5O/nAXJwYXK2YXax6UGC4e/UIYXJ/42DC6B7BwYwDC4iTGI44vJYgpHSC5JEBI5LzGL7gXjU64XKAA4XDAA4XYIYIAIx4XKV4IXJn6LGAAc//4XJOAgAGPoQuIBYMzFxIYCmYAEBQYLMABQWGDAgLLm93AA1zKYQAIEQIWHAAM/FxAwCFxAABl4XWuYXzUIQXHRAX/+QXGYoYXIEgMzmQXHco5HEn8nI6YXMJAQXUJQwXPCgQXsO8szd5IAGC4oAFC/4AHl5xEAAv/+YXJRQIwISoUyCw8jXQQALHRH/")) diff --git a/apps/rest/app.png b/apps/rest/app.png new file mode 100644 index 000000000..c04ed7831 Binary files /dev/null and b/apps/rest/app.png differ diff --git a/apps/rest/metadata.json b/apps/rest/metadata.json new file mode 100644 index 000000000..bdce75cd6 --- /dev/null +++ b/apps/rest/metadata.json @@ -0,0 +1,15 @@ +{ "id": "rest", + "name": "Rest - Workout Timer App", + "shortName":"Rest", + "version": "0.01", + "description": "Rest timer and Set counter for workout, fitness and lifting things.", + "icon": "app.png", + "screenshots": [{"url": "screenshot1.png"}, {"url": "screenshot2.png"}, {"url": "screenshot3.png"}], + "tags": "workout,weight lifting,rest,fitness,timer", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"rest.app.js","url":"rest.app.js"}, + {"name":"rest.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/rest/rest.app.js b/apps/rest/rest.app.js new file mode 100644 index 000000000..aeb066e55 --- /dev/null +++ b/apps/rest/rest.app.js @@ -0,0 +1,322 @@ + +function roundRect (x1, y1, x2, y2, halfrad) { + const fullrad = halfrad + halfrad + const bgColor = g.getBgColor(); + const fgColor = g.getColor(); + g.fillRect(x1, y1, x2, y2); + g.setColor(bgColor).fillRect(x1, y1, x1 + halfrad, y1 + halfrad); + g.setColor(fgColor).fillEllipse(x1, y1, x1 + fullrad, y1 + fullrad); + g.setColor(bgColor).fillRect(x2 - halfrad, y1, x2, y1 + halfrad); + g.setColor(fgColor).fillEllipse(x2 - fullrad, y1, x2, y1 + fullrad); + + g.setColor(bgColor).fillRect(x1, y2-halfrad, x1 + halfrad, y2); + g.setColor(fgColor).fillEllipse(x1, y2-fullrad, x1 + fullrad, y2); + g.setColor(bgColor).fillRect(x2 - halfrad, y2-halfrad, x2, y2); + g.setColor(fgColor).fillEllipse(x2 - fullrad, y2-fullrad, x2, y2); +} + +function center(r) { + return {x: r.x + (r.x2 - r.x)/2 + 1, y: r.y + (r.y2 - r.y)/2 + 1} +} +function inRect(r, xy) { + return xy.x >= r.x && xy.x <= r.x2 && xy.y >= r.y && xy.y <= r.y2; +} + +let restSeconds = 60; +let setsCount = 3; + +let currentSet = 1; +let restUntil = 0; + +Bangle.loadWidgets(); + +const m = 2; // margin +const R = Bangle.appRect; +const r = {x:R.x+m, x2:R.x2-m, y:R.y+m, y2:R.y2-m}; +const s = 2; // spacing +const h = r.y2 - r.y; +const w = r.x2 - r.x; +const cx = r.x + w/2; // center x +const cy = r.y + h/2; // center y +const q1 = {x: r.x, y: r.y, x2: cx - s, y2: cy - s}; +const q2 = {x: cx + s, y: r.y, x2: r.x2, y2: cy - s}; +const q3 = {x: r.x, y: cy + s, x2: cx - s, y2: r.y2}; +const q4 = {x: cx + s, y: cy + s, x2: r.x2, y2: r.y2}; +const quadrants = [q1,q2,q3,q4]; +const c1 = center(q1) +const c2 = center(q2) +const c3 = center(q3) +const c4 = center(q4) + +const GREY_COLOR = '#CCCCCC'; +const SET_COLOR = '#FF00FF'; +const SET_COLOR_MUTED = '#FF88FF'; +const REST_COLOR = '#00FFFF'; +const REST_COLOR_MUTED = '#88FFFF'; +const RED_COLOR = '#FF0000'; +const GREEN_COLOR = '#00FF00'; +const GREEN_COLOR_MUTED = '#88FF88'; +const BIG_FONT = "6x8:2x2"; +const HUGE_FONT = "6x8:3x3"; +const BIGHUGE_FONT = "6x8:6x6"; + +function drawMainMenu(splash) { + g.setColor(REST_COLOR); + roundRect(q1.x, q1.y, q1.x2, q1.y2, 20); + g.setColor(SET_COLOR); + roundRect(q2.x, q2.y, q2.x2, q2.y2, 20); + g.setColor(GREY_COLOR); + roundRect(q3.x, q3.y, q3.x2, q3.y2, 20); + g.setColor(GREEN_COLOR); + roundRect(q4.x, q4.y, q4.x2, q4.y2, 20); + g.setColor(-1) + + if (splash) { + g.setFont(BIGHUGE_FONT).setFontAlign(0,0).drawString("R", c1.x, c1.y) + g.setFont(BIGHUGE_FONT).setFontAlign(0,0).drawString("E", c2.x, c2.y) + g.setFont(BIGHUGE_FONT).setFontAlign(0,0).drawString("S", c3.x, c3.y) + g.setFont(BIGHUGE_FONT).setFontAlign(0,0).drawString("T", c4.x, c4.y) + } else { + g.setFont("6x8").setFontAlign(0,0).drawString("Tap to\nConfigure", c1.x, c1.y-25) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString(restSeconds+ "s", c1.x, c1.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("REST", c1.x, c1.y + 25) + + g.setFont("6x8").setFontAlign(0,0).drawString("Tap to\nConfigure", c2.x, c2.y-25) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString(setsCount, c2.x, c2.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("SETS", c2.x, c2.y + 25) + + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("JUST\nDO\nIT", c3.x, c3.y) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString("GO", c4.x, c4.y) + } +} + +function drawSetRest() { + g.setColor(REST_COLOR); + roundRect(q1.x, q1.y, q1.x2, q1.y2, 20); + g.setColor(RED_COLOR); + roundRect(q3.x, q3.y, q3.x2, q3.y2, 20); + g.setColor(GREEN_COLOR); + roundRect(q4.x, q4.y, q4.x2, q4.y2, 20); + g.setColor(-1) + g.setFont("6x8").setFontAlign(0,0).drawString("Tap to\nConfirm", c1.x, c1.y-25) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString(restSeconds+ "s", c1.x, c1.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("REST", c1.x, c1.y + 25) + // g.setFont(BIG_FONT).setFontAlign(0,0).drawString("OK", c2.x, c2.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("-", c3.x, c3.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("+", c4.x, c4.y) +} + +function drawSetSets() { + g.setColor(SET_COLOR); + roundRect(q2.x, q2.y, q2.x2, q2.y2, 20); + g.setColor(RED_COLOR); + roundRect(q3.x, q3.y, q3.x2, q3.y2, 20); + g.setColor(GREEN_COLOR); + roundRect(q4.x, q4.y, q4.x2, q4.y2, 20); + g.setColor(-1) + g.setFont("6x8").setFontAlign(0,0).drawString("Tap to\nConfirm", c2.x, c2.y-25) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString(setsCount, c2.x, c2.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("SETS", c2.x, c2.y + 25) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("-", c3.x, c3.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("+", c4.x, c4.y) +} + +function drawExercise() { + g.setColor(REST_COLOR_MUTED); + roundRect(q1.x, q1.y, q1.x2, q1.y2, 20); + g.setColor(SET_COLOR); + roundRect(q2.x, q2.y, q2.x2, q2.y2, 20); + g.setColor(GREEN_COLOR_MUTED); + roundRect(q4.x, q4.y, q4.x2, q4.y2, 20); + g.setColor(-1); + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("SET", c2.x, c2.y-25) + g.setFont(HUGE_FONT).setFontAlign(0,0).drawString("#"+currentSet, c2.x, c2.y) + g.setFont(BIG_FONT).setFontAlign(0,0).drawString("PUSH >\nBUTTON\nWHEN\nDONE", c4.x, c4.y) +} + +function circlePoints (cx, cy, r, points) { + let circlePoints = []; + for (let i=0; i> 1) << 1 + const poly = smallQ3Circle.slice(0, circleParts + 2) + g.setColor(SET_COLOR); + g.fillPoly(poly); + + g.setColor(GREY_COLOR); + roundRect(q3.x, q3.y, q3.x2, q3.y2, 20); + g.setColor(-1).setFont(BIG_FONT).setFontAlign(0,0).drawString("REST", c3.x, c3.y) + + g.setColor(0); + g.setFont("6x8").drawString("Push button\nto skip ->", c4.x, c4.y); + + if (secondsRemaining > 0) { + if (secondsRemaining < 5) { + if (secondsRemaining > 1) { + Bangle.buzz(100); + } else { + Bangle.buzz(1000); + } + } + const renderTime = Date.now() - start; + setTimeout(redrawApp, Math.max(10, 1000 - renderTime)); + } else { + currentSet += 1; + if (currentSet > setsCount) { + currentSet = 1; + setMode(MAIN_MENU); + } else { + setMode(EXERCISE); + } + redrawApp(); + } +} + +function drawDoIt() { + const oldBgColor = g.getBgColor(); + g.setBgColor('#00FF00').clear(); + g.drawImage(getImg(), 44, 44); + g.setFont(BIG_FONT) + g.setColor(0); + setTimeout(() => { + g.setFontAlign(0, 0) + g.drawString('just ', R.x2/2, 20); + Bangle.buzz(150, 0.5); + }, 200); + setTimeout(() => { + g.drawImage(getImg(), 22, 44, {scale: 1.5}); + g.drawString(' DO ', R.x2/2, 20); + Bangle.buzz(200); + }, 1000); + setTimeout(() => { + g.drawString(' IT', R.x2/2, 20); + Bangle.buzz(200); + }, 1400); + setTimeout(() => { + setMode(MAIN_MENU); + g.setBgColor(oldBgColor); + redrawApp(); + }, 2000); +} + +const MAIN_MENU = 'MAIN_MENU'; +const SET_REST = 'SET_REST'; +const SET_SETS = 'SET_SETS'; +const EXERCISE = 'EXERCISE'; +const REST = 'REST'; +const DOIT = 'DOIT'; + +let mode = MAIN_MENU; + +function setMode(newMode){ + mode = newMode; +} + +function getImg() { + return require("heatshrink").decompress(atob("rFYwcBpMkyQCB6QFDmnStsk6dpmmatO2AoMm7VpkmapMm6Vp02TEAmSCIIFB2mbEYPbtu07VJmwFCzYRD0gdB0gmBEAgCCtoOBtIOBIIPTpo1BHwJQCAQMmydNI4RBFLIILDmnaps2L4Om7ZEBI4IgCAQNN0g+GJQKJDKwIaB0iJCJQQmBCgWmHAIdEHYKnFDQSbBkBcE0wOBFgImBSoMmQZJTE6VAbYMJPQRHBDQKMBmmTtoUCEBPSJQT8CgKPCcAJQEIILFHMohxDEAUANwZ9E0wdBUhDLGyAgDO4LIByYOBAQLpEL45KEm2AQIMkwEEYQZTB7Vt23TC4wCHCgOAgRUBEAL+CzVtkwRCHw4CJEANNm2QggXEX4jpBIJgCBgESOoKHB6RiByYCBDQSGCMoIdJHAQgCkmCgALCZALpCd4RiNYoKkCkESpC8CEYm2QByDDgEBkETpBWDtukKYZBOHAKkBgIGBIIRNC0wFEIKCDCyVEBASbLAReQEAXSghKCzQ7BQYIgUoAGBEARuDIKmSgAAByAgFASwgCgALFmikUEBRBYgggcwBBDtDrDASwfDgFIgAgYkAfDgVAgEJECw6BAAcSEAKGXDIUAhEgZIcEYS4ABAwwgUyAgFAwjIUDIifBdQggUDIkBZIjKBECYZEAA4gSHQogoRYIgQD5gghgIgQpAg/QeAgRQcNAggeLECQDBwAgryIgTxAgKwAgQpQgKgMhkmQIKcIIJEgEA+kEBNApMgdJBhBgkQIKFCpMAEBUAMQ+aIJUioAgKIItpIJkCEBEAIJIgKhIgMyRBFmikLMRMAgkEEAmTUhogRARlAhIggkAgLUiNIpMgD5AgWXQIgcpMJED8BEBmAED0kwIgRkAgLkAgSkMkwAhKxIgRkgggXIIcFgIEDaYIgRwggGgBKDECcEyVAgEQEIkSpIgUgADCQwzSBEC0gD4pBBkQdQDgYgIBAIgVHAJFBcYgMBgQgUPQIgFFINIBQQgQTYYgfXQIgFFYggPGgIVCgmQDogFCECr8CII4KCECUBED4AKFYQgOoAYFggIGEC4XDEDgLDkAgVD4kCBYgKEECsSBYmAEDILFEEGQEBYA==")); +} + +const onTouchPerQuadrantPerMode = { + // mode -> [[nextMode on touch, custom function], ... for all quadrants] + MAIN_MENU: [ + [SET_REST, null], [SET_SETS, null], + [DOIT, null], [EXERCISE, null] + ], + SET_REST: [ + [MAIN_MENU, Bangle.buzz], [null, null], + [null, () => { + restSeconds = Math.min(120, Math.max(0, restSeconds - 15)); + Bangle.buzz(100); + }], + [null, () => { + restSeconds = Math.min(120, Math.max(0, restSeconds + 15)); + Bangle.buzz(100); + }], + ], + SET_SETS: [ + [null, null], [MAIN_MENU, Bangle.buzz], + [null, () => { + setsCount = Math.min(15, Math.max(0, setsCount - 1)); + Bangle.buzz(100); + }], + [null, () => { + setsCount = Math.min(15, Math.max(0, setsCount + 1)); + Bangle.buzz(100); + }], + ], + EXERCISE: [ + [null, null], [null, null], + [null, null], [null, null], + ], + REST: [ + [null, null], [null, null], + [null, null], [null, null], + ] +} + +const drawFuncPerMode = { + MAIN_MENU: drawMainMenu, + SET_REST: drawSetRest, + SET_SETS: drawSetSets, + EXERCISE: drawExercise, + REST: drawRest, + DOIT: drawDoIt, +} + +function redrawApp(){ + g.clear(); + Bangle.drawWidgets(); + drawFuncPerMode[mode](); +} + +function buttonPress () { + if (mode === EXERCISE) { + setMode(REST); + restUntil = Date.now() + (restSeconds * 1000); + redrawApp(); + return; + } + if (mode === REST) { + restUntil = Date.now(); // skipping rest! + redrawApp(); + return; + } +} + +setWatch(buttonPress, BTN, { repeat: true, debounce: 25, edge:"falling"}); + +Bangle.on('touch', (button, xy) => { + for (let qidx=0; qidx<4; qidx++) { + if (inRect(quadrants[qidx], xy)) { + const nextMode = onTouchPerQuadrantPerMode[mode][qidx][0]; + const func = onTouchPerQuadrantPerMode[mode][qidx][1]; + if (func) func(); + if (nextMode) setMode(nextMode); + redrawApp(); + } + } +}); + +g.clear(); +drawMainMenu(true); +setTimeout(redrawApp, 1000); + diff --git a/apps/rest/screenshot1.png b/apps/rest/screenshot1.png new file mode 100644 index 000000000..616a7232b Binary files /dev/null and b/apps/rest/screenshot1.png differ diff --git a/apps/rest/screenshot2.png b/apps/rest/screenshot2.png new file mode 100644 index 000000000..e878fea96 Binary files /dev/null and b/apps/rest/screenshot2.png differ diff --git a/apps/rest/screenshot3.png b/apps/rest/screenshot3.png new file mode 100644 index 000000000..28d94eb91 Binary files /dev/null and b/apps/rest/screenshot3.png differ