Skip to content

Commit d97e4d4

Browse files
committed
feat: japanese walking
1 parent 07ce498 commit d97e4d4

File tree

7 files changed

+229
-0
lines changed

7 files changed

+229
-0
lines changed

apps/jwalk/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Here's the updated README with the title changed and the icon section removed:
2+
3+
# Japanese Walking Timer
4+
5+
A simple timer designed to help you manage your walking intervals, whether you're in a relaxed mode or an intense workout!
6+
7+
![](screenshot.png)
8+
9+
## Usage
10+
11+
- The timer starts with a default total duration and interval duration, which can be adjusted in the settings.
12+
- Tap the screen to pause or resume the timer.
13+
- The timer will switch modes between "Relax" and "Intense" at the end of each interval.
14+
- The display shows the current time, the remaining interval time, and the total time left.
15+
16+
## Creator
17+
18+
[Fabian Köll] ([Koell](https://github.com/Koell))
19+
20+
21+
## Icon
22+
23+
([Icon](https://www.koreanwikiproject.com/wiki/images/2/2f/%E8%A1%8C.png))

apps/jwalk/app-icon.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/jwalk/app.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
const FILE = "jwalk.json";
2+
const DEFAULTS = {
3+
totalDuration: 30, // in minutes
4+
intervalDuration: 3, // in minutes
5+
startMode: 0, // 0 for Relax, 1 for Intense
6+
};
7+
8+
// Constants for time calculations
9+
const SECONDS_IN_MINUTE = 60;
10+
const PAUSE_BEEP_DURATION = 1000; // in milliseconds
11+
const SECOND_BEEP_DURATION = 500; // in milliseconds
12+
const MESSAGE_DISPLAY_DURATION = 1500; // in milliseconds
13+
const FONT_SIZE_TIME = 40;
14+
const FONT_SIZE_MODE = 2;
15+
const FONT_SIZE_TOTAL = 2;
16+
const FONT_SIZE_PAUSE = 15;
17+
const CIRCLE_RADIUS = 5;
18+
const TRIANGLE_OFFSET = 6;
19+
20+
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
21+
22+
let remainingTotal = settings.totalDuration * SECONDS_IN_MINUTE;
23+
let intervalDuration = settings.intervalDuration * SECONDS_IN_MINUTE;
24+
let remainingInterval = intervalDuration;
25+
let currentMode = settings.startMode === 1 ? "Intense" : "Relax";
26+
let paused = false;
27+
let intervalEnd = 0;
28+
let drawTimerInterval;
29+
30+
function formatTime(seconds) {
31+
const minutes = Math.floor(seconds / SECONDS_IN_MINUTE);
32+
const secs = (seconds % SECONDS_IN_MINUTE).toString().padStart(2, '0');
33+
return `${minutes}:${secs}`;
34+
}
35+
36+
function drawTimer() {
37+
g.setBgColor("#000");
38+
g.clear();
39+
g.setColor("#FFF");
40+
41+
// Display current time
42+
const now = new Date();
43+
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
44+
g.setFont("6x8", FONT_SIZE_MODE);
45+
g.setFontAlign(1, 0);
46+
g.drawString(timeStr, g.getWidth() - 4, 8);
47+
48+
// Display current mode
49+
g.setFontAlign(0, 0);
50+
g.drawString(currentMode, g.getWidth() / 2, 40);
51+
52+
// Display interval time
53+
const displayInterval = paused ? remainingInterval : Math.max(0, Math.floor((intervalEnd - Date.now()) / 1000));
54+
g.setFont("Vector", FONT_SIZE_TIME);
55+
g.drawString(formatTime(displayInterval), g.getWidth() / 2, g.getHeight() / 2);
56+
57+
// Display paused status
58+
if (paused) {
59+
g.setFont("Vector", FONT_SIZE_PAUSE);
60+
g.setFontAlign(0, 0);
61+
g.drawString("PAUSED", g.getWidth() / 2, g.getHeight() / 2 + 40);
62+
} else {
63+
// Draw mode indicator
64+
if (currentMode === "Relax") {
65+
g.fillCircle(g.getWidth() / 2, g.getHeight() / 2 + 40, CIRCLE_RADIUS);
66+
} else {
67+
const cx = g.getWidth() / 2;
68+
const cy = g.getHeight() / 2 + 40;
69+
g.fillPoly([
70+
cx, cy - TRIANGLE_OFFSET,
71+
cx - TRIANGLE_OFFSET, cy + TRIANGLE_OFFSET,
72+
cx + TRIANGLE_OFFSET, cy + TRIANGLE_OFFSET
73+
]);
74+
}
75+
}
76+
77+
// Display total time left
78+
g.setFont("6x8", FONT_SIZE_TOTAL);
79+
g.drawString(`Left: ${formatTime(remainingTotal)}`, g.getWidth() / 2, g.getHeight() / 2 + 60);
80+
81+
g.flip();
82+
}
83+
84+
function toggleMode() {
85+
currentMode = (currentMode === "Relax") ? "Intense" : "Relax";
86+
Bangle.buzz();
87+
}
88+
89+
function startNextInterval() {
90+
if (remainingTotal <= 0) {
91+
clearInterval(drawTimerInterval);
92+
Bangle.buzz(PAUSE_BEEP_DURATION);
93+
setTimeout(() => Bangle.buzz(SECOND_BEEP_DURATION), MESSAGE_DISPLAY_DURATION);
94+
E.showMessage("Fertig!");
95+
return;
96+
}
97+
98+
remainingInterval = Math.min(intervalDuration, remainingTotal);
99+
remainingTotal -= remainingInterval;
100+
intervalEnd = Date.now() + remainingInterval * 1000;
101+
}
102+
103+
function tick() {
104+
if (paused) return;
105+
106+
if (Math.floor((intervalEnd - Date.now()) / 1000) <= 0) {
107+
toggleMode();
108+
startNextInterval();
109+
}
110+
111+
drawTimer();
112+
}
113+
114+
function togglePause() {
115+
if (!paused) {
116+
remainingInterval = Math.max(0, Math.floor((intervalEnd - Date.now()) / 1000));
117+
paused = true;
118+
} else {
119+
intervalEnd = Date.now() + remainingInterval * 1000;
120+
paused = false;
121+
}
122+
drawTimer();
123+
}
124+
125+
Bangle.on("touch", togglePause);
126+
Bangle.on("swipe", () => {});
127+
Bangle.loadWidgets();
128+
Bangle.drawWidgets();
129+
130+
startNextInterval();
131+
drawTimer();
132+
drawTimerInterval = setInterval(tick, 1000);

apps/jwalk/app.png

7.2 KB
Loading

apps/jwalk/metadata.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"id": "jwalk",
3+
"name": "Japanese Walking",
4+
"shortName": "J-Walk",
5+
"icon": "app.png",
6+
"version": "0.01",
7+
"description": "Alternating walk timer: 3 min Relax / 3 min Intense for a set time. Tap to pause/resume. Start mode, interval and total time configurable via Settings.",
8+
"tags": "walk,timer,fitness",
9+
"supports": ["BANGLEJS2"],
10+
"readme": "README.md",
11+
"data": [
12+
{ "name": "jwalk.json" }
13+
],
14+
"storage": [
15+
{ "name": "jwalk.app.js", "url": "app.js" },
16+
{ "name": "jwalk.settings.js", "url": "settings.js" },
17+
{ "name": "jwalk.img", "url": "app-icon.js", "evaluate": true }
18+
]
19+
}

apps/jwalk/screenshot.png

5.48 KB
Loading

apps/jwalk/settings.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
(function (back) {
2+
const FILE = "jwalk.json";
3+
const DEFAULTS = {
4+
totalDuration: 30,
5+
intervalDuration: 3,
6+
startMode: 0,
7+
};
8+
9+
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
10+
11+
function saveSettings() {
12+
require("Storage").writeJSON(FILE, settings);
13+
}
14+
15+
function showSettingsMenu() {
16+
const menu = {
17+
'': { title: 'Japanese Walking' },
18+
'< Back': back,
19+
'Total Time (min)': {
20+
value: settings.totalDuration,
21+
min: 10,
22+
max: 60,
23+
step: 1,
24+
onchange: v => {
25+
settings.totalDuration = v;
26+
saveSettings();
27+
}
28+
},
29+
'Interval (min)': {
30+
value: settings.intervalDuration,
31+
min: 1,
32+
max: 10,
33+
step: 1,
34+
onchange: v => {
35+
settings.intervalDuration = v;
36+
saveSettings();
37+
}
38+
},
39+
'Start Mode': {
40+
value: settings.startMode,
41+
min: 0,
42+
max: 1,
43+
format: v => v ? "Intense" : "Relax",
44+
onchange: v => {
45+
settings.startMode = v;
46+
saveSettings();
47+
}
48+
}
49+
};
50+
E.showMenu(menu);
51+
}
52+
53+
showSettingsMenu();
54+
})

0 commit comments

Comments
 (0)