Skip to content

Commit 3dcbab9

Browse files
author
thyttan
committed
Merge remote-tracking branch 'voloved/jsonclock' into app-loader
2 parents 37e7bba + bc2db7d commit 3dcbab9

File tree

11 files changed

+296
-0
lines changed

11 files changed

+296
-0
lines changed

apps/jsonclock/ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: first release

apps/jsonclock/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# JSON Clock ![](app.png)
2+
3+
*JSON view of the time.*
4+
5+
Written by: [David Volovskiy](https://github.com/voloved) and fully
6+
inspired by [This FitBit face](https://github.com/Sharkgrammer/clockface.json)
7+
8+
* Displays a JSON that shows the date, time, step count, sunrise/set and battery.
9+
* If the Bangle's theme is dark, then it'll show in dark view (`Settings>System>Theme>Dark BW`)
10+
* It'll show 12 hrs if it's set so in `Settings>System>Locale>Time Format`
11+
* Along with the physical button, you can leave the clock by pressing the `X` next to the `clockface.json` in the tab.
12+
* If the steps cannot be gotten, they won't display
13+
* If the location isn't set, then the JSON array will ignore the sun info and instead display the time as a struct.
14+
15+
## Screenshots
16+
![](dark.png)
17+
![](white.png)
18+
![](no_steps.png)
19+
![](no_location.png)
20+
![](dark-emulator.png)

apps/jsonclock/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/jsonclock/app.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
{
2+
const SunCalc = require("suncalc"); // from modules folder
3+
const storage = require('Storage');
4+
const widget_utils = require('widget_utils');
5+
const global_settings = storage.readJSON("setting.json", true) || {};
6+
const LOCATION_FILE = "mylocation.json";
7+
const h = g.getHeight();
8+
const w = g.getWidth();
9+
let location;
10+
let cachedSunTimes = null;
11+
let lastSunCalcDate = null;
12+
var prevVals = {};
13+
let drawTimeout;
14+
15+
var settings = {
16+
hr_12: global_settings["12hour"] !== undefined ? global_settings["12hour"] : false,
17+
dark_mode: g.theme.dark
18+
};
19+
20+
let clrs = {
21+
tab: "#505050", // grey
22+
keys: settings.dark_mode ? "#4287f5" : "#0000FF", // blue
23+
strings: settings.dark_mode ? "#F0A000" : "#FF0000", // orange or red
24+
ints: settings.dark_mode ? "#00FF00" : "#005F00", // green
25+
bg: g.theme.bg,
26+
brackets: g.theme.fg,
27+
};
28+
29+
30+
let jsonText;
31+
let lines = [];
32+
let fontSize = 13;
33+
const lineHeight = 16;
34+
35+
const buttonHeight = 12;
36+
const buttonX = 78;
37+
const buttonY = 3;
38+
39+
let valuePositions = [];
40+
const headerHeight = 16;
41+
const usableHeight = h - headerHeight;
42+
const maxLines = Math.floor(usableHeight / lineHeight);
43+
var numWidth = 0;
44+
45+
// requires the myLocation app
46+
let loadLocation = function() {
47+
location = require("Storage").readJSON(LOCATION_FILE, 1) || {};
48+
location.lat = location.lat || 0;
49+
location.lon = location.lon || 0;
50+
location.location = location.location || null;
51+
};
52+
53+
let getHr = function(h) {
54+
var amPm = "";
55+
if (settings.hr_12) {
56+
amPm = h < 12 ? "AM" : "PM";
57+
h = h % 12;
58+
if (h == 0) h = 12;
59+
}
60+
return [h, amPm];
61+
};
62+
63+
let extractTime = function(d) {
64+
const out = getHr(d.getHours());
65+
const h = out[0];
66+
const amPm = out[1];
67+
const m = d.getMinutes();
68+
return `${h}:${("0"+m).substr(-2)}${amPm}`;
69+
};
70+
71+
let extractDate = function(d) {
72+
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
73+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
74+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
75+
];
76+
77+
const weekday = weekdays[d.getDay()];
78+
const month = months[d.getMonth()];
79+
const day = d.getDate();
80+
81+
return `${weekday} ${month} ${day}`;
82+
};
83+
84+
let getSteps = function() {
85+
try {
86+
return Bangle.getHealthStatus("day").steps;
87+
} catch (e) {
88+
if (WIDGETS.wpedom !== undefined)
89+
return WIDGETS.wpedom.getSteps();
90+
else
91+
return null;
92+
}
93+
};
94+
95+
let getVal = function(now, loc) {
96+
const vals = {};
97+
const currentDateStr = extractDate(now);
98+
if (loc.location) {
99+
if (lastSunCalcDate !== currentDateStr) {
100+
cachedSunTimes = SunCalc.getTimes(now, location.lat, location.lon);
101+
lastSunCalcDate = currentDateStr;
102+
}
103+
vals.rise = extractTime(cachedSunTimes.sunrise);
104+
vals.set = extractTime(cachedSunTimes.sunset);
105+
}
106+
vals.time = extractTime(now);
107+
vals.date = currentDateStr;
108+
vals.batt_pct = E.getBattery();
109+
vals.steps = getSteps();
110+
return vals;
111+
};
112+
113+
let loadJson = function() {
114+
const now = new Date();
115+
const vals = getVal(now, location);
116+
//vals.steps = null; // For testing; uncomment to see the steps not appear
117+
//location.location = null; // For testing, if null, the time becomes an struct to take up sun's struct
118+
let raw;
119+
120+
if (location.location !== null) {
121+
raw = {
122+
time: vals.time,
123+
dt: vals.date,
124+
sun: {
125+
rise: vals.rise,
126+
set: vals.set,
127+
},
128+
"batt_%": vals.batt_pct,
129+
};
130+
} else {
131+
raw = {
132+
time: {
133+
hr: getHr(now.getHours())[0],
134+
min: now.getMinutes(),
135+
},
136+
dt: vals.date,
137+
"batt_%": vals.batt_pct,
138+
};
139+
}
140+
141+
if (vals.steps != null) raw.steps = vals.steps;
142+
143+
jsonText = JSON.stringify(raw, null, 2);
144+
lines = jsonText.split("\n");
145+
};
146+
147+
let draw = function() {
148+
g.clear();
149+
g.setFontAlign(-1, -1);
150+
g.setFont("Vector", 10);
151+
valuePositions = [];
152+
153+
g.setColor(clrs.tab);
154+
155+
g.fillRect(90, 0, w, headerHeight);
156+
g.setColor(clrs.brackets);
157+
g.drawString("clockface.json", 3, 3);
158+
159+
g.setFont("Vector", buttonHeight);
160+
g.drawString("X", buttonX, buttonY);
161+
g.setFont("Vector", fontSize);
162+
163+
for (let i = 0; i < maxLines; i++) {
164+
const y = headerHeight + i * lineHeight;
165+
const lineNumberStr = (i + 1).toString().padStart(2, " ") + " ";
166+
g.drawString(lineNumberStr, 0, y);
167+
numWidth = Math.max(numWidth, g.stringWidth(lineNumberStr));
168+
}
169+
170+
redrawValues();
171+
};
172+
173+
let redraw = function() {
174+
for (let i = 0; i < maxLines; i++) {
175+
const lineIndex = i;
176+
const line = lines[lineIndex];
177+
if (!line) continue;
178+
const y = headerHeight + i * lineHeight;
179+
180+
const indentMatch = line.match(/^(\s*)/);
181+
const indent = indentMatch ? indentMatch[1] : "";
182+
183+
const kvMatch = line.trim().match(/^"([^"]+)":\s*(.+)$/);
184+
if (kvMatch) {
185+
const key = kvMatch[1];
186+
let value = kvMatch[2];
187+
188+
if (prevVals.key == value) continue;
189+
prevVals.key = value;
190+
191+
// Key
192+
g.setColor(clrs.keys);
193+
g.drawString(indent + `"${key}"`, numWidth, y);
194+
const keyWidth = g.stringWidth(indent + `"${key}"`);
195+
const valueX = numWidth + keyWidth;
196+
const valueText = ": " + value;
197+
198+
// Value color
199+
if (value.startsWith('"')) {
200+
g.setColor(clrs.strings);
201+
} else if (value.startsWith('{') || value.startsWith('}')) {
202+
g.setColor(clrs.brackets);
203+
} else {
204+
g.setColor(clrs.ints);
205+
}
206+
g.drawString(valueText, valueX, y);
207+
208+
valuePositions.push({
209+
key,
210+
x: valueX,
211+
y,
212+
text: value
213+
});
214+
} else {
215+
g.setColor(clrs.brackets);
216+
g.drawString(line, numWidth, y);
217+
}
218+
}
219+
};
220+
221+
let clearVals = function() {
222+
g.setFont("Vector", fontSize);
223+
g.setFontAlign(-1, -1);
224+
valuePositions.forEach(pos => {
225+
g.setColor(clrs.bg);
226+
g.fillRect(pos.x, pos.y, w, pos.y + lineHeight);
227+
});
228+
};
229+
230+
let redrawValues = function() {
231+
loadJson();
232+
clearVals();
233+
redraw();
234+
if (drawTimeout) clearTimeout(drawTimeout);
235+
drawTimeout = setTimeout(function() {
236+
drawTimeout = undefined;
237+
redrawValues();
238+
}, 60000 - (Date.now() % 60000));
239+
};
240+
241+
Bangle.on('touch', (zone, e) => {
242+
if (e.x >= (buttonY - buttonHeight) && e.x <= (buttonX + buttonHeight) &&
243+
(e.y >= (buttonY - buttonHeight) && e.y <= (buttonY + buttonHeight))) {
244+
Bangle.showLauncher(); // Exit app
245+
}
246+
});
247+
248+
Bangle.on('backlight', function(on) {
249+
if (on) {
250+
redrawValues();
251+
}
252+
});
253+
254+
Bangle.setUI("clock");
255+
loadLocation();
256+
Bangle.loadWidgets();
257+
widget_utils.hide();
258+
draw();
259+
}

apps/jsonclock/app.png

2.46 KB
Loading

apps/jsonclock/dark-emulator.png

3.68 KB
Loading

apps/jsonclock/dark.png

3.59 KB
Loading

apps/jsonclock/light.png

3.24 KB
Loading

apps/jsonclock/metadata.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{ "id": "jsonclock",
2+
"name": "JsonClock",
3+
"version": "0.01",
4+
"description": "JSON view of the time, date, steps, battery, and sunrise and sunset times",
5+
"icon": "app.png",
6+
"screenshots": [{"url":"dark-emulator.png"}],
7+
"readme": "README.md",
8+
"type": "clock",
9+
"tags": "clock",
10+
"supports": ["BANGLEJS","BANGLEJS2"],
11+
"storage": [
12+
{"name":"jsonclock.app.js","url":"app.js"},
13+
{"name":"jsonclock.img","url":"app-icon.js","evaluate":true}
14+
]
15+
}

apps/jsonclock/no_location.png

3.26 KB
Loading

0 commit comments

Comments
 (0)