diff --git a/apps/jsonclock/ChangeLog b/apps/jsonclock/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/jsonclock/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/jsonclock/app-icon.js b/apps/jsonclock/app-icon.js new file mode 100644 index 000000000..8c2888b97 --- /dev/null +++ b/apps/jsonclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("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")) diff --git a/apps/jsonclock/app.js b/apps/jsonclock/app.js new file mode 100644 index 000000000..8c215994a --- /dev/null +++ b/apps/jsonclock/app.js @@ -0,0 +1,234 @@ +var SunCalc = require("suncalc"); // from modules folder +const storage = require('Storage'); +const widget_utils = require('widget_utils'); +//const SETTINGS_FILE = "jsonClock.json"; +const global_settings = storage.readJSON("setting.json", true) || {}; +const LOCATION_FILE = "mylocation.json"; +const h = g.getHeight(); +const w = g.getWidth(); +let location; + +var settings = { + hr_12: global_settings["12hour"] !== undefined ? global_settings["12hour"] : false, + dark_mode: g.theme.dark +}; + +let clrs = { + tab : settings.dark_mode ? "#2d2d30" : "#DDDDDD", // grey + keys: settings.dark_mode ? "#4287f5" : "#0000FF", // blue + strings: settings.dark_mode ? "#F0A000" : "#FF0000", // orange or red + ints: settings.dark_mode ? "#00FF00" : "#00FF00", // green + bg: g.theme.bg, + brackets: g.theme.fg, +}; + + +let jsonText; +let lines = []; +let scrollOffset = 0; +let fontSize = 12; +const lineHeight = 16; + +const buttonHeight = 12; +const buttonX = 78; +const buttonY = 3; + +let valuePositions = []; +const headerHeight = 16; +const usableHeight = h - headerHeight; +const maxLines = Math.floor(usableHeight / lineHeight); +var numWidth = 0; + +// requires the myLocation app +function loadLocation() { + location = require("Storage").readJSON(LOCATION_FILE,1)||{}; + location.lat = location.lat||35.7796; + location.lon = location.lon||-78.6382; + location.location = location.location||"Raleigh"; +} + +function extractTime(d){ + var h = d.getHours(), m = d.getMinutes(); + var amPm = ""; + if (settings.hr_12) { + amPm = h < 12 ? "AM" : "PM"; + h = h % 12; + if (h == 0) h = 12; + } + return `${h}:${("0"+m).substr(-2)}${amPm}`; + } + + function extractDate(d) { + const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + const weekday = weekdays[d.getDay()]; + const month = months[d.getMonth()]; + const day = d.getDate(); + + return `${weekday} ${month}, ${day}`; + } + + function getSteps() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return 0; + } + } + +function getVal() { + vals = {}; + const now = new Date(); + const sunTimes = SunCalc.getTimes(now, location.lat, location.lon); + vals.time = extractTime(now); + vals.date = extractDate(now); + vals.rise = extractTime(sunTimes.sunrise); + vals.set = extractTime(sunTimes.sunset); + vals.batt_pct = E.getBattery(); + vals.steps = getSteps(); + return vals; +} + + +function loadJson() { + vals = getVal(); + const raw = JSON.stringify({ + time: vals.time, + date: vals.date, + sun: { + rise: vals.rise, + set: vals.set, + }, + "batt_%": vals.batt_pct, + steps: vals.steps + }); + jsonText = JSON.stringify(JSON.parse(raw), null, 2); + lines = jsonText.split("\n"); +} + +function draw() { + g.clear(); + g.setFontAlign(-1, -1); + g.setFont("Vector", 10); + valuePositions = []; + + g.setColor(clrs.tab); + + g.fillRect(90, 0, w, headerHeight); + loadJson(); + g.setColor(clrs.brackets); + g.drawString("clockface.json",3,3); + + g.setFont("Vector", buttonHeight); + g.drawString("X",buttonX,buttonY); + g.setFont("Vector", fontSize); + + for (let i = 0; i < maxLines; i++) { + const lineIndex = i + scrollOffset; + const line = lines[lineIndex]; + if (!line) continue; + + const y = headerHeight + i * lineHeight; + + const lineNumberStr = (lineIndex + 1).toString().padStart(2, " ") + " "; + g.drawString(lineNumberStr, 0, y); + numWidth = Math.max(numWidth, g.stringWidth(lineNumberStr)); + } + + redraw(); +} + +function redraw() { + for (let i = 0; i < maxLines; i++) { + const lineIndex = i + scrollOffset; + const line = lines[lineIndex]; + if (!line) continue; + const y = headerHeight + i * lineHeight; + + const indentMatch = line.match(/^(\s*)/); + const indent = indentMatch ? indentMatch[1] : ""; + + const kvMatch = line.trim().match(/^"([^"]+)":\s*(.+)$/); + if (kvMatch) { + const key = kvMatch[1]; + let value = kvMatch[2]; + + // Key + g.setColor(clrs.keys); + g.drawString(indent + `"${key}"`, numWidth, y); + const keyWidth = g.stringWidth(indent + `"${key}"`); + const valueX = numWidth + keyWidth; + const valueText = ": " + value; + + // Value color + if (value.startsWith('"')) { + g.setColor(clrs.strings); + } + else if (value.startsWith('{') || value.startsWith('}')) { + g.setColor(clrs.brackets); + } + else { + g.setColor(clrs.ints); + } + g.drawString(valueText, valueX, y); + + valuePositions.push({ key, x: valueX, y, text: value }); + } + else { + g.setColor(clrs.brackets); + g.drawString(line, numWidth, y); + } + } + Bangle.drawWidgets(); +} + +function clearVals() { + g.setFont("Vector", fontSize); + g.setFontAlign(-1, -1); + valuePositions.forEach(pos => { + g.setColor(clrs.bg); + g.fillRect(pos.x, pos.y, w, pos.y + lineHeight); + }); +} + +function redrawValues(){ + loadJson(); + clearVals(); + redraw(); +} + +function scroll(amount) { + const maxOffset = Math.max(0, lines.length - Math.floor((h - headerHeight) / lineHeight)); + scrollOffset = Math.max(0, Math.min(scrollOffset + amount, maxOffset)); + draw(); +} + + +Bangle.on('touch', (zone, e) => { + if (e.x >= (buttonY - buttonHeight) && e.x <= (buttonX + buttonHeight) + && (e.y >= (buttonY - buttonHeight) && e.y <= (buttonY + buttonHeight))) { + load(); // Exit app + } +}); + + +Bangle.on('drag', (e) => scroll(-e.dy)); + +Bangle.setUI({ + mode: "clock", + remove: function () { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +loadLocation(); +Bangle.loadWidgets(); +widget_utils.hide(); +draw(); +setInterval(redrawValues, 5000); diff --git a/apps/jsonclock/app.png b/apps/jsonclock/app.png new file mode 100644 index 000000000..88e06c4ef Binary files /dev/null and b/apps/jsonclock/app.png differ diff --git a/apps/jsonclock/metadata.json b/apps/jsonclock/metadata.json new file mode 100644 index 000000000..643c25613 --- /dev/null +++ b/apps/jsonclock/metadata.json @@ -0,0 +1,19 @@ +{ "id": "jsonclock", + "name": "jsonclock", + "version": "0.01", + "dependencies": {"mylocation":"app"}, + "description": "JSON view of the time, date, steps, battery, and sunrise and sunset times", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"app.png"}], + "readme": "README.md", + "storage": [ + {"name":"jsonclock.app.js","url":"app.js"}, + {"name":"jsonclock.img","url":"app-icon.js","evaluate":true}, + {"name":"jsonclock.settings.js","url":"settings.js"} + ], + "data": [{"name":"jsonclock.json"}], + "sortorder": -9 +}