Merge pull request #1183 from crazysaem/touchtimer
New App: Touchtimer - Quickly create a timer with a number pad inputmaster
18
apps.json
|
@ -5102,6 +5102,24 @@
|
|||
{"name":"ltherm.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "touchtimer",
|
||||
"name": "Touch Timer",
|
||||
"shortName": "Touch Timer",
|
||||
"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",
|
||||
"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.settings.js", "url":"settings.js"},
|
||||
{ "name": "touchtimer.img", "url": "app-icon.js", "evaluate": true }
|
||||
],
|
||||
"data": [{"name":"touchtimer.data.json"}]
|
||||
},
|
||||
{
|
||||
"id": "teatimer",
|
||||
"name": "Tea Timer",
|
||||
|
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial creation of the touch timer app
|
||||
0.02: Add settings menu
|
|
@ -0,0 +1,29 @@
|
|||
# Touch Timer
|
||||
|
||||
Quickly and easily create a timer with touch-only input. The time can 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 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
|
||||
|
||||
### Light Theme
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Dark Theme
|
||||
|
||||

|
||||

|
||||

|
||||

|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkE/4A3mUQIAMRkYWQkBaFiQWQgMjn8zGYUDCxkxFA3zD4MfCxXygECMAURiReCDAM/IpUBFIJ2CAAIeB+ZJKBYI8BCwMBiABBDARSBC5EwFwMwEwUwh5FCEIJhJiEfGIIXC+IQBSwQeBNYR1Gn4xB+MDDYITBiEzFoIOCC4vwEAIxBAwQzBAoQtCBgaNEh4iEAwMwRQXxHgRnBLwsvFQJdCFoIGBl55DH4QAEEIK/BC4KjBC4RECiED+RnBXooxCn4uBKwPwgIiB+fxgQQCRwgeBLwRbBkAXBh5yCBwoACEAoVBC4fwJ4I+DC5EjJQQXDBYP/kJWDC4qmBBYYXFfIQXKiQvUL6AXGR5LzBR4YXIBAS/BC4UCeAQOFC4rvDN4LvCFYMgd4IXJmEABgMxC4bWBiADDC45EBZIRHBMYINCBQQXIIgIkB//wgIFDmBKBC5QNB+UDboU/kEzgCRBC5QTBNwUxLoZRDC5J5EmAqBkEAiYMCC5XzFIMRkECAgILDC5YYDAAUBIoQXNDAMhiMRkYJEC5oAKC7qKBACDfCK4IWRPwjqBkczAB0yGAcQGgYAOmByCfAYAP+MBC4QWR//yC4ciACMhC4YATC4T9BACUSLiQAdA="))
|
|
@ -0,0 +1,456 @@
|
|||
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);
|
||||
|
||||
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);
|
||||
|
||||
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 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 }, "<-");
|
||||
|
||||
var timerNumberButtons = [
|
||||
button1,
|
||||
button2,
|
||||
button3,
|
||||
button4,
|
||||
button5,
|
||||
button6,
|
||||
button7,
|
||||
button8,
|
||||
button9,
|
||||
button0,
|
||||
];
|
||||
|
||||
var timerInputButtons = [
|
||||
button1,
|
||||
button2,
|
||||
button3,
|
||||
button4,
|
||||
button5,
|
||||
button6,
|
||||
button7,
|
||||
button8,
|
||||
button9,
|
||||
buttonOK,
|
||||
button0,
|
||||
buttonDelete,
|
||||
];
|
||||
|
||||
var buttonStartPause = new Button(
|
||||
{ x1: 1, y1: 35, x2: 174, y2: 105 },
|
||||
"START"
|
||||
);
|
||||
var buttonStop = new Button({ x1: 1, y1: 107, x2: 174, y2: 174 }, "STOP");
|
||||
|
||||
var timerRunningButtons = [buttonStartPause, buttonStop];
|
||||
|
||||
var timerEdit = new TimerEdit();
|
||||
timerNumberButtons.forEach((numberButton) => {
|
||||
numberButton.setOnClick((number) => {
|
||||
log("number button clicked");
|
||||
log(number);
|
||||
timerEdit.appendNumber(number);
|
||||
timerEdit.draw();
|
||||
});
|
||||
});
|
||||
|
||||
buttonDelete.setOnClick(() => {
|
||||
log("delete button clicked");
|
||||
timerEdit.removeNumber();
|
||||
timerEdit.draw();
|
||||
});
|
||||
|
||||
buttonOK.setOnClick(() => {
|
||||
if (timerEdit.timeStr.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g.clear();
|
||||
timerEdit.draw();
|
||||
|
||||
timerInputButtons.forEach((button) => button.disable());
|
||||
|
||||
timerRunningButtons.forEach((button) => {
|
||||
button.enable();
|
||||
button.draw();
|
||||
});
|
||||
});
|
||||
|
||||
var timerIntervalId = undefined;
|
||||
var buzzIntervalId = undefined;
|
||||
var timerCountDown = undefined;
|
||||
buttonStartPause.setOnClick(() => {
|
||||
if (buttonStartPause.value === "PAUSE") {
|
||||
if (timerCountDown) {
|
||||
timerCountDown.pause();
|
||||
}
|
||||
|
||||
buttonStartPause.value = "START";
|
||||
buttonStartPause.draw();
|
||||
|
||||
if (timerIntervalId) {
|
||||
clearInterval(timerIntervalId);
|
||||
timerIntervalId = undefined;
|
||||
}
|
||||
|
||||
if (buzzIntervalId) {
|
||||
clearInterval(buzzIntervalId);
|
||||
buzzIntervalId = undefined;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonStartPause.value === "START") {
|
||||
if (!timerCountDown) {
|
||||
timerCountDown = new TimerCountDown(timerEdit.timeStr);
|
||||
} else {
|
||||
timerCountDown.unpause();
|
||||
}
|
||||
|
||||
buttonStartPause.value = "PAUSE";
|
||||
buttonStartPause.draw();
|
||||
|
||||
timerIntervalId = setInterval(() => {
|
||||
timerCountDown.draw();
|
||||
|
||||
if (timerCountDown.isFinished()) {
|
||||
buttonStartPause.value = "FINISHED!";
|
||||
buttonStartPause.draw();
|
||||
|
||||
if (timerIntervalId) {
|
||||
clearInterval(timerIntervalId);
|
||||
timerIntervalId = undefined;
|
||||
}
|
||||
|
||||
var buzzCount = 1;
|
||||
Bangle.buzz(settings.buzzDuration * 1000, 1);
|
||||
buzzIntervalId = setInterval(() => {
|
||||
if (buzzCount >= settings.buzzCount) {
|
||||
clearInterval(buzzIntervalId);
|
||||
buzzIntervalId = undefined;
|
||||
return;
|
||||
} else {
|
||||
Bangle.buzz(settings.buzzDuration * 1000, 1);
|
||||
buzzCount++;
|
||||
}
|
||||
}, settings.buzzDuration * 1000 + settings.pauseBetween * 1000);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
buttonStop.setOnClick(() => {
|
||||
if (timerCountDown) {
|
||||
timerCountDown = undefined;
|
||||
}
|
||||
|
||||
if (timerIntervalId) {
|
||||
clearInterval(timerIntervalId);
|
||||
timerIntervalId = undefined;
|
||||
}
|
||||
|
||||
if (buzzIntervalId) {
|
||||
clearInterval(buzzIntervalId);
|
||||
buzzIntervalId = undefined;
|
||||
}
|
||||
|
||||
buttonStartPause.value = "START";
|
||||
buttonStartPause.draw();
|
||||
|
||||
g.clear();
|
||||
timerEdit.reset();
|
||||
timerEdit.draw();
|
||||
|
||||
timerRunningButtons.forEach((button) => button.disable());
|
||||
|
||||
timerInputButtons.forEach((button) => {
|
||||
button.enable();
|
||||
button.draw();
|
||||
});
|
||||
});
|
||||
|
||||
// initalize
|
||||
g.clear();
|
||||
timerEdit.draw();
|
||||
timerInputButtons.forEach((button) => {
|
||||
button.enable();
|
||||
button.draw();
|
||||
});
|
||||
};
|
||||
|
||||
// lib functions
|
||||
|
||||
var log = (message) => {
|
||||
if (DEBUG) {
|
||||
console.log(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
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(x, y);
|
||||
});
|
||||
});
|
||||
|
||||
var BUTTON_BORDER_WITH = 2;
|
||||
|
||||
class Button {
|
||||
constructor(position, value) {
|
||||
this.position = position;
|
||||
this.value = value;
|
||||
|
||||
this.touchHandler = undefined;
|
||||
this.highlightTimeoutId = undefined;
|
||||
}
|
||||
|
||||
draw(highlight) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(
|
||||
this.position.x1,
|
||||
this.position.y1,
|
||||
this.position.x2,
|
||||
this.position.y2
|
||||
);
|
||||
|
||||
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,
|
||||
this.position.x2 - BUTTON_BORDER_WITH,
|
||||
this.position.y2 - BUTTON_BORDER_WITH
|
||||
);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("Vector", 35);
|
||||
g.drawString(
|
||||
this.value,
|
||||
this.position.x1 + (this.position.x2 - this.position.x1) / 2 + 2,
|
||||
this.position.y1 + (this.position.y2 - this.position.y1) / 2 + 2
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TimerEdit {
|
||||
constructor() {
|
||||
this.timeStr = "";
|
||||
}
|
||||
|
||||
appendNumber(number) {
|
||||
if (number === 0 && this.timeStr.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.timeStr.length <= 6) {
|
||||
this.timeStr = this.timeStr + number;
|
||||
}
|
||||
}
|
||||
|
||||
removeNumber() {
|
||||
if (this.timeStr.length > 0) {
|
||||
this.timeStr = this.timeStr.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -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();
|
||||
});
|