Skip to content

Commit caa6319

Browse files
author
thyttan
committed
Merge remote-tracking branch 'Koell/jwalk' into app-loader
2 parents 29a3614 + 678854d commit caa6319

File tree

7 files changed

+248
-0
lines changed

7 files changed

+248
-0
lines changed

apps/jwalk/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Japanese Walking Timer
2+
3+
A simple timer designed to help you manage your walking intervals, whether you're in a relaxed mode or an intense workout!
4+
5+
![](screenshot.png)
6+
7+
## Usage
8+
9+
- The timer starts with a default total duration and interval duration, which can be adjusted in the settings.
10+
- Tap the screen to pause or resume the timer.
11+
- The timer will switch modes between "Relax" and "Intense" at the end of each interval.
12+
- The display shows the current time, the remaining interval time, and the total time left.
13+
14+
## Creator
15+
16+
[Fabian Köll] ([Koell](https://github.com/Koell))
17+
18+
19+
## Icon
20+
21+
[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: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
const FILE = "jwalk.json";
2+
const DEFAULTS = {
3+
totalDuration: 30,
4+
intervalDuration: 3,
5+
startMode: 0
6+
};
7+
8+
const PAUSE_BEEP_DURATION = 1000;
9+
const SECOND_BEEP_DURATION = 500;
10+
const MESSAGE_DISPLAY_DURATION = 1500;
11+
const FONT_SIZE_TIME = 40;
12+
const FONT_SIZE_BACKGROUND = 2;
13+
const FONT_SIZE_PAUSE = 15;
14+
const CIRCLE_RADIUS = 5;
15+
const TRIANGLE_OFFSET = 6;
16+
17+
// Load settings
18+
var settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
19+
20+
// App state
21+
var state = {
22+
remainingTotal: settings.totalDuration * 60,
23+
intervalDuration: settings.intervalDuration * 60,
24+
remainingInterval: 0,
25+
intervalEnd: 0,
26+
paused: false,
27+
currentMode: settings.startMode === 1 ? "Intense" : "Relax"
28+
};
29+
30+
var drawTimerInterval;
31+
var cachedLeftTime = "";
32+
33+
function formatTime(seconds) {
34+
var mins = Math.floor(seconds / 60);
35+
var secs = (seconds % 60).toString().padStart(2, '0');
36+
return `${mins}:${secs}`;
37+
}
38+
39+
function updateCachedLeftTime() {
40+
cachedLeftTime = "Left: " + formatTime(state.remainingTotal);
41+
}
42+
43+
function getTimeStr() {
44+
var d = new Date();
45+
return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
46+
}
47+
48+
49+
function drawUI() {
50+
var y = Bangle.appRect.y + 8;
51+
g.reset().setBgColor(g.theme.bg).clearRect(Bangle.appRect);
52+
53+
// Background
54+
g.setColor(g.theme.bg);
55+
g.fillRect(0, y + 25, g.getWidth(), g.getHeight());
56+
57+
g.setColor(g.theme.fg);
58+
59+
var displayInterval = state.paused
60+
? state.remainingInterval
61+
: Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
62+
63+
g.setFont("Vector", FONT_SIZE_TIME);
64+
g.setFontAlign(0, 0);
65+
g.drawString(formatTime(displayInterval), g.getWidth() / 2, y + 70);
66+
67+
var cy = y + 100;
68+
if (state.paused) {
69+
g.setFont("Vector", FONT_SIZE_PAUSE);
70+
g.drawString("PAUSED", g.getWidth() / 2, cy);
71+
} else {
72+
var cx = g.getWidth() / 2;
73+
// Use accent color for mode indicators for nice contrast
74+
g.setColor(g.theme.accent || g.theme.fg2 || g.theme.fg);
75+
76+
if (state.currentMode === "Relax") {
77+
g.fillCircle(cx, cy, CIRCLE_RADIUS);
78+
} else {
79+
g.fillPoly([
80+
cx, cy - TRIANGLE_OFFSET,
81+
cx - TRIANGLE_OFFSET, cy + TRIANGLE_OFFSET,
82+
cx + TRIANGLE_OFFSET, cy + TRIANGLE_OFFSET
83+
]);
84+
}
85+
86+
// Reset to normal fg for text below
87+
g.setColor(g.theme.fg);
88+
}
89+
90+
g.setFont("6x8", FONT_SIZE_BACKGROUND);
91+
g.setFontAlign(0, -1);
92+
g.drawString(state.currentMode, g.getWidth() / 2, y + 15);
93+
94+
g.drawString(cachedLeftTime, g.getWidth() / 2, cy + 15);
95+
96+
g.setFontAlign(1, 0);
97+
g.drawString(getTimeStr(), g.getWidth() - 4, y);
98+
99+
100+
101+
g.flip();
102+
}
103+
104+
function toggleMode() {
105+
state.currentMode = state.currentMode === "Relax" ? "Intense" : "Relax";
106+
Bangle.buzz();
107+
}
108+
109+
function startNextInterval() {
110+
if (state.remainingTotal <= 0) {
111+
clearInterval(drawTimerInterval);
112+
Bangle.buzz(PAUSE_BEEP_DURATION);
113+
setTimeout(() => Bangle.buzz(SECOND_BEEP_DURATION), MESSAGE_DISPLAY_DURATION);
114+
E.showMessage("Done!");
115+
return;
116+
}
117+
118+
state.remainingInterval = Math.min(state.intervalDuration, state.remainingTotal);
119+
state.remainingTotal -= state.remainingInterval;
120+
updateCachedLeftTime();
121+
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
122+
}
123+
124+
function tick() {
125+
if (!state.paused) {
126+
if ((state.intervalEnd - Date.now()) / 1000 <= 0) {
127+
toggleMode();
128+
startNextInterval();
129+
}
130+
}
131+
drawUI();
132+
}
133+
134+
function togglePause() {
135+
if (!state.paused) {
136+
state.remainingInterval = Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
137+
state.paused = true;
138+
} else {
139+
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
140+
state.paused = false;
141+
}
142+
drawUI();
143+
}
144+
145+
// Button / touch handlers
146+
Bangle.on("touch", togglePause);
147+
148+
Bangle.loadWidgets();
149+
Bangle.drawWidgets();
150+
151+
startNextInterval();
152+
drawUI();
153+
drawTimerInterval = setInterval(tick, 1000);

apps/jwalk/app.png

6.28 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": ["BANGLEJS","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)