178 lines
4.3 KiB
JavaScript
178 lines
4.3 KiB
JavaScript
// === Utility Functions ===
|
|
function formatTime(seconds) {
|
|
let mins = Math.floor(seconds / 60);
|
|
let secs = (seconds % 60).toString().padStart(2, '0');
|
|
return `${mins}:${secs}`;
|
|
}
|
|
|
|
function getTimeStr() {
|
|
let d = new Date();
|
|
return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function updateCachedLeftTime() {
|
|
cachedLeftTime = "Left: " + formatTime(state.remainingTotal);
|
|
}
|
|
|
|
// === Constants ===
|
|
const FILE = "jwalk.json";
|
|
const DEFAULTS = {
|
|
totalDuration: 30,
|
|
intervalDuration: 3,
|
|
startMode: 0,
|
|
modeBuzzerDuration: 1000,
|
|
finishBuzzerDuration: 1500,
|
|
showClock: 1,
|
|
updateWhileLocked: 0
|
|
};
|
|
|
|
// === Settings and State ===
|
|
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
|
|
|
|
let state = {
|
|
remainingTotal: settings.totalDuration * 60,
|
|
intervalDuration: settings.intervalDuration * 60,
|
|
remainingInterval: 0,
|
|
intervalEnd: 0,
|
|
paused: false,
|
|
currentMode: settings.startMode === 1 ? "Intense" : "Relax",
|
|
finished: false,
|
|
forceDraw: false,
|
|
};
|
|
|
|
let cachedLeftTime = "";
|
|
let lastMinuteStr = getTimeStr();
|
|
let drawTimerInterval;
|
|
|
|
// === UI Rendering ===
|
|
function drawUI() {
|
|
let y = Bangle.appRect.y + 8;
|
|
g.reset().setBgColor(g.theme.bg).clearRect(Bangle.appRect);
|
|
g.setColor(g.theme.fg);
|
|
|
|
let displayInterval = state.paused
|
|
? state.remainingInterval
|
|
: Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
|
|
|
|
g.setFont("Vector", 40);
|
|
g.setFontAlign(0, 0);
|
|
g.drawString(formatTime(displayInterval), g.getWidth() / 2, y + 70);
|
|
|
|
let cy = y + 100;
|
|
if (state.paused) {
|
|
g.setFont("Vector", 15);
|
|
g.drawString("PAUSED", g.getWidth() / 2, cy);
|
|
} else {
|
|
let cx = g.getWidth() / 2;
|
|
g.setColor(g.theme.accent || g.theme.fg2 || g.theme.fg);
|
|
if (state.currentMode === "Relax") {
|
|
g.fillCircle(cx, cy, 5);
|
|
} else {
|
|
g.fillPoly([
|
|
cx, cy - 6,
|
|
cx - 6, cy + 6,
|
|
cx + 6, cy + 6
|
|
]);
|
|
}
|
|
g.setColor(g.theme.fg);
|
|
}
|
|
|
|
g.setFont("6x8", 2);
|
|
g.setFontAlign(0, -1);
|
|
g.drawString(state.currentMode, g.getWidth() / 2, y + 15);
|
|
g.drawString(cachedLeftTime, g.getWidth() / 2, cy + 15);
|
|
|
|
if (settings.showClock) {
|
|
g.setFontAlign(1, 0);
|
|
g.drawString(lastMinuteStr, g.getWidth() - 4, y);
|
|
}
|
|
g.flip();
|
|
}
|
|
|
|
// === Workout Logic ===
|
|
function toggleMode() {
|
|
state.currentMode = state.currentMode === "Relax" ? "Intense" : "Relax";
|
|
Bangle.buzz(settings.modeBuzzerDuration);
|
|
state.forceDraw = true;
|
|
}
|
|
|
|
function startNextInterval() {
|
|
if (state.remainingTotal <= 0) {
|
|
finishWorkout();
|
|
return;
|
|
}
|
|
|
|
state.remainingInterval = Math.min(state.intervalDuration, state.remainingTotal);
|
|
state.remainingTotal -= state.remainingInterval;
|
|
updateCachedLeftTime();
|
|
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
|
|
state.forceDraw = true;
|
|
}
|
|
|
|
function togglePause() {
|
|
if (state.finished) return;
|
|
|
|
if (!state.paused) {
|
|
state.remainingInterval = Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
|
|
state.paused = true;
|
|
} else {
|
|
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
|
|
state.paused = false;
|
|
}
|
|
drawUI();
|
|
}
|
|
|
|
function finishWorkout() {
|
|
clearInterval(drawTimerInterval);
|
|
Bangle.buzz(settings.finishBuzzerDuration);
|
|
state.finished = true;
|
|
|
|
setTimeout(() => {
|
|
g.clear();
|
|
g.setFont("Vector", 30);
|
|
g.setFontAlign(0, 0);
|
|
g.drawString("Well done!", g.getWidth() / 2, g.getHeight() / 2);
|
|
g.flip();
|
|
|
|
const exitHandler = () => {
|
|
Bangle.removeListener("touch", exitHandler);
|
|
Bangle.removeListener("btn1", exitHandler);
|
|
load(); // Exit app
|
|
};
|
|
|
|
Bangle.on("touch", exitHandler);
|
|
setWatch(exitHandler, BTN1, { repeat: false });
|
|
}, 500);
|
|
}
|
|
|
|
// === Timer Tick ===
|
|
function tick() {
|
|
if (state.finished) return;
|
|
|
|
const currentMinuteStr = getTimeStr();
|
|
if (currentMinuteStr !== lastMinuteStr) {
|
|
lastMinuteStr = currentMinuteStr;
|
|
state.forceDraw = true;
|
|
}
|
|
|
|
if (!state.paused && (state.intervalEnd - Date.now()) / 1000 <= 0) {
|
|
toggleMode();
|
|
startNextInterval();
|
|
return;
|
|
}
|
|
|
|
if (state.forceDraw || settings.updateWhileLocked || !Bangle.isLocked()) {
|
|
drawUI();
|
|
state.forceDraw = false;
|
|
}
|
|
}
|
|
|
|
// === Initialization ===
|
|
Bangle.on("touch", togglePause);
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
updateCachedLeftTime();
|
|
startNextInterval();
|
|
drawUI();
|
|
drawTimerInterval = setInterval(tick, 1000); |