Merge branch 'espruino:master' into StoragwAnalyser

master
RKBoss6 2025-07-22 12:01:11 -04:00 committed by GitHub
commit 234680e7d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 358 additions and 251 deletions

View File

@ -5,7 +5,7 @@
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clkinfo",
"tags": "clkinfo",
"tags": "clkinfo,clock",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"}

View File

@ -0,0 +1 @@
0.01: New App!

BIN
apps/clkinfodist/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,22 @@
(function() {
let strideLength = (require("Storage").readJSON("myprofile.json",1)||{}).strideLength ?? 0.79,
lastSteps = 0;
function stepUpdateHandler() { distance.emit("redraw"); }
var distance = {
name : "Distance",
get : () => { let v = (Bangle.getHealthStatus("day").steps - lastSteps)*strideLength; return {
text : require("locale").distance(v,1),
img : atob("GBiBAAMAAAeAAA/AAA/AAA/gAA/gwAfh4AfD4APD4AOH4AAH4ADj4AHjwAHhwADgAAACAAAHgAAPAAAHAAgCEBgAGD///BgAGAgAEA==")
};},
run : function() {
lastSteps = (lastSteps>=Bangle.getHealthStatus("day").steps) ? 0 : Bangle.getHealthStatus("day").steps;
this.emit("redraw");
},
show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
hide : function() { Bangle.removeListener("step", stepUpdateHandler); }
};
return {
name: "Bangle",
items: [ distance ]
};
})

BIN
apps/clkinfodist/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,14 @@
{ "id": "clkinfodist",
"name": "Clockinfo Distance",
"version":"0.01",
"description": "Uses the 'My Profile' app's Stride Length to calculate distance travelled based on step count. Tap to reset for measuring distances.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clkinfo",
"tags": "clkinfo,distance,steps,outdoors,tool",
"dependencies": {"myprofile":"app"},
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"clkinfodist.clkinfo.js","url":"clkinfo.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -17,3 +17,5 @@
0.16: Add BLE clkinfo entry
0.17: Fix BLE icon alignment and border on some clocks
0.18: Tweak BLE icon to add gap and ensure middle of B isn't filled
0.19: Fix Altitude ClockInfo after BLE added
Tapping Altitude now updates the reading

View File

@ -39,22 +39,23 @@ exports.load = function() {
var hrm = 0;
var alt = "--";
// callbacks (needed for easy removal of listeners)
function batteryUpdateHandler() { bangleItems[0].emit("redraw"); }
function stepUpdateHandler() { bangleItems[1].emit("redraw"); }
function batteryUpdateHandler() { bangleItems.find(i=>i.name=="Battery").emit("redraw"); }
function stepUpdateHandler() { bangleItems.find(i=>i.name=="Steps").emit("redraw"); }
function hrmUpdateHandler(e) {
if (e && e.confidence>60) hrm = Math.round(e.bpm);
bangleItems[2].emit("redraw");
bangleItems.find(i=>i.name=="HRM").emit("redraw");
}
function altUpdateHandler() {
try {
Bangle.getPressure().then(data=>{
if (!data) return;
alt = Math.round(data.altitude) + "m";
bangleItems[3].emit("redraw");
bangleItems.find(i=>i.name=="Altitude").emit("redraw");
});
} catch (e) {
print("Caught "+e+"\n in function altUpdateHandler in module clock_info");
bangleItems[3].emit('redraw');}
bangleItems.find(i=>i.name=="Altitude").emit('redraw');
}
}
// actual menu
var menu = [{
@ -120,7 +121,6 @@ exports.load = function() {
},
},
{ name: "BLE",
hasRange: false,
isOn: () => {
const s = NRF.getSecurityStatus();
return s.advertising || s.connected;
@ -156,6 +156,7 @@ exports.load = function() {
min : 0, max : settings.maxAltitude,
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==")
}),
run : function() { alt = "--"; this.emit("redraw"); altUpdateHandler(); },
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
hide : function() { clearInterval(this.interval); delete this.interval; },
});

View File

@ -1,7 +1,7 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"shortName": "Clock Info",
"version":"0.18",
"version":"0.19",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png",
"type": "module",

View File

@ -39,3 +39,4 @@
Speed improvements (put temporary functions in RAM where possible)
0.34: Fix readFullDatabase (was skipping first month of data)
0.35: Update boot/lib.min.js
0.36: Fix Distance graphs that used '1*' to remove the suffix

View File

@ -32,7 +32,7 @@ function menuStepCount() {
}
function menuDistance() {
const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm'
const distMult = parseFloat(require("locale").distance(myprofile.strideLength, 2)); // this removes the distance suffix, e.g. 'm'
E.showMenu({
"": { title:/*LANG*/"Distance" },
/*LANG*/"< Back": () => menuStepCount(),

View File

@ -2,7 +2,7 @@
"id": "health",
"name": "Health Tracking",
"shortName": "Health",
"version": "0.35",
"version": "0.36",
"description": "Logs health data and provides an app to view it",
"icon": "app.png",
"screenshots" : [ { "url":"screenshot.png" } ],

View File

@ -117,3 +117,4 @@
Remove workaround for 2v10 (>3 years ago) - assume everyone is on never firmware now
0.86: Default to showing message scroller (with title, bigger icon)
0.87: Make choosing of font size more repeatable
0.88: Adjust padding calculation so messages are spaced out properly even when using international fonts

View File

@ -14,7 +14,7 @@
// a message
require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1})
require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
require("messages").pushMessage({"t":"add","id":1575479850,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1})
require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1})
require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1})
@ -391,8 +391,6 @@ function showMessage(msgid, persist) {
}
}
lines = g.setFont(bodyFont).wrapString(body, w);
if (lines.length<3)
lines.unshift(""); // if less lines, pad them out a bit at the top!
}
let negHandler,posHandler,rowLeftDraw,rowRightDraw;
if (msg.negative) {
@ -432,12 +430,14 @@ function showMessage(msgid, persist) {
}
let fontHeight = g.setFont(bodyFont).getFontHeight();
let lineHeight = (fontHeight>25)?fontHeight:25;
if (title.includes("\n")) lineHeight=25; // ensure enough room for 2 lines of title in header
if (title.includes("\n") && lineHeight<25) lineHeight=25; // ensure enough room for 2 lines of title in header
let linesPerRow = 2;
if (fontHeight<17) {
lineHeight = 16;
linesPerRow = 3;
}
if ((lines.length+4.5)*lineHeight < Bangle.appRect.h)
lines.unshift(""); // if less lines, pad them out a bit at the top!
let rowHeight = lineHeight*linesPerRow;
let textLineOffset = -(linesPerRow + ((rowLeftDraw||rowRightDraw)?1:0));
let msgIcon = require("messageicons").getImage(msg);

View File

@ -2,7 +2,7 @@
"id": "messagegui",
"name": "Message UI",
"shortName": "Messages",
"version": "0.87",
"version": "0.88",
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -88,3 +88,4 @@ of 'Select Clock'
0.77: Save altitude calibration when user exits via reset
0.78: Fix menu scroll restore on BangleJS1
0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily
0.80: Add option to set LCD brightness to 0, default brightness is now 0 as well.

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.79",
"version": "0.80",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

View File

@ -1,3 +1,4 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -474,11 +475,11 @@ function LCDMenu() {
Object.assign(lcdMenu, {
/*LANG*/'LCD Brightness': {
value: settings.brightness,
min: 0.1,
min : BANGLEJS2 ? 0 : 0.1,
max: 1,
step: 0.1,
onchange: v => {
settings.brightness = v || 1;
settings.brightness = v ?? 1;
updateSettings();
Bangle.setLCDBrightness(settings.brightness);
}

View File

@ -6,3 +6,4 @@
0.05: Make the "App source not found" warning less buggy
0.06: Fixed a crash if an app has no tags (app.tags is undefined)
0.07: Clear cached app list when updating showClocks setting
0.08: Add haptic feedback option when selecting app or category in menu, increase vector size limit in settings

View File

@ -11,6 +11,7 @@ Settings
- `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`.
- `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`.
- `Haptic Feedback` - Whether or not to vibrate slightly when selecting an app or category in the launcher. Default `No`.
- `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`.
- `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`.
@ -28,3 +29,4 @@ Contributors
- [atjn](https://github.com/atjn)
- [BlueFox4](https://github.com/BlueFox4)
- [RKBoss6](https://github.com/RKBoss6)

View File

@ -17,7 +17,8 @@ let vectorval = 20;
let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
let settings = Object.assign({
showClocks: true,
fullscreen: false
fullscreen: false,
buzz:false
}, s.readJSON("taglaunch.json", true) || {});
if ("vectorsize" in settings)
vectorval = parseInt(settings.vectorsize);
@ -108,6 +109,7 @@ let showTagMenu = (tag) => {
}
},
select : i => {
const loadApp = () => {
let app = appsByTag[tag][i];
if (!app) return;
if (!app.src || require("Storage").read(app.src)===undefined) {
@ -117,6 +119,15 @@ let showTagMenu = (tag) => {
} else {
load(app.src);
}
};
if(settings.buzz){
Bangle.buzz(25);
//let the buzz have effect
setTimeout(loadApp,27);
}else{
loadApp();
}
},
back : showMainMenu,
remove: unload
@ -138,6 +149,7 @@ let showMainMenu = () => {
}
},
select : i => {
if(settings.buzz)Bangle.buzz(25);
let tag = tagKeys[i];
showTagMenu(tag);
},

View File

@ -2,8 +2,8 @@
"id": "taglaunch",
"name": "Tag Launcher",
"shortName": "Taglauncher",
"version": "0.07",
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access of the default launcher.",
"version": "0.08",
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access from the default launcher.",
"readme": "README.md",
"icon": "app.png",
"type": "launch",

View File

@ -2,7 +2,8 @@
(function(back) {
let settings = Object.assign({
showClocks: true,
fullscreen: false
fullscreen: false,
buzz:false
}, require("Storage").readJSON("taglaunch.json", true) || {});
let fonts = g.getFonts();
@ -21,9 +22,16 @@
},
/*LANG*/"Vector Font Size": {
value: settings.vectorsize || 10,
min:10, max: 20,step:1,wrap:true,
min:10, max: 25,step:1,wrap:true,
onchange: (m) => {save("vectorsize", m)}
},
/*LANG*/"Haptic Feedback": {
value: settings.buzz == true,
onchange: (m) => {
save("buzz", m);
}
},
/*LANG*/"Show Clocks": {
value: settings.showClocks == true,
onchange: (m) => {

View File

@ -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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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);
}
}
}
// start or restart timer => state: count
function startTimer() {
counterStart = counter;
setState(states.count);
countDown();
if (!counterInterval)
counterInterval = setInterval(countDown, 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);
}
}
// 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;
}
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;
}
// 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);
}
}
}
});
}
// show help text while in start state (see initDragEvents())
function showHelp() {
if (state == states.start) {
state = states.help;
g.setBgColor(g.theme.bg);
// === UI Drawing ===
function drawUI() {
g.reset();
g.setBgColor(g.theme.bg).clear();
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");
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;
}
// return to start
else if (state == states.help) {
counterStart = counter;
initTimer();
// 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);
}
}
// 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();
// === Timer Logic ===
function startTimer() {
setState(STATES.RUNNING);
startTime = Date.now();
target = startTime + remaining * 1000;
}
function pauseTimer() {
if (isState(STATES.RUNNING)) {
remaining = Math.max(0, Math.ceil((target - Date.now()) / 1000));
setState(STATES.PAUSED);
}
}
function resumeTimer() {
if (isState(STATES.PAUSED)) {
startTime = Date.now();
target = startTime + remaining * 1000;
setState(STATES.RUNNING);
}
}
function resetTimer() {
setState(STATES.INIT);
remaining = settings.timerDuration;
}
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();
}
// === 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();
}
drawUI();
}
function handleDoubleTap() {
if (isState(STATES.INIT)) {
let now = Date.now();
if (now - lastTapTime < 400) {
showHelp = !showHelp;
drawUI();
}
lastTapTime = now;
}
}
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);

View File

@ -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"}
]
}

47
apps/teatimer/settings.js Normal file
View File

@ -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();
})