diff --git a/apps.json b/apps.json index a7517fb27..84d465376 100644 --- a/apps.json +++ b/apps.json @@ -2855,5 +2855,17 @@ "storage": [ {"name":"widhrt.wid.js","url":"widget.js"} ] +}, +{ "id": "countdowntimer", + "name" : "Countdown Timer", + "icon": "countdowntimer.png", + "version": "0.01", + "description": "A simple countdown timer with a focus on usability", + "tags": "timer, tool", + "readme": "README.md", + "storage": [ + {"name": "countdowntimer.app.js", "url": "countdowntimer.js"}, + {"name": "countdowntimer.img", "url": "countdowntimer-icon.js", "evaluate": true} + ] } ] diff --git a/apps/countdowntimer/ChangeLog b/apps/countdowntimer/ChangeLog new file mode 100644 index 000000000..624f1b0fb --- /dev/null +++ b/apps/countdowntimer/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version \ No newline at end of file diff --git a/apps/countdowntimer/README.md b/apps/countdowntimer/README.md new file mode 100644 index 000000000..3ad0eea84 --- /dev/null +++ b/apps/countdowntimer/README.md @@ -0,0 +1,12 @@ +# countdown-timer + +A basic bangle.js timer with a focus on usability. + +* Start or Pause the timer with BTN1 +* Reset the timer with BTN2 +* Exit the application with BTN3 +* Touch the right side of the screen to increase the time amount by 1 second +* Touch the left side of the scren to decrease the time amount by 1 second +* Touching and holding the screen will increase or decrease the time amount by 60 seconds at a time. + +Icons made by [Freepik](https://www.freepik.com) from [Flaticon](https://www.flaticon.com/). diff --git a/apps/countdowntimer/countdowntimer-icon.js b/apps/countdowntimer/countdowntimer-icon.js new file mode 100644 index 000000000..bf5972683 --- /dev/null +++ b/apps/countdowntimer/countdowntimer-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgMJhvd7tEogkSC4lAC6xWUgUgNysiC6hGBL58BiMQLoYXDMJkRAAIWEC4gYJFwIABCwgXFDBAWCpoXLMYwuCigUD6czmUSmQYKC4QuD6f//8xiUzmckJJIuECwQXEDAgXGFwc/C4XykYXCmZIJCwVPCwQABCwczmgwHXYXUCwgXFGBAXC74XLGAQXELowXIVgYXF6QWF+Z3EJAhGFUganHJAoXFRooXLMAQXCLwwXHMAQXUMAQXCRwQWFC9HSiMvC40iCocxiKoEC4cTC4sRiLBDC5MiF4wXFmQXHL4/zkRHEO6H/OwwXFX5IXfd5HfC5s0C4/TC5skRwZ4LLxAXHMAxeIC4hIJLxYXEJAxGMJAgwFFw4XGGAZhD+UjLoxGEC4owDmMSIoouGJAgYBGIIXDCwYuGGAoABIQMiaIQuKDA/dCoguJJIwXHCxQYGCyIYFCyRjELZYA=")) \ No newline at end of file diff --git a/apps/countdowntimer/countdowntimer.js b/apps/countdowntimer/countdowntimer.js new file mode 100644 index 000000000..b57372864 --- /dev/null +++ b/apps/countdowntimer/countdowntimer.js @@ -0,0 +1,232 @@ +const heatshrinkDecompress = require("heatshrink").decompress; + +const playIcon = heatshrinkDecompress(atob("jEYwhC/gFwBZV3BhV3u4LLBhILCEpALCBhALDu9gBaojKHZZrVQZSbLAG4A=")); +const pauseIcon = heatshrinkDecompress(atob("jEYwhC/xGIAYoL/Bf4LfAHA=")); +const resetIcon = heatshrinkDecompress(atob("jEYwg30h3u93gAgIKHBgXuBYgIBoEEBoQWFAgQMCBYgrBE4giEBYYjGAgY+DBY4AHBZlABZQ7DLIpTFAo5ZJLYYDFTZKzLAGQA==")); +const closeIcon = heatshrinkDecompress(atob("jEYwhC/4AEDhgKEhnMAofMCIgGECAoHFCwwIDCw4YDCxAYCCxALMEZY7KKZZrKQZibKAHIA=")); + +const timerState = { + IDLE: 0, + RUNNING: 1 +}; + +let currentState = timerState.IDLE; +let remainingSeconds = 0; +let countdownInterval = null; +let increasingInterval = null; +let decreasingInterval = null; +let isDecreasingRemainingSeconds = false; +let isIncreasingRemainingSeconds = false; + +function main() { + g.clear(); + g.setFont("Vector", 40); + g.setFontAlign(0, 0); + + registerInputHandlers(); + + draw(); +} + +function registerInputHandlers() { + setWatch(onPrimaryButtonPressed, BTN1, { repeat: true }); + setWatch(onResetButtonPressed, BTN2, { repeat: true }); + setWatch(onExitButtonPressed, BTN3, { repeat: true }); + setWatch(onDecreaseRemainingSecondsPressed, BTN4, { repeat: true, edge: "rising" }); + setWatch(onIncreaseRemainingSecondsPressed, BTN5, { repeat: true, edge: "rising" }); + setWatch(onDecreaseRemainingSecondsReleased, BTN4, { repeat: true, edge: "falling" }); + setWatch(onIncreaseRemainingSecondsReleased, BTN5, { repeat: true, edge: "falling" }); +} + +function draw() { + g.clearRect(200, 0, 240, 240); + g.clearRect(0, 0, 240, 80); + + drawRemainingSecondsPanel(); + + g.drawImage(resetIcon, 216, 108); + g.drawImage(closeIcon, 216, 188); + + if (currentState == timerState.IDLE) { + g.drawImage(playIcon, 216, 28); + } else { + g.drawImage(pauseIcon, 216, 28); + } + + g.flip(); +} + +function drawRemainingSecondsPanel() { + g.clearRect(0, 100, 200, 140); + g.drawString(formatRemainingSeconds(), 105, 120); + + if (currentState == timerState.IDLE) { + drawSubtractRemainingSeconds(); + drawIncreaseRemainingSeconds(); + } else { + g.setColor(0.4, 0.4, 0.4); + drawSubtractRemainingSeconds(); + drawIncreaseRemainingSeconds(); + g.setColor(-1); + } +} + +function drawSubtractRemainingSeconds() { + if (isDecreasingRemainingSeconds) { + drawFilledCircle(22, 117, 15); + } + + g.drawString("-", 25, 120); +} + +function drawIncreaseRemainingSeconds() { + if (isIncreasingRemainingSeconds) { + drawFilledCircle(182, 117, 15); + } + + g.drawString("+", 185, 120); +} + +function drawFilledCircle(x, y, radians) { + g.setColor(0.1, 0.37, 0.87); + g.fillCircle(x, y, radians); + g.setColor(-1); +} + +function formatRemainingSeconds() { + const minutes = Math.floor(remainingSeconds / 60); + const minutesTens = Math.floor(minutes / 10); + const minutesUnits = minutes % 10; + + const seconds = remainingSeconds % 60; + const secondsTens = Math.floor(seconds / 10); + const secondsUnits = seconds % 10; + + return `${minutesTens}${minutesUnits}:${secondsTens}${secondsUnits}`; +} + +function onPrimaryButtonPressed() { + if (isIncreasingRemainingSeconds || isDecreasingRemainingSeconds) return; + + if (currentState == timerState.IDLE) { + if (remainingSeconds == 0) return; + currentState = timerState.RUNNING; + beginCountdown(); + draw(); + } else { + currentState = timerState.IDLE; + stopCountdown(); + draw(); + } +} + +function beginCountdown() { + countdownInterval = setInterval(countdown, 1000); +} + +function countdown() { + --remainingSeconds; + + if (remainingSeconds <= 0) { + remainingSeconds = 0; + stopCountdown(); + } + + drawRemainingSecondsPanel(); + + if (remainingSeconds <= 0) { + drawStopMessage(); + } +} + +function drawStopMessage() { + draw(); + Bangle.buzz(800); + g.setFont("Vector", 30); + g.setColor(1.0, 0.91, 0); + g.drawString("Time's Up!", 105, 40); + g.setColor(-1); + g.setFont("Vector", 40); +} + +function stopCountdown() { + clearInterval(countdownInterval); + countdownInterval = null; + currentState = timerState.IDLE; +} + +function onResetButtonPressed() { + currentState = timerState.IDLE; + remainingSeconds = 0; + draw(); +} + +function onExitButtonPressed() { + Bangle.showLauncher(); +} + +function onIncreaseRemainingSecondsPressed() { + if (currentState == timerState.RUNNING) return; + incremementRemainingSeconds(); + + increasingInterval = setInterval(() => { + remainingSeconds += 60; + + if (remainingSeconds >= 5999) { + remainingSeconds = 5999; + } + + drawRemainingSecondsPanel(); + }, 250); + + isIncreasingRemainingSeconds = true; + + drawRemainingSecondsPanel(); +} + +function incremementRemainingSeconds() { + if (remainingSeconds >= 5999) return; + ++remainingSeconds; +} + +function onIncreaseRemainingSecondsReleased() { + if (currentState == timerState.RUNNING) return; + clearInterval(increasingInterval); + isIncreasingRemainingSeconds = false; + drawRemainingSecondsPanel(); +} + +function onDecreaseRemainingSecondsPressed() { + if (currentState == timerState.RUNNING) return; + decreaseRemainingSeconds(); + + decreasingInterval = setInterval(() => { + remainingSeconds -= 60; + + if (remainingSeconds < 0) { + remainingSeconds = 0; + } + + drawRemainingSecondsPanel(); + }, 250); + + isDecreasingRemainingSeconds = true; + + drawRemainingSecondsPanel(); +} + +function decreaseRemainingSeconds() { + if (remainingSeconds <= 0) return; + --remainingSeconds; +} + +function onDecreaseRemainingSecondsReleased() { + if (currentState == timerState.RUNNING) return; + + clearInterval(decreasingInterval); + + isDecreasingRemainingSeconds = false; + draw(); +} + +main(); \ No newline at end of file diff --git a/apps/countdowntimer/countdowntimer.png b/apps/countdowntimer/countdowntimer.png new file mode 100644 index 000000000..fe59fab3b Binary files /dev/null and b/apps/countdowntimer/countdowntimer.png differ