From 59b8cd31217a56b7502eed186cfcf5207e95d697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20K=C3=B6ll?= Date: Sat, 12 Jul 2025 22:10:12 +0200 Subject: [PATCH] [teatimer] - 0.07 - simplify ui - enable settings - adjustable time steps - remove stuttering timer - refactor code --- apps/teatimer/ChangeLog | 1 + apps/teatimer/README.md | 25 +- apps/teatimer/TeatimerHelp.jpg | Bin 5125 -> 0 bytes apps/teatimer/TeatimerHelp.png | Bin 0 -> 3043 bytes apps/teatimer/TeatimerPause.png | Bin 0 -> 2299 bytes apps/teatimer/TeatimerRun.jpg | Bin 3981 -> 0 bytes apps/teatimer/TeatimerRun.png | Bin 0 -> 2202 bytes apps/teatimer/TeatimerStart.jpg | Bin 4245 -> 0 bytes apps/teatimer/TeatimerStart.png | Bin 0 -> 2507 bytes apps/teatimer/TeatimerUp.jpg | Bin 3737 -> 0 bytes apps/teatimer/TeatimerUp.png | Bin 0 -> 2289 bytes apps/teatimer/app.js | 388 +++++++++++++++----------------- apps/teatimer/metadata.json | 15 +- apps/teatimer/settings.js | 47 ++++ 14 files changed, 258 insertions(+), 218 deletions(-) delete mode 100644 apps/teatimer/TeatimerHelp.jpg create mode 100644 apps/teatimer/TeatimerHelp.png create mode 100644 apps/teatimer/TeatimerPause.png delete mode 100644 apps/teatimer/TeatimerRun.jpg create mode 100644 apps/teatimer/TeatimerRun.png delete mode 100644 apps/teatimer/TeatimerStart.jpg create mode 100644 apps/teatimer/TeatimerStart.png delete mode 100644 apps/teatimer/TeatimerUp.jpg create mode 100644 apps/teatimer/TeatimerUp.png create mode 100644 apps/teatimer/settings.js diff --git a/apps/teatimer/ChangeLog b/apps/teatimer/ChangeLog index fcdcda875..cf68c1d69 100644 --- a/apps/teatimer/ChangeLog +++ b/apps/teatimer/ChangeLog @@ -4,3 +4,4 @@ 0.04: Get time zone from settings for showing the clock 0.05: Minor code improvements 0.06: Adjust format of title, save counter before leaving help screen +0.07: Refactor code, fix stuttering timer, add settings menu diff --git a/apps/teatimer/README.md b/apps/teatimer/README.md index b7ece6022..b7c628420 100644 --- a/apps/teatimer/README.md +++ b/apps/teatimer/README.md @@ -3,7 +3,7 @@ A simple timer. You can easily set up the time. The initial time is 2:30 On the first screen, you can -- tap to get help +- double tap to get help - swipe up/down to change the timer by +/- one minute - swipe left/right to change the time by +/- 15 seconds - press Btn1 to start @@ -12,24 +12,31 @@ Press Btn1 again to stop the timer - when time is up, your Bangle will buzz for 15 seconds - and it will count up to 60 seconds and stop after that -## Images -_1. Startscreen_ +The time changes can be adjusted in the settings menu. -![](TeatimerStart.jpg) +## Images +_1. Start screen_ + +![](TeatimerStart.png) Current time is displayed below the Title. Initial time is 2:30. _2. Help Screen_ -![](TeatimerHelp.jpg) +![](TeatimerHelp.png) _3. Tea Timer running_ -![](TeatimerRun.jpg) -Remainig time is shown in big font size. Above the initial time is shown. +![](TeatimerRun.png) +Remainig time is shown in big font size. -_4. When time is up_ +_4. Pause Timer -![](TeatimerUp.jpg) +![](TeatimerPause.png) +While the timer is running, you can pause and unpause it by pressing BTN1. + +_5. When time is up_ + +![](TeatimerUp.png) When time is up, the watch will buzz for 15 seconds. It will count up to 60 seconds. ## Requests diff --git a/apps/teatimer/TeatimerHelp.jpg b/apps/teatimer/TeatimerHelp.jpg deleted file mode 100644 index e22960c668b2e7e1b783163274d6c80cc3c90416..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5125 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 Z!0XbiQ=eYi86xsdy&oXAAyW26{|hcX0fqno diff --git a/apps/teatimer/TeatimerHelp.png b/apps/teatimer/TeatimerHelp.png new file mode 100644 index 0000000000000000000000000000000000000000..f26e290fbffd37d9f05d207ae0c047b1a76b0e8e GIT binary patch literal 3043 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 R|9IYk^R_NF)mHe_e*#F^yXOD^ literal 0 HcmV?d00001 diff --git a/apps/teatimer/TeatimerPause.png b/apps/teatimer/TeatimerPause.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6738e5426f1841d36db931bfaef132b9017d01 GIT binary patch literal 2299 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 LlRLfv7yRQN=F{yD literal 0 HcmV?d00001 diff --git a/apps/teatimer/TeatimerStart.jpg b/apps/teatimer/TeatimerStart.jpg deleted file mode 100644 index 4fa8f2fc49b04d4a40328b8cd227a60c958e2325..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4245 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 Mj|KVpyVpkl3!DOAqyPW_ diff --git a/apps/teatimer/TeatimerStart.png b/apps/teatimer/TeatimerStart.png new file mode 100644 index 0000000000000000000000000000000000000000..2793c9bae81a681779d360dbcfdfe3b75f580c9f GIT binary patch literal 2507 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 literal 0 HcmV?d00001 diff --git a/apps/teatimer/TeatimerUp.jpg b/apps/teatimer/TeatimerUp.jpg deleted file mode 100644 index 80b8c3c8abdd8bce55727722ab9bcf782a5f6b65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3737 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 C|0R3? literal 0 HcmV?d00001 diff --git a/apps/teatimer/app.js b/apps/teatimer/app.js index a22000342..7e2e3dbb7 100644 --- a/apps/teatimer/app.js +++ b/apps/teatimer/app.js @@ -1,237 +1,217 @@ -// Tea Timer -// Button press stops timer, next press restarts timer -let drag; -var counter = 0; -var counterStart = 150; // 150 seconds -var counterInterval; -const states = { - init: 1, // unused - help: 2, // show help text - start: 4, // show/change initial counter - count: 8, // count down - countUp: 16, // count up after timer finished - stop: 32 // timer stopped +const FILE = "teatimer.json"; +const DEFAULTS = { + timerDuration: 150, + bigJump: 60, + smallJump: 15, + finishBuzzDuration: 1500, + overtimeBuzzDuration: 100, + overtimeBuzzLimit: 60, + overtimeBuzzSeconds: 15 }; -var state = states.start; -let setting = require("Storage").readJSON("setting.json",1); -E.setTimeZone(setting.timezone); -// Title showing current time -function appTitle() { - return "Tea Timer\n" + currentTime(); +// Enum for states +const STATES = { + INIT: "init", + RUNNING: "running", + PAUSED: "paused", + FINISHED: "finished", + OVERTIME: "overtime" +}; + +let savedSettings = require("Storage").readJSON(FILE, 1) || {}; +let settings = Object.assign({}, DEFAULTS, savedSettings); + +let state = STATES.INIT; +let showHelp = false; + +let startTime = 0; +let remaining = settings.timerDuration; +let target = 0; + +let drag = null; +let dragAdjusted = false; +let lastTapTime = 0; + +// === Helpers === +function formatTime(s) { + let m = Math.floor(s / 60); + let sec = (s % 60).toString().padStart(2, '0'); + return `${m}:${sec}`; } -function currentTime() { - let min = Date().getMinutes(); - if (min < 10) min = "0" + min; - return Date().getHours() + ":" + min; +function getTimeStr() { + let d = new Date(); + return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`; } -function timeFormated(sec) { - let min = Math.floor(sec / 60); - sec = sec % 60; - if (sec < 10) sec = "0" + sec; - return min + ":" + sec; +function isState(s) { + return state === s; } -// initialize timer and show timer value => state: start -function initTimer() { - counter = counterStart; - setState(states.start); - showCounter(true); +function setState(s) { + state = s; } -// timer value (counter) can be changed in state start -function changeCounter(diff) { - if (state == states.start) { - if (counter + diff > 0) { - counter = counter + diff; - showCounter(true); - } +// === UI Drawing === +function drawUI() { + g.reset(); + g.setBgColor(g.theme.bg).clear(); + g.setColor(g.theme.fg); + let cx = g.getWidth() / 2; + + // Time (top right) + g.setFont("6x8", 2); + g.setFontAlign(1, 0); + g.drawString(getTimeStr(), g.getWidth() - 4, 10); + + // Help text + if (showHelp) { + g.setFontAlign(0, 0); + g.setFont("Vector", 15); + g.drawString( + `Swipe up/down: ±${settings.bigJump}s\nSwipe left/right: ±${settings.smallJump}s\n\nBTN1: Start/Pause\nDouble Tap: Hide Help`, + cx, 80 + ); + return; + } + + // Title + g.setFont("Vector", 20); + g.setFontAlign(0, 0); + let label = (isState(STATES.OVERTIME)) ? "Time's Up!" : "Tea Timer"; + g.drawString(label, cx, 40); + + // Time remaining / overtime + g.setFont("Vector", 60); + g.setColor(isState(STATES.OVERTIME) ? "#f00" : g.theme.fg); + g.drawString(formatTime(remaining), cx, 100); + + // Bottom state text + g.setFontAlign(0, 0); + if (isState(STATES.PAUSED)) { + g.setFont("6x8", 2); + g.drawString("paused", cx, g.getHeight() - 20); + } else if (!isState(STATES.RUNNING) && !isState(STATES.OVERTIME)) { + g.setFont("Vector", 13); + g.drawString("double tap for help", cx, g.getHeight() - 20); } } -// start or restart timer => state: count +// === Timer Logic === function startTimer() { - counterStart = counter; - setState(states.count); - countDown(); - if (!counterInterval) - counterInterval = setInterval(countDown, 1000); + setState(STATES.RUNNING); + startTime = Date.now(); + target = startTime + remaining * 1000; } -/* show current counter value at start and while count down - Show - - Title with current time - - initial timer value - - remaining time - - hint for help in state start -*/ -function showCounter(withHint) { - g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier - E.showMessage("", appTitle()); - g.reset().setFontAlign(0,0); // center font - // draw the current counter value - g.setBgColor(-1).setColor(0,0,1); // blue - g.setFont("Vector",20); // vector font, 20px - g.drawString("Timer: " + timeFormated(counterStart),80,55); - g.setFont("Vector",60); // vector font, 60px - g.drawString(timeFormated(counter),83,100); - if (withHint) { - g.setFont("Vector",20); // vector font, 80px - g.drawString("Tap for help",80,150); +function pauseTimer() { + if (isState(STATES.RUNNING)) { + remaining = Math.max(0, Math.ceil((target - Date.now()) / 1000)); + setState(STATES.PAUSED); } } -// count down and update every second -// when time is up, start counting up -function countDown() { - counter--; - // Out of time - if (counter<=0) { - outOfTime(); - countUp(); - counterInterval = setInterval(countUp, 1000); - return; +function resumeTimer() { + if (isState(STATES.PAUSED)) { + startTime = Date.now(); + target = startTime + remaining * 1000; + setState(STATES.RUNNING); } - showCounter(false); } -// -function outOfTime() { - E.showMessage("Time is up!",appTitle()); - setState(states.countUp); - resetTimer(); - Bangle.buzz(); - Bangle.buzz(); -} - -/* this counts up (one minute), after time is up - Show - - Title with current time - - initial timer value - - "Time is up!" - - time since timer finished -*/ -function countUp() { - // buzz for 15 seconds - counter++; - if (counter <=15) { - Bangle.buzz(); - } - // stop counting up after 60 seconds - if (counter > 60) { - outOfTime(); - return; - } - g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier - E.showMessage("", appTitle()); - g.reset().setFontAlign(0,0); // center font - g.setBgColor(-1).setColor(0,0,1); // blue - g.setFont("Vector",20); // vector font, 20px - g.drawString("Timer: " + timeFormated(counterStart),80,55); - g.setFont("Vector",30); // vector font, 80px - g.setBgColor(-1).setColor(1,0,0); // red - g.drawString("Time is up!",85,85); - g.setFont("Vector",40); // vector font, 80px - // draw the current counter value - g.drawString(timeFormated(counter),80,130); -} - -// reset when interupted by user oder 60 seconds after timer finished function resetTimer() { - clearInterval(); - counterInterval = undefined; + setState(STATES.INIT); + remaining = settings.timerDuration; } -// timer is stopped by user => state: stop -function stopTimer() { - resetTimer(); - E.showMessage("Timer stopped!", appTitle()); - setState(states.stop); -} - -// timer is stopped by user while counting up => state: start -function stopTimer2() { - resetTimer(); - initTimer(); -} - - -function setState(st) { - state = st; -} - -function buttonPressed() { - switch(state) { - case states.init: - initTimer(); - break; - case states.help: - initTimer(); - break; - case states.start: - startTimer(); - break; - case states.count: - stopTimer(); - break; - case states.countUp: - stopTimer2(); - break; - case states.stop: - initTimer(); - break; - default: - initTimer(); - break; - } -} - -/* Change initial counter value by swiping - swipe up: +1 minute - swipe down: -1 minute - swipe right: +15 seconds - swipe left: -15 seconds */ -function initDragEvents() { - Bangle.on("drag", e => { - if (state == states.start) { - if (!drag) { // start dragging - drag = {x: e.x, y: e.y}; - } else if (!e.b) { // released - const dx = e.x-drag.x, dy = e.y-drag.y; - drag = null; - if (Math.abs(dx)>Math.abs(dy)+10) { - // horizontal - changeCounter(dx>0 ? 15 : -15); - } else if (Math.abs(dy)>Math.abs(dx)+10) { - // vertical - changeCounter(dy>0 ? -60 : 60); - } +function tick() { + if (isState(STATES.RUNNING)) { + remaining -= 1; + if (remaining <= 0) { + remaining = 0; + setState(STATES.OVERTIME); + startTime = Date.now(); + remaining = 0; // Start overtime count-up from 0 + Bangle.buzz(settings.finishBuzzDuration); + } + } else if (isState(STATES.OVERTIME)) { + remaining += 1; + if (remaining <= settings.overtimeBuzzSeconds) { + Bangle.buzz(settings.overtimeBuzzDuration, 0.3); + } + if (remaining >= settings.overtimeBuzzLimit) { + resetTimer(); // Stop overtime after max duration } } -}); + drawUI(); } -// show help text while in start state (see initDragEvents()) -function showHelp() { - if (state == states.start) { - state = states.help; - g.setBgColor(g.theme.bg); - g.setColor(g.theme.fg); - E.showMessage("Swipe up/down\n+/- one minute\n\nSwipe left/right\n+/- 15 seconds\n\nPress Btn1 to start","Tea timer help"); +// === UI Controls === +function toggleTimer() { + if (showHelp) { + showHelp = false; + } else if (isState(STATES.OVERTIME)) { + resetTimer(); + } else if (isState(STATES.INIT)) { + startTimer(); + } else if (isState(STATES.PAUSED)) { + resumeTimer(); + } else if (isState(STATES.RUNNING)) { + pauseTimer(); } - // return to start - else if (state == states.help) { - counterStart = counter; - initTimer(); + + drawUI(); +} + +function handleDoubleTap() { + if (isState(STATES.INIT)) { + let now = Date.now(); + if (now - lastTapTime < 400) { + showHelp = !showHelp; + drawUI(); + } + lastTapTime = now; } } -// drag events in start state (to change counter value) -initDragEvents(); -// Show help test in start state -Bangle.on('touch', function(button, xy) { showHelp(); }); -// event handling for button1 -setWatch(buttonPressed, BTN1, {repeat: true}); -initTimer(); \ No newline at end of file +function adjustTimer(diff) { + if (isState(STATES.INIT)) { + remaining = Math.max(5, remaining + diff); + settings.timerDuration = remaining; + drawUI(); + } +} + +function handleDrag(e) { + if (isState(STATES.INIT) && !showHelp) { + if (e.b) { + if (!drag) { + drag = { x: e.x, y: e.y }; + dragAdjusted = false; + } else if (!dragAdjusted) { + let dx = e.x - drag.x; + let dy = e.y - drag.y; + + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > settings.smallJump) { + adjustTimer(dx > 0 ? settings.smallJump : -settings.smallJump); + dragAdjusted = true; + } else if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > settings.bigJump) { + adjustTimer(dy > 0 ? -settings.bigJump : settings.bigJump); + dragAdjusted = true; + } + } + } else { + drag = null; + dragAdjusted = false; + } + } +} + +// === Init App === +setWatch(toggleTimer, BTN1, { repeat: true }); +Bangle.on("drag", handleDrag); +Bangle.on("touch", handleDoubleTap); + +resetTimer(); +drawUI(); +setInterval(tick, 1000); diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json index 4d4ddd9e4..571053077 100644 --- a/apps/teatimer/metadata.json +++ b/apps/teatimer/metadata.json @@ -1,21 +1,26 @@ { "id": "teatimer", "name": "Tea Timer", - "version": "0.06", + "version": "0.07", "description": "A simple timer. You can easily set up the time.", "icon": "teatimer.png", "type": "app", "tags": "tool", "supports": ["BANGLEJS2"], "readme": "README.md", + "data": [ + { "name": "teatimer.json" } + ], "storage": [ {"name":"teatimer.app.js","url":"app.js"}, + {"name": "teatimer.settings.js", "url": "settings.js" }, {"name":"teatimer.img","url":"app-icon.js","evaluate":true} ], "screenshots": [ - {"url":"TeatimerStart.jpg"}, - {"url":"TeatimerHelp.jpg"}, - {"url":"TeatimerRun.jpg"}, - {"url":"TeatimerUp.jpg"} + {"url":"TeatimerStart.png"}, + {"url":"TeatimerHelp.png"}, + {"url":"TeatimerRun.png"}, + {"url":"TeatimerPause.png"}, + {"url":"TeatimerUp.png"} ] } diff --git a/apps/teatimer/settings.js b/apps/teatimer/settings.js new file mode 100644 index 000000000..8ed780344 --- /dev/null +++ b/apps/teatimer/settings.js @@ -0,0 +1,47 @@ +(function(back) { + const FILE = "teatimer.json"; + const DEFAULTS = { + timerDuration: 150, // Initial timer duration in seconds + bigJump: 60, // Jump for vertical swipes + smallJump: 15 // Jump for horizontal swipes + }; + + let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS; + + function saveSettings() { + require("Storage").writeJSON(FILE, settings); + } + + function showSettingsMenu() { + E.showMenu({ + '': { title: 'Tea Timer Settings' }, + '< Back': back, + 'Default Duration (sec)': { + value: settings.timerDuration, + min: 5, max: 900, step: 5, + onchange: v => { + settings.timerDuration = v; + saveSettings(); + } + }, + 'Swipe Up/Down (sec)': { + value: settings.bigJump, + min: 5, max: 300, step: 5, + onchange: v => { + settings.bigJump = v; + saveSettings(); + } + }, + 'Swipe Left/Right (sec)': { + value: settings.smallJump, + min: 5, max: 60, step: 5, + onchange: v => { + settings.smallJump = v; + saveSettings(); + } + } + }); + } + + showSettingsMenu(); +})