From a382c6bd4f785a49357712781dcb72c7ce44233b Mon Sep 17 00:00:00 2001 From: crazysaem Date: Wed, 22 Dec 2021 22:28:51 +0000 Subject: [PATCH 1/9] touchtimer: initial creation --- apps.json | 16 +++++++ apps/touchtimer/ChangeLog | 1 + apps/touchtimer/README.md | 3 ++ apps/touchtimer/app-icon.js | 1 + apps/touchtimer/app.js | 92 ++++++++++++++++++++++++++++++++++++ apps/touchtimer/app.png | Bin 0 -> 1770 bytes 6 files changed, 113 insertions(+) create mode 100644 apps/touchtimer/ChangeLog create mode 100644 apps/touchtimer/README.md create mode 100644 apps/touchtimer/app-icon.js create mode 100644 apps/touchtimer/app.js create mode 100644 apps/touchtimer/app.png diff --git a/apps.json b/apps.json index e5e9f8f02..33dae37c7 100644 --- a/apps.json +++ b/apps.json @@ -5062,5 +5062,21 @@ {"name":"ltherm.app.js","url":"app.js"}, {"name":"ltherm.img","url":"icon.js","evaluate":true} ] + }, + { + "id": "touchtimer", + "name": "Touch Timer", + "shortName": "Touch Timer", + "version": "0.01", + "description": "Quickly and easily create a timer touch-only.", + "icon": "app.png", + "tags": "tools", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + { "name": "touchtimer.app.js", "url": "app.js" }, + { "name": "touchtimer.boot.js", "url": "boot.js" }, + { "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true } + ] } ] diff --git a/apps/touchtimer/ChangeLog b/apps/touchtimer/ChangeLog new file mode 100644 index 000000000..193a476aa --- /dev/null +++ b/apps/touchtimer/ChangeLog @@ -0,0 +1 @@ +0.01: Initial creation of the touch timer app \ No newline at end of file diff --git a/apps/touchtimer/README.md b/apps/touchtimer/README.md new file mode 100644 index 000000000..99c755639 --- /dev/null +++ b/apps/touchtimer/README.md @@ -0,0 +1,3 @@ +# Touch Timer + +Quickly and easily create a timer touch-only. diff --git a/apps/touchtimer/app-icon.js b/apps/touchtimer/app-icon.js new file mode 100644 index 000000000..d58446bcc --- /dev/null +++ b/apps/touchtimer/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkE/4A3mUQIAMRkYWQkBaFiQWQgMjn8zGYUDCxkxFA3zD4MfCxXygECMAURiReCDAM/IpUBFIJ2CAAIeB+ZJKBYI8BCwMBiABBDARSBC5EwFwMwEwUwh5FCEIJhJiEfGIIXC+IQBSwQeBNYR1Gn4xB+MDDYITBiEzFoIOCC4vwEAIxBAwQzBAoQtCBgaNEh4iEAwMwRQXxHgRnBLwsvFQJdCFoIGBl55DH4QAEEIK/BC4KjBC4RECiED+RnBXooxCn4uBKwPwgIiB+fxgQQCRwgeBLwRbBkAXBh5yCBwoACEAoVBC4fwJ4I+DC5EjJQQXDBYP/kJWDC4qmBBYYXFfIQXKiQvUL6AXGR5LzBR4YXIBAS/BC4UCeAQOFC4rvDN4LvCFYMgd4IXJmEABgMxC4bWBiADDC45EBZIRHBMYINCBQQXIIgIkB//wgIFDmBKBC5QNB+UDboU/kEzgCRBC5QTBNwUxLoZRDC5J5EmAqBkEAiYMCC5XzFIMRkECAgILDC5YYDAAUBIoQXNDAMhiMRkYJEC5oAKC7qKBACDfCK4IWRPwjqBkczAB0yGAcQGgYAOmByCfAYAP+MBC4QWR//yC4ciACMhC4YATC4T9BACUSLiQAdA=")) \ No newline at end of file diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js new file mode 100644 index 000000000..7ffce959f --- /dev/null +++ b/apps/touchtimer/app.js @@ -0,0 +1,92 @@ +var DEBUG = true; + +var main = () => { + var button0 = new Button({ x1: 0, y1: 35, x2: 58, y1: 70 }, 0); + + button0.draw(); + + button0.onClick((value) => { + log("button with value clicked"); + log(value); + }); +}; + +// lib functions + +var log = (message) => { + if (DEBUG) { + console.log(JSON.stringify(message)); + } +}; + +var touchHandlers = []; + +Bangle.on("touch", (_button, xy) => { + touchHandlers.forEach((touchHandler) => { + touchHandler(xy); + }); +}); + +var BUTTON_BORDER_WITH = 2; + +class Button { + constructor(position, value) { + this.position = position; + this.value = value; + + this.onClickCallbacks = []; + + touchHandlers.push((xy) => { + var x = xy.x; + var y = xy.y; + + if ( + x >= this.position.x1 && + x <= this.position.x2 && + y >= this.position.y1 && + y <= this.position.y2 + ) { + this.onClickCallbacks.forEach((onClickCallback) => + onClickCallback(this.value) + ); + } + }); + } + + draw() { + g.clear(); + + g.setColor(g.theme.fg); + g.fillRect( + this.position.x1, + this.position.y1, + this.position.x2, + this.position.y2 + ); + + g.setColor(g.theme.bg); + g.fillRect( + this.position.x1 + BUTTON_BORDER_WITH, + this.position.y1 + BUTTON_BORDER_WITH, + this.position.x2 - BUTTON_BORDER_WITH, + this.position.y2 - BUTTON_BORDER_WITH + ); + + g.setColor(g.theme.fg); + g.setFontAlign(0, 0); + g.setFont("Vector", 40); + g.drawString( + this.value, + this.position.x2 - this.position.x1, + this.position.y2 - this.position.y1 + ); + } + + onClick(callback) { + this.onClickCallbacks.push(callback); + } +} + +// start main function + +main(); diff --git a/apps/touchtimer/app.png b/apps/touchtimer/app.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccdb17f0cc322440aeb52d649f1c6dededacef4 GIT binary patch literal 1770 zcmVPx#1ZP1_K>z@;j|==^1poj7novwsMgRZ*{{H^*^782D=;`U{`1ts_xw&+7bU!~o z8yg!32L}QI0t^fcBO@bDPELr3h|<#1{r&yo;^Gz-761SME-o&YmzVbT_Ve@ec6N3N z2?+}e3rkB&wY9b1-{0Ka+{((zsi~=VcXu>2Gy?+z9v&X0rKQHk#wjT&nVFgK@$nE4 z5bEmcp`oD&2nZ1o5tx{m`}_O1x3`0XgJNQ0Qc_Y?RaI(gYLStV%gf9B{QT?d>t|V!Nl8grSy?M9D**uk4-XHG zjg8ye+u`Bi)YQ~bQBlRk#iOI6aBy%$L`2}=;2j+uHa0f-`T6?#`ZYB*FE20j^z;e} z3MVHgV`F1zXlUo>=YM~H)6>(=&dv=D4Js-s-rn9}VPUeeva_?Z6B83NGc#pnWv#8P zudlE5_4SO5j2Rgjd3kvzCMM?Q<_-=H1_lO3Mn<@}xM^u=U|?XLo}TXR?zFVDhK7dl z@bJ96yk=%*uCA^svsO%yB<21BVPrBEp-3K^DET4*9!5oKi- zm8lsiWfv>cEV|z>|4ZL<_HxM%jUVds%ihcLIq&V9cld9xjEk_2SLLvbw?$qT6|HG8 zu@ff7*;V7{#78D@1c^zLREIgq*E>k(jS7umt&^o5ltsy+*CnHj2^PfPDx=(% zgZ1UyP@!T&RQg#}4O@Ud%{yPFUb9gaut{nxuEk;pZ&$e?mePF(?hJBs-CeG`v1Kb+ zACCstJ@*FazYp8!NKrw>r0r}(auaJJki7#DLGEnkE_ikTyQA5H$||<1q~d=1_Mnk` zK(AQZxP0$}bj2B657Cv1eQd5ZYJ;@%VMNGw1|9((-A^7sx|hw5$*c6Z>{SP{4|1D| z8blo86FAJ3HSEOPBeFM!Kz0H7%%hfzC)uvD2Aw?W!Bc#?vIJ;lOle4XhTeUsNU;~o z9(B__PK9+E`7EqGd`^mMU3~snNLc>@H_PTqZ#$A5GKXOb_#$4Sqfm;+S#MFonz|lN zlctxs)q^I^HliU;a5Y}x&f{`qugds?g3PiPo<&*IUc>8rLxput@=YYkB7MLqo*ot^ z^9!R!XspCx!%TG|x%X z6#6Pmg!3BvrI_l~-W?R)W4Zp8UZ5W)g1v?dV29;>ZeJ1-R=7A1uv`3$G!taIKiD7B zjq~NhC*5bqht4do_ajr`;vv$z1t>{Wg11!;ra`=Vxj0M&d`y0#*9t%7B~QP|`UO+* z87WUO6|9$epjslRYvz^D>9HCWiH@5{Ub^RzGt~is7)3;XCTxwih$f7By?$G3xgL* zGs&J!gI~dD0`U8J9Vh*1T(0|t)$)Sh@`*U!KFs#(^b M07*qoM6N<$f}*WUg8%>k literal 0 HcmV?d00001 From 6eb5858742702d10deaf97645c958b3277a75413 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Wed, 22 Dec 2021 22:39:26 +0000 Subject: [PATCH 2/9] touchtimer: fix app.json --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index 33dae37c7..93c5b7fcf 100644 --- a/apps.json +++ b/apps.json @@ -5075,7 +5075,6 @@ "readme": "README.md", "storage": [ { "name": "touchtimer.app.js", "url": "app.js" }, - { "name": "touchtimer.boot.js", "url": "boot.js" }, { "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true } ] } From 62b3cf0796df1e168a9b30c651bda5460273e0e5 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Thu, 23 Dec 2021 22:56:08 +0000 Subject: [PATCH 3/9] touchtimer: add all main buttons and create click handlers --- apps/touchtimer/app.js | 212 ++++++++++++++++++++++++++++++++++------- 1 file changed, 180 insertions(+), 32 deletions(-) diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index 7ffce959f..534f4d62c 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -1,13 +1,114 @@ var DEBUG = true; var main = () => { - var button0 = new Button({ x1: 0, y1: 35, x2: 58, y1: 70 }, 0); + var button1 = new Button({ x1: 1, y1: 35, x2: 58, y2: 70 }, 1); + var button2 = new Button({ x1: 60, y1: 35, x2: 116, y2: 70 }, 2); + var button3 = new Button({ x1: 118, y1: 35, x2: 174, y2: 70 }, 3); - button0.draw(); + var button4 = new Button({ x1: 1, y1: 72, x2: 58, y2: 105 }, 4); + var button5 = new Button({ x1: 60, y1: 72, x2: 116, y2: 105 }, 5); + var button6 = new Button({ x1: 118, y1: 72, x2: 174, y2: 105 }, 6); - button0.onClick((value) => { - log("button with value clicked"); - log(value); + var button7 = new Button({ x1: 1, y1: 107, x2: 58, y2: 140 }, 7); + var button8 = new Button({ x1: 60, y1: 107, x2: 116, y2: 140 }, 8); + var button9 = new Button({ x1: 118, y1: 107, x2: 174, y2: 140 }, 9); + + var buttonStart = new Button({ x1: 1, y1: 142, x2: 58, y2: 174 }, "GO"); + var button0 = new Button({ x1: 60, y1: 142, x2: 116, y2: 174 }, 0); + var buttonDelete = new Button({ x1: 118, y1: 142, x2: 174, y2: 174 }, "<-"); + + var timerNumberButtons = [ + button1, + button2, + button3, + button4, + button5, + button6, + button7, + button8, + button9, + button0, + ]; + + var timerInputButtons = [ + button1, + button2, + button3, + button4, + button5, + button6, + button7, + button8, + button9, + buttonStart, + button0, + buttonDelete, + ]; + + var buttonPauseContinue = new Button( + { x1: 1, y1: 35, x2: 174, y2: 105 }, + "PAUSE" + ); + var buttonStop = new Button({ x1: 1, y1: 107, x2: 174, y2: 174 }, "STOP"); + + var timerRunningButtons = [buttonPauseContinue, buttonStop]; + + var timeStr = ""; + timerNumberButtons.forEach((numberButton) => { + numberButton.setOnClick((value) => { + log("number button clicked"); + log(value); + log(timeStr); + if (value === 0 && timeStr.length === 0) { + return; + } + + if (timeStr.length <= 6) { + timeStr = timeStr + value; + } + log(timeStr); + drawTimer(timeStr); + }); + }); + + buttonDelete.setOnClick(() => { + log("delete button clicked"); + timeStr = timeStr.slice(0, -1); + log(timeStr); + drawTimer(timeStr); + }); + + buttonStart.setOnClick(() => { + g.clear(); + drawTimer(timeStr); + + timerInputButtons.forEach((button) => button.disable()); + + timerRunningButtons.forEach((button) => { + button.enable(); + button.draw(); + }); + }); + + buttonStop.setOnClick(() => { + g.clear(); + timeStr = ""; + drawTimer(timeStr); + + timerRunningButtons.forEach((button) => button.disable()); + + timerInputButtons.forEach((button) => { + button.enable(); + button.draw(); + }); + }); + + // initalize + g.clear(); + drawTimer(timeStr); + timerInputButtons.forEach((button) => { + button.enable(); + button.draw(); }); }; @@ -19,11 +120,35 @@ var log = (message) => { } }; +var drawTimer = (timeStr) => { + timeStr = timeStr.padStart(6, "0"); + var timeStrDisplay = + "" + + timeStr.slice(0, 2) + + "h " + + timeStr.slice(2, 4) + + "m " + + timeStr.slice(4, 6) + + "s"; + + g.clearRect(0, 0, 176, 34); + g.setColor(g.theme.fg); + g.setFontAlign(-1, -1); + g.setFont("Vector:26x40"); + g.drawString(timeStrDisplay, 2, 0); +}; + var touchHandlers = []; Bangle.on("touch", (_button, xy) => { + log("touch"); + log(xy); + + var x = Math.min(Math.max(xy.x, 1), 174); + var y = Math.min(Math.max(xy.y, 1), 174); + touchHandlers.forEach((touchHandler) => { - touchHandler(xy); + touchHandler(x, y); }); }); @@ -34,28 +159,11 @@ class Button { this.position = position; this.value = value; - this.onClickCallbacks = []; - - touchHandlers.push((xy) => { - var x = xy.x; - var y = xy.y; - - if ( - x >= this.position.x1 && - x <= this.position.x2 && - y >= this.position.y1 && - y <= this.position.y2 - ) { - this.onClickCallbacks.forEach((onClickCallback) => - onClickCallback(this.value) - ); - } - }); + this.touchHandler = undefined; + this.highlightTimeoutId = undefined; } - draw() { - g.clear(); - + draw(highlight) { g.setColor(g.theme.fg); g.fillRect( this.position.x1, @@ -64,7 +172,11 @@ class Button { this.position.y2 ); - g.setColor(g.theme.bg); + if (highlight) { + g.setColor(g.theme.bgH); + } else { + g.setColor(g.theme.bg); + } g.fillRect( this.position.x1 + BUTTON_BORDER_WITH, this.position.y1 + BUTTON_BORDER_WITH, @@ -74,16 +186,52 @@ class Button { g.setColor(g.theme.fg); g.setFontAlign(0, 0); - g.setFont("Vector", 40); + g.setFont("Vector", 35); g.drawString( this.value, - this.position.x2 - this.position.x1, - this.position.y2 - this.position.y1 + this.position.x1 + (this.position.x2 - this.position.x1) / 2 + 2, + this.position.y1 + (this.position.y2 - this.position.y1) / 2 + 2 ); } - onClick(callback) { - this.onClickCallbacks.push(callback); + setOnClick(callback) { + this.touchHandler = (x, y) => { + if ( + x >= this.position.x1 && + x <= this.position.x2 && + y >= this.position.y1 && + y <= this.position.y2 + ) { + this.draw(true); + this.highlightTimeoutId = setTimeout(() => { + this.draw(); + this.highlightTimeoutId = undefined; + }, 100); + setTimeout(() => callback(this.value), 25); + } + }; + } + + disable() { + log("disable button"); + log(this.value); + var touchHandlerIndex = touchHandlers.indexOf(this.touchHandler); + if (touchHandlerIndex > -1) { + log("clearing touch handler"); + touchHandlers.splice(touchHandlerIndex, 1); + } + + if (this.highlightTimeoutId) { + log("clearing higlight timeout"); + clearTimeout(this.highlightTimeoutId); + this.highlightTimeoutId = undefined; + } + } + + enable() { + if (this.touchHandler) { + touchHandlers.push(this.touchHandler); + } } } From 8bbed23dc81906a17096afaa5f38c113e6f2fb94 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Mon, 27 Dec 2021 21:08:27 +0000 Subject: [PATCH 4/9] touchtimer: add actual timer functionality --- apps/touchtimer/app.js | 134 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index 534f4d62c..c0da034b7 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -13,7 +13,7 @@ var main = () => { var button8 = new Button({ x1: 60, y1: 107, x2: 116, y2: 140 }, 8); var button9 = new Button({ x1: 118, y1: 107, x2: 174, y2: 140 }, 9); - var buttonStart = new Button({ x1: 1, y1: 142, x2: 58, y2: 174 }, "GO"); + var buttonOK = new Button({ x1: 1, y1: 142, x2: 58, y2: 174 }, "OK"); var button0 = new Button({ x1: 60, y1: 142, x2: 116, y2: 174 }, 0); var buttonDelete = new Button({ x1: 118, y1: 142, x2: 174, y2: 174 }, "<-"); @@ -40,18 +40,18 @@ var main = () => { button7, button8, button9, - buttonStart, + buttonOK, button0, buttonDelete, ]; - var buttonPauseContinue = new Button( + var buttonStartPause = new Button( { x1: 1, y1: 35, x2: 174, y2: 105 }, - "PAUSE" + "START" ); var buttonStop = new Button({ x1: 1, y1: 107, x2: 174, y2: 174 }, "STOP"); - var timerRunningButtons = [buttonPauseContinue, buttonStop]; + var timerRunningButtons = [buttonStartPause, buttonStop]; var timeStr = ""; timerNumberButtons.forEach((numberButton) => { @@ -78,7 +78,11 @@ var main = () => { drawTimer(timeStr); }); - buttonStart.setOnClick(() => { + buttonOK.setOnClick(() => { + if (timeStr.length === 0) { + return; + } + g.clear(); drawTimer(timeStr); @@ -90,7 +94,80 @@ var main = () => { }); }); + var timerIntervalId = undefined; + var buzzIntervalId = undefined; + buttonStartPause.setOnClick(() => { + if (buttonStartPause.value === "PAUSE") { + buttonStartPause.value = "START"; + buttonStartPause.draw(); + + if (timerIntervalId) { + clearInterval(timerIntervalId); + timerIntervalId = undefined; + } + + if (buzzIntervalId) { + clearInterval(buzzIntervalId); + buzzIntervalId = undefined; + } + + return; + } + + if (buttonStartPause.value === "START") { + buttonStartPause.value = "PAUSE"; + buttonStartPause.draw(); + + var time = timeStrToTime(timeStr); + + timerIntervalId = setInterval(() => { + time = time - 1; + + timeStr = timeToTimeStr(time); + drawTimer(timeStr); + + if (time === 0) { + buttonStartPause.value = "FINISHED!"; + buttonStartPause.draw(); + + if (timerIntervalId) { + clearInterval(timerIntervalId); + timerIntervalId = undefined; + } + + var buzzCount = 0; + Bangle.buzz(1000, 1); + buzzIntervalId = setInterval(() => { + if (buzzCount >= 10) { + clearInterval(buzzIntervalId); + buzzIntervalId = undefined; + return; + } else { + Bangle.buzz(1000, 1); + buzzCount++; + } + }, 5000); + } + }, 1000); + + return; + } + }); + buttonStop.setOnClick(() => { + if (timerIntervalId) { + clearInterval(timerIntervalId); + timerIntervalId = undefined; + } + + if (buzzIntervalId) { + clearInterval(buzzIntervalId); + buzzIntervalId = undefined; + } + + buttonStartPause.value = "START"; + buttonStartPause.draw(); + g.clear(); timeStr = ""; drawTimer(timeStr); @@ -235,6 +312,51 @@ class Button { } } +var timeToTimeStr = (time) => { + var hours = Math.floor(time / 3600); + time = time - hours * 3600; + var minutes = Math.floor(time / 60); + time = time - minutes * 60; + var seconds = time; + + if (hours === 0) { + hours = ""; + } else { + hours = hours.toString(); + } + + if (hours.length === 0) { + if (minutes === 0) { + minutes = ""; + } else { + minutes = minutes.toString(); + } + } else { + minutes = minutes.toString().padStart(2, "0"); + } + + if (hours.length === 0 && minutes.length === 0) { + if (seconds === 0) { + seconds = ""; + } else { + seconds = seconds.toString(); + } + } else { + seconds = seconds.toString().padStart(2, "0"); + } + + return hours + minutes + seconds; +}; + +var timeStrToTime = (timeStr) => { + timeStr = timeStr.padStart(6, "0"); + return ( + parseInt(timeStr.slice(0, 2), 10) * 3600 + + parseInt(timeStr.slice(2, 4), 10) * 60 + + parseInt(timeStr.slice(4, 6), 10) + ); +}; + // start main function main(); From bf345e53b8a42456b502d1a5d43a2ed450af8b5f Mon Sep 17 00:00:00 2001 From: crazysaem Date: Mon, 27 Dec 2021 21:27:33 +0000 Subject: [PATCH 5/9] touchtimer: add screenshots and describe the usage --- apps.json | 3 ++- apps/touchtimer/0_dark_timer_edit.png | Bin 0 -> 3790 bytes apps/touchtimer/0_light_timer_edit.png | Bin 0 -> 3844 bytes apps/touchtimer/1_dark_timer_ready.png | Bin 0 -> 3100 bytes apps/touchtimer/1_light_timer_ready.png | Bin 0 -> 3085 bytes apps/touchtimer/2_dark_timer_running.png | Bin 0 -> 3021 bytes apps/touchtimer/2_light_timer_running.png | Bin 0 -> 3007 bytes apps/touchtimer/3_dark_timer_finished.png | Bin 0 -> 2895 bytes apps/touchtimer/3_light_timer_finished.png | Bin 0 -> 2912 bytes apps/touchtimer/README.md | 27 ++++++++++++++++++++- 10 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 apps/touchtimer/0_dark_timer_edit.png create mode 100644 apps/touchtimer/0_light_timer_edit.png create mode 100644 apps/touchtimer/1_dark_timer_ready.png create mode 100644 apps/touchtimer/1_light_timer_ready.png create mode 100644 apps/touchtimer/2_dark_timer_running.png create mode 100644 apps/touchtimer/2_light_timer_running.png create mode 100644 apps/touchtimer/3_dark_timer_finished.png create mode 100644 apps/touchtimer/3_light_timer_finished.png diff --git a/apps.json b/apps.json index 93c5b7fcf..d457198be 100644 --- a/apps.json +++ b/apps.json @@ -5068,11 +5068,12 @@ "name": "Touch Timer", "shortName": "Touch Timer", "version": "0.01", - "description": "Quickly and easily create a timer touch-only.", + "description": "Quickly and easily create a timer with touch-only input. The time caan be easily set with a number pad.", "icon": "app.png", "tags": "tools", "supports": ["BANGLEJS2"], "readme": "README.md", + "screenshots": [{"url":"0_light_timer_edit.png"},{"url":"1_light_timer_ready.png"},{"url":"2_light_timer_running.png"},{"url":"3_light_timer_finished.png"}], "storage": [ { "name": "touchtimer.app.js", "url": "app.js" }, { "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true } diff --git a/apps/touchtimer/0_dark_timer_edit.png b/apps/touchtimer/0_dark_timer_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..2160ef38db0205d4f1b11da3ac7aedaf2be61981 GIT binary patch literal 3790 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%0DEqKF<0s>QUH}Jv4ncaw+5+mSBP|NI>q31yfHM-A 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 literal 0 HcmV?d00001 diff --git a/apps/touchtimer/1_light_timer_ready.png b/apps/touchtimer/1_light_timer_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..5e2ca3c9d689316ac26a112bee4c315bccc5adbc GIT binary patch literal 3085 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 oT@*^`rXZKr*~Y`-zof5BG;V)!B)-6S)@&0Km>; zcGf4iu=3v{wQXxx)O{vx0Z2PZv;xYzm1h6|6?@Fu@^lR8Yu;Sc96$~7 zV>9D(p0aU$rQp8Q!}NBCFC_w9kgz>9SC&ZOtd`;`_t$SHpQ&KK@1x*On)i7}K)2-Gs)|2zPRh2xZ=8UGr~@c6fowUEp4z!dp5eg+OSNLK7~a7zffl`Hjfx2&L2bTfM4UD*L!_km;rel6 zie;NyRptbWys_&V+lhnbpc$OM3k>*bM8Weiz&WK$LeN)Nc9lKIvkSAOaN>&u?mLTC z-DEtbK8)#Ji(H9<@ku|=!F(z@IZ4%sG-NjhkNZG1D_XS{DK8jM;Zu4`jNz3d7IXjP zL5CU(SqRH9CEPsl++T$jOYEl`@V!rb!o8s(K6q)~AZvCszPfGtzYmiNB zlEqOD@rH|RrU^IWxGT{8X!9Psjx_!CZ)U;RfR^WcGeCA}wuH=Gd5SH|;ZyQ1AdP?_btE$Yzsot0DgdZVCK z7S#UK4M z@!d~)`hCPw@S4^WE8?#FC3`wiKgKeZPG7TbH@IOv5g_63)u!u3caZGV zu!V$P8jg0CH|WQ7E}>vJ(Q?g<)%{?=3la6H zZWx=Zv0ELf}SneMoYO9){l%E3O)4;2aN^t;KH|e_U1lNmA+KJIPeN21Xm3 zeXmnygkBm@&f_Es6>iw&)n|+|SE>%a%2{{!dLLI+GkbGEvOtuoy;f^28n&?HHe9Oc zVO?o0_lG4)FI*d3U_9N)%O5{NL`snjoIODBTthsP1f(UtK}wCk`Eu$zo{&cxV_j%^3#*gKm`QC!e0SBa(vumPv0juQB9}`y}LPxEhafQsz zxS#Fo`-<=X;$A{8?`6Hx4IJ8XN44<}c?>}@k4M88Fo91tWF2LQ@m zF=rh6mss2KO*t&~r!J7{^?OZgYSNpf{Y+r9g9*8OS*O<)0Ko@0|SbKgjm3_kQNjEq3TV z3ACa{;@}zVg?NeQK#(27mDeQXG2teqw1A&1Mri- zh~X9Q>BeX}61bFRSjQKz`C7_#pMtGe7pyGi7SaJH8J7bqT3Gsz^Lm0ETd9Z3`uqNW zp literal 0 HcmV?d00001 diff --git a/apps/touchtimer/3_dark_timer_finished.png b/apps/touchtimer/3_dark_timer_finished.png new file mode 100644 index 0000000000000000000000000000000000000000..c6dd77b8288daf5b0d89f4224b716c0141a43460 GIT binary patch literal 2895 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 X`E_qF+2$_7A2MKvzhv{=igNq^PSH5E literal 0 HcmV?d00001 diff --git a/apps/touchtimer/3_light_timer_finished.png b/apps/touchtimer/3_light_timer_finished.png new file mode 100644 index 0000000000000000000000000000000000000000..18ee015e3e3208e76d93529362264363238f6ab6 GIT binary patch literal 2912 zcmcJR_fwPU7RTSb2?j#Bbdec5ZR0e)sSefH!F?7B1F5~%7^SOfrJc{`ki3(51#qrY-zv_x=({eAq7 zdv`i{T&!eBK=@NZXU!nJULs$0g}KU){ckjs^aXAjaFC(!`lj}A#5W#~hgY{R`e6a8L*N*{h=MBg0lO<=A57h@UW)=#B!c;T*d85X8JlJKMW>v(i}huB{0Y^?;t&`H-#*Efn14yRbjN0joCW|9rSd^@SgxrP%^+@*WWtjYSKVu=pdX228|xY zvhL$Hi~4_I#am2^hTJF?2A(xNqm6~^^Jp)ny!9N3Fz`|fMWhJ0moS79=MK&Kt|9V8?z=)j z-chC7zp{TTsRt3wQQzfEVd`+Y42Dhb>EsF>Kcv9`U`H>TA0R;8*dPJHh{dnEB=GEk z|Fw>VQ8R7guDbLmZ?^PtPHmnfcX{|_dijv|ZcJPMY^UI6wLVM|IdzvEJQ?HEb%fiu z+serc+1xHwEVg|tYpk}6y9+k&|lQHvfQ^f_4N``r>)HV>%brfmZNt=% zmSO{oOtX0nBm$AKVryC?cKX@#j+MaaGVMok!le>lg#eZ6OVQZ7>Epx02UEL!PouoQ zl!qCz7yxzYnIIrxA8#Nik8wpdU+iHtiQ{2eYByw;owV;GkN*b7E%)b>YeVvjWl)D`fzB{)Zmvf(SCMTy?Syvwfw>EO!$D5y2|z_);7FNmQ|5MI zis)y>_ljRDZ_mkzez5T!c9u$uSB)VA;DhVc*j>xkZ)(8tjnklg;!7~UqxfyR^!2%S zk)Jm_bK1fK@sbT}y@H9h#7i{yK5LtP`N4RGEb7Y(!=WJZ&ZYPu--clE_pch!ZcDG~ zOd>XOUx9KaYsKNeikVdV+Z;x}hRcp!FsP6j!eOs1O-Mc9X^6d{1u&njGd{hQI?1{- zfJl=5MTi)e(pvVzdNyN@2NPNUR6~FWD2z`C@X^%KdNm_)HOdWlyYTf54w)Xy!NRmW(*6x$={N*e^_)I`P9|1d{+mrLP+$}b8FKV`+^ zdO~@Ey}nAY#b$hgoWxpDNSgz)UD*5mA9tR(DY7%|rfZa7)lp|P5^iOD+BU1ck9Fb5 zeYc=}>hozxvc9$`gT5?aAkY9DxhhKJlr>;mV#22-yyuzO3~?#$)Oo9dr!+XJJ||^~ zUDh`Lv@Xc}OfE{>j7_|j({e2eX>*OD%3d&+S$r&;#l*OUVth^i6)9P%-y~i%POX{H zN$O~}Mo>#Z2|h1^GC?X1Fz}(e85*yFg;ZS1GYt4N6l_K*lmalSW7dfwX$(7PXgD$n zM4Ym;9=($rnyy=D#IRvLe-Jw-Ftvc04)X$O8}4Kz2z^)~-om7V)4{jN_ zK6=OW05>nc?J>z>NoI*^r$3ArNa=wsCtQ_*%#7;Qdn#FiYe|OX7bp%ehV_fxhOrxV zdZ63AwE9j?N9ACQWsrlqeYAHPdrW!T<)I%waZR2!_vWo+onLw?tHgp1+m5ne+;qCr zaMltrohzH0uj5-!w3#G87KSRQ=Zi&U)s7~A7qLPWAbW3q>asqtea=) zyBYr;oH`>wpB?tmmlx|UikjGskP|B5xY|Mbn2)a_@Ui!Nmdut*2i(*(gH4v&YXO>~ zzkjHvs#|6vZsgRDtYr!Ba;>yUsqgIi9$Cxt&rlOPm-2L`1{8&A{`?|-%D-dd>oIiM zVO>|)P74?=6yY+TUR$Tf{=ima+nN|p=s$-y8COUX!^5?sxz94Gn;QG#6*`R#DH6cW zIYPZ|=_6Auf4iAp9v!d|K51M!@M@?|#&7--rF(9{4W1$N*?Go{BpogRz|0` z(5};C$dxOvzl-(>N-QB(Moq~AWr=YpIiZMf<;OQlE}Izp+f(W~@54vV@8tm7pChq{ zXRV+Bf>LsP&>@>VcY`GNb_iO=*kG9{Pb@ SxW!+Y0Xyq+xGGEU#Qy?`Jv?>* literal 0 HcmV?d00001 diff --git a/apps/touchtimer/README.md b/apps/touchtimer/README.md index 99c755639..b54ed4b1d 100644 --- a/apps/touchtimer/README.md +++ b/apps/touchtimer/README.md @@ -1,3 +1,28 @@ # Touch Timer -Quickly and easily create a timer touch-only. +Quickly and easily create a timer with touch-only input. The time caan be easily set with a number pad. + +## How to + +- First input the timer time via the input buttons +- If you need to correct the time, press "<-". +- If the timer time is correct, press "OK". +- If you have accidentially pressed "OK", press "STOP" to go cancel. +- Press "START" to start the timer, if the time is correct. +- The timer will run the time until 0. Once it hits zero the watch will buzz for 1 second every 5 seconds for a total of 10 times, or until you press "STOP" + +## Screenshots + +### Light Theme + +![](0_light_timer_edit.png) +![](1_light_timer_ready.png) +![](2_light_timer_running.png) +![](3_light_timer_finished.png) + +### Dark Theme + +![](0_dark_timer_edit.png) +![](1_dark_timer_ready.png) +![](2_dark_timer_running.png) +![](3_dark_timer_finished.png) \ No newline at end of file From e40648ac3b93b2957b9fb14a6f3a34cfed70bfc6 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Mon, 27 Dec 2021 21:28:44 +0000 Subject: [PATCH 6/9] touchtimer: disable debug output --- apps/touchtimer/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index c0da034b7..ad8eb42ae 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -1,4 +1,4 @@ -var DEBUG = true; +var DEBUG = false; var main = () => { var button1 = new Button({ x1: 1, y1: 35, x2: 58, y2: 70 }, 1); From c21622015386328119c063e8c355030ad4c21b60 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Mon, 27 Dec 2021 21:30:51 +0000 Subject: [PATCH 7/9] touchtimer: fix typo --- apps.json | 2 +- apps/touchtimer/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d457198be..01ad2eac4 100644 --- a/apps.json +++ b/apps.json @@ -5068,7 +5068,7 @@ "name": "Touch Timer", "shortName": "Touch Timer", "version": "0.01", - "description": "Quickly and easily create a timer with touch-only input. The time caan be easily set with a number pad.", + "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.", "icon": "app.png", "tags": "tools", "supports": ["BANGLEJS2"], diff --git a/apps/touchtimer/README.md b/apps/touchtimer/README.md index b54ed4b1d..c8bc2c084 100644 --- a/apps/touchtimer/README.md +++ b/apps/touchtimer/README.md @@ -1,6 +1,6 @@ # Touch Timer -Quickly and easily create a timer with touch-only input. The time caan be easily set with a number pad. +Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad. ## How to From 3db8bd43c8e18e817992efa51c33e74f5caac054 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Sun, 2 Jan 2022 20:42:09 +0000 Subject: [PATCH 8/9] touchtimer: calculate time based on actual input, and only convert to 60m/60s afterwards. Clean up code. --- apps/touchtimer/README.md | 2 +- apps/touchtimer/app.js | 246 +++++++++++++++++++++++++------------- 2 files changed, 164 insertions(+), 84 deletions(-) diff --git a/apps/touchtimer/README.md b/apps/touchtimer/README.md index c8bc2c084..c97e69afc 100644 --- a/apps/touchtimer/README.md +++ b/apps/touchtimer/README.md @@ -9,7 +9,7 @@ Quickly and easily create a timer with touch-only input. The time can be easily - If the timer time is correct, press "OK". - If you have accidentially pressed "OK", press "STOP" to go cancel. - Press "START" to start the timer, if the time is correct. -- The timer will run the time until 0. Once it hits zero the watch will buzz for 1 second every 5 seconds for a total of 10 times, or until you press "STOP" +- The timer will run the time until 0. Once it hits zero the watch will buzz for 1 second every 5 seconds for a total of 5 times, or until you press "STOP" ## Screenshots diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index ad8eb42ae..c3200327d 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -53,38 +53,29 @@ var main = () => { var timerRunningButtons = [buttonStartPause, buttonStop]; - var timeStr = ""; + var timerEdit = new TimerEdit(); timerNumberButtons.forEach((numberButton) => { - numberButton.setOnClick((value) => { + numberButton.setOnClick((number) => { log("number button clicked"); - log(value); - log(timeStr); - if (value === 0 && timeStr.length === 0) { - return; - } - - if (timeStr.length <= 6) { - timeStr = timeStr + value; - } - log(timeStr); - drawTimer(timeStr); + log(number); + timerEdit.appendNumber(number); + timerEdit.draw(); }); }); buttonDelete.setOnClick(() => { log("delete button clicked"); - timeStr = timeStr.slice(0, -1); - log(timeStr); - drawTimer(timeStr); + timerEdit.removeNumber(); + timerEdit.draw(); }); buttonOK.setOnClick(() => { - if (timeStr.length === 0) { + if (timerEdit.timeStr.length === 0) { return; } g.clear(); - drawTimer(timeStr); + timerEdit.draw(); timerInputButtons.forEach((button) => button.disable()); @@ -96,8 +87,13 @@ var main = () => { var timerIntervalId = undefined; var buzzIntervalId = undefined; + var timerCountDown = undefined; buttonStartPause.setOnClick(() => { if (buttonStartPause.value === "PAUSE") { + if (timerCountDown) { + timerCountDown.pause(); + } + buttonStartPause.value = "START"; buttonStartPause.draw(); @@ -115,18 +111,19 @@ var main = () => { } if (buttonStartPause.value === "START") { + if (!timerCountDown) { + timerCountDown = new TimerCountDown(timerEdit.timeStr); + } else { + timerCountDown.unpause(); + } + buttonStartPause.value = "PAUSE"; buttonStartPause.draw(); - var time = timeStrToTime(timeStr); - timerIntervalId = setInterval(() => { - time = time - 1; + timerCountDown.draw(); - timeStr = timeToTimeStr(time); - drawTimer(timeStr); - - if (time === 0) { + if (timerCountDown.isFinished()) { buttonStartPause.value = "FINISHED!"; buttonStartPause.draw(); @@ -138,7 +135,7 @@ var main = () => { var buzzCount = 0; Bangle.buzz(1000, 1); buzzIntervalId = setInterval(() => { - if (buzzCount >= 10) { + if (buzzCount >= 5) { clearInterval(buzzIntervalId); buzzIntervalId = undefined; return; @@ -155,6 +152,10 @@ var main = () => { }); buttonStop.setOnClick(() => { + if (timerCountDown) { + timerCountDown = undefined; + } + if (timerIntervalId) { clearInterval(timerIntervalId); timerIntervalId = undefined; @@ -169,8 +170,8 @@ var main = () => { buttonStartPause.draw(); g.clear(); - timeStr = ""; - drawTimer(timeStr); + timerEdit.reset(); + timerEdit.draw(); timerRunningButtons.forEach((button) => button.disable()); @@ -182,7 +183,7 @@ var main = () => { // initalize g.clear(); - drawTimer(timeStr); + timerEdit.draw(); timerInputButtons.forEach((button) => { button.enable(); button.draw(); @@ -197,24 +198,6 @@ var log = (message) => { } }; -var drawTimer = (timeStr) => { - timeStr = timeStr.padStart(6, "0"); - var timeStrDisplay = - "" + - timeStr.slice(0, 2) + - "h " + - timeStr.slice(2, 4) + - "m " + - timeStr.slice(4, 6) + - "s"; - - g.clearRect(0, 0, 176, 34); - g.setColor(g.theme.fg); - g.setFontAlign(-1, -1); - g.setFont("Vector:26x40"); - g.drawString(timeStrDisplay, 2, 0); -}; - var touchHandlers = []; Bangle.on("touch", (_button, xy) => { @@ -312,50 +295,147 @@ class Button { } } -var timeToTimeStr = (time) => { - var hours = Math.floor(time / 3600); - time = time - hours * 3600; - var minutes = Math.floor(time / 60); - time = time - minutes * 60; - var seconds = time; - - if (hours === 0) { - hours = ""; - } else { - hours = hours.toString(); +class TimerEdit { + constructor() { + this.timeStr = ""; } - if (hours.length === 0) { - if (minutes === 0) { - minutes = ""; - } else { - minutes = minutes.toString(); + appendNumber(number) { + if (number === 0 && this.timeStr.length === 0) { + return; } - } else { - minutes = minutes.toString().padStart(2, "0"); - } - if (hours.length === 0 && minutes.length === 0) { - if (seconds === 0) { - seconds = ""; - } else { - seconds = seconds.toString(); + if (this.timeStr.length <= 6) { + this.timeStr = this.timeStr + number; } - } else { - seconds = seconds.toString().padStart(2, "0"); } - return hours + minutes + seconds; -}; + removeNumber() { + if (this.timeStr.length > 0) { + this.timeStr = this.timeStr.slice(0, -1); + } + } -var timeStrToTime = (timeStr) => { - timeStr = timeStr.padStart(6, "0"); - return ( - parseInt(timeStr.slice(0, 2), 10) * 3600 + - parseInt(timeStr.slice(2, 4), 10) * 60 + - parseInt(timeStr.slice(4, 6), 10) - ); -}; + reset() { + this.timeStr = ""; + } + + draw() { + log("drawing timer edit"); + var timeStrPadded = this.timeStr.padStart(6, "0"); + var timeStrDisplay = + "" + + timeStrPadded.slice(0, 2) + + "h " + + timeStrPadded.slice(2, 4) + + "m " + + timeStrPadded.slice(4, 6) + + "s"; + log(timeStrPadded); + log(timeStrDisplay); + + g.clearRect(0, 0, 176, 34); + g.setColor(g.theme.fg); + g.setFontAlign(-1, -1); + g.setFont("Vector:26x40"); + g.drawString(timeStrDisplay, 2, 0); + } +} + +class TimerCountDown { + constructor(timeStr) { + log("creating timer"); + this.timeStr = timeStr; + log(this.timeStr); + this.start = Math.floor(Date.now() / 1000); + log(this.start); + this.pausedTime = undefined; + } + + getAdjustedTime() { + var elapsedTime = Math.floor(Date.now() / 1000) - this.start; + + var timeStrPadded = this.timeStr.padStart(6, "0"); + var timeStrHours = parseInt(timeStrPadded.slice(0, 2), 10); + var timeStrMinutes = parseInt(timeStrPadded.slice(2, 4), 10); + var timeStrSeconds = parseInt(timeStrPadded.slice(4, 6), 10); + + var hours = timeStrHours; + var minutes = timeStrMinutes; + var seconds = timeStrSeconds - elapsedTime; + + if (seconds < 0) { + var neededMinutes = Math.ceil(Math.abs(seconds) / 60); + + seconds = seconds + neededMinutes * 60; + minutes = minutes - neededMinutes; + + if (minutes < 0) { + var neededHours = Math.ceil(Math.abs(minutes) / 60); + + minutes = minutes + neededHours * 60; + hours = hours - neededHours; + } + } + + if (hours < 0 || minutes < 0 || seconds < 0) { + hours = 0; + minutes = 0; + seconds = 0; + } + + return { hours: hours, minutes: minutes, seconds: seconds }; + } + + pause() { + this.pausedTime = Math.floor(Date.now() / 1000); + } + + unpause() { + if (this.pausedTime) { + this.start += Math.floor(Date.now() / 1000) - this.pausedTime; + } + + this.pausedTime = undefined; + } + + draw() { + log("drawing timer count down"); + var adjustedTime = this.getAdjustedTime(); + var hours = adjustedTime.hours; + var minutes = adjustedTime.minutes; + var seconds = adjustedTime.seconds; + + var timeStrDisplay = + "" + + hours.toString().padStart(2, "0") + + "h " + + minutes.toString().padStart(2, "0") + + "m " + + seconds.toString().padStart(2, "0") + + "s"; + log(timeStrDisplay); + + g.clearRect(0, 0, 176, 34); + g.setColor(g.theme.fg); + g.setFontAlign(-1, -1); + g.setFont("Vector:26x40"); + g.drawString(timeStrDisplay, 2, 0); + } + + isFinished() { + var adjustedTime = this.getAdjustedTime(); + var hours = adjustedTime.hours; + var minutes = adjustedTime.minutes; + var seconds = adjustedTime.seconds; + + if (hours <= 0 && minutes <= 0 && seconds <= 0) { + return true; + } else { + return false; + } + } +} // start main function From 3150fc7536b7b8bfa1aaa228a36aefb357bcfc96 Mon Sep 17 00:00:00 2001 From: crazysaem Date: Sun, 2 Jan 2022 21:01:49 +0000 Subject: [PATCH 9/9] touchtimer: add settings --- apps.json | 6 ++- apps/touchtimer/ChangeLog | 3 +- apps/touchtimer/README.md | 3 +- apps/touchtimer/app.js | 24 +++++++++--- apps/touchtimer/settings.js | 77 +++++++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 apps/touchtimer/settings.js diff --git a/apps.json b/apps.json index 01ad2eac4..846ab7efe 100644 --- a/apps.json +++ b/apps.json @@ -5067,7 +5067,7 @@ "id": "touchtimer", "name": "Touch Timer", "shortName": "Touch Timer", - "version": "0.01", + "version": "0.02", "description": "Quickly and easily create a timer with touch-only input. The time can be easily set with a number pad.", "icon": "app.png", "tags": "tools", @@ -5076,7 +5076,9 @@ "screenshots": [{"url":"0_light_timer_edit.png"},{"url":"1_light_timer_ready.png"},{"url":"2_light_timer_running.png"},{"url":"3_light_timer_finished.png"}], "storage": [ { "name": "touchtimer.app.js", "url": "app.js" }, + { "name":"touchtimer.settings.js", "url":"settings.js"}, { "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true } - ] + ], + "data": [{"name":"touchtimer.data.json"}] } ] diff --git a/apps/touchtimer/ChangeLog b/apps/touchtimer/ChangeLog index 193a476aa..01904c6ea 100644 --- a/apps/touchtimer/ChangeLog +++ b/apps/touchtimer/ChangeLog @@ -1 +1,2 @@ -0.01: Initial creation of the touch timer app \ No newline at end of file +0.01: Initial creation of the touch timer app +0.02: Add settings menu \ No newline at end of file diff --git a/apps/touchtimer/README.md b/apps/touchtimer/README.md index c97e69afc..39afba8e5 100644 --- a/apps/touchtimer/README.md +++ b/apps/touchtimer/README.md @@ -9,7 +9,8 @@ Quickly and easily create a timer with touch-only input. The time can be easily - If the timer time is correct, press "OK". - If you have accidentially pressed "OK", press "STOP" to go cancel. - Press "START" to start the timer, if the time is correct. -- The timer will run the time until 0. Once it hits zero the watch will buzz for 1 second every 5 seconds for a total of 5 times, or until you press "STOP" +- The timer will run the time until 0. Once it hits zero the watch will buzz for 1 second every 1 seconds for a total of 3 times, or until you press "STOP" +- -> The number of buzzes, the buzz duration, and the pause between buzzes is configurable in the settings app ## Screenshots diff --git a/apps/touchtimer/app.js b/apps/touchtimer/app.js index c3200327d..ffa1af80a 100644 --- a/apps/touchtimer/app.js +++ b/apps/touchtimer/app.js @@ -1,6 +1,9 @@ var DEBUG = false; +var FILE = "touchtimer.data.json"; var main = () => { + var settings = readSettings(); + var button1 = new Button({ x1: 1, y1: 35, x2: 58, y2: 70 }, 1); var button2 = new Button({ x1: 60, y1: 35, x2: 116, y2: 70 }, 2); var button3 = new Button({ x1: 118, y1: 35, x2: 174, y2: 70 }, 3); @@ -132,18 +135,18 @@ var main = () => { timerIntervalId = undefined; } - var buzzCount = 0; - Bangle.buzz(1000, 1); + var buzzCount = 1; + Bangle.buzz(settings.buzzDuration * 1000, 1); buzzIntervalId = setInterval(() => { - if (buzzCount >= 5) { + if (buzzCount >= settings.buzzCount) { clearInterval(buzzIntervalId); buzzIntervalId = undefined; return; } else { - Bangle.buzz(1000, 1); + Bangle.buzz(settings.buzzDuration * 1000, 1); buzzCount++; } - }, 5000); + }, settings.buzzDuration * 1000 + settings.pauseBetween * 1000); } }, 1000); @@ -437,6 +440,17 @@ class TimerCountDown { } } +var readSettings = () => { + log("reading settings"); + var settings = require("Storage").readJSON(FILE, 1) || { + buzzCount: 3, + buzzDuration: 1, + pauseBetween: 1, + }; + log(settings); + return settings; +}; + // start main function main(); diff --git a/apps/touchtimer/settings.js b/apps/touchtimer/settings.js new file mode 100644 index 000000000..885670f57 --- /dev/null +++ b/apps/touchtimer/settings.js @@ -0,0 +1,77 @@ +(function (back) { + var DEBUG = false; + var FILE = "touchtimer.data.json"; + + var settings = {}; + + var showMainMenu = () => { + log("Loading main menu"); + + E.showMenu({ + "": { title: "Touch Timer" }, + "< Back": () => back(), + "Buzz Count": { + value: settings.buzzCount, + min: 1, + max: 3, + step: 1, + onchange: (value) => { + settings.buzzCount = value; + writeSettings(settings); + }, + }, + "Buzz Duration": { + value: settings.buzzDuration, + min: 1, + max: 10, + step: 0.5, + format: (value) => value + "s", + onchange: (value) => { + settings.buzzDuration = value; + writeSettings(settings); + }, + }, + "Pause Between": { + value: settings.pauseBetween, + min: 1, + max: 5, + step: 1, + format: (value) => value + "s", + onchange: (value) => { + settings.pauseBetween = value; + writeSettings(settings); + }, + }, + }); + }; + + // lib functions + + var log = (message) => { + if (DEBUG) { + console.log(JSON.stringify(message)); + } + }; + + var readSettings = () => { + log("reading settings"); + var settings = require("Storage").readJSON(FILE, 1) || { + buzzCount: 3, + buzzDuration: 1, + pauseBetween: 1, + }; + log(settings); + return settings; + }; + + var writeSettings = (settings) => { + log("writing settings"); + log(settings); + require("Storage").writeJSON(FILE, settings); + }; + + // start main function + + settings = readSettings(); + showMainMenu(); +});