From f66efa1b367072e6d7a1b2454a1dc9107da3526d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 19 Apr 2025 08:34:52 -0400 Subject: [PATCH] initial commit of jsonclock --- apps/jsonclock/ChangeLog | 1 + apps/jsonclock/app-icon.js | 1 + apps/jsonclock/app.js | 234 +++++++++++++++++++++++++++++++++++ apps/jsonclock/app.png | Bin 0 -> 3773 bytes apps/jsonclock/metadata.json | 19 +++ 5 files changed, 255 insertions(+) create mode 100644 apps/jsonclock/ChangeLog create mode 100644 apps/jsonclock/app-icon.js create mode 100644 apps/jsonclock/app.js create mode 100644 apps/jsonclock/app.png create mode 100644 apps/jsonclock/metadata.json 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 0000000000000000000000000000000000000000..88e06c4ef24ac012d13fc7ab382611afc6f8c2a9 GIT binary patch literal 3773 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 pO&|ud9k