From a15a049b941507261401534b077b3e015a7a6c6d Mon Sep 17 00:00:00 2001 From: Francesco Bedussi Date: Sun, 3 May 2020 21:12:28 +0200 Subject: [PATCH] feat: add large clock --- apps.json | 31 +++++ apps/largeclock/ChangeLog | 1 + apps/largeclock/README.md | 19 +++ apps/largeclock/largeclock-icon.js | 1 + apps/largeclock/largeclock.js | 198 +++++++++++++++++++++++++++++ apps/largeclock/largeclock.json | 4 + apps/largeclock/largeclock.png | Bin 0 -> 513 bytes apps/largeclock/settings.js | 72 +++++++++++ 8 files changed, 326 insertions(+) create mode 100644 apps/largeclock/ChangeLog create mode 100644 apps/largeclock/README.md create mode 100644 apps/largeclock/largeclock-icon.js create mode 100644 apps/largeclock/largeclock.js create mode 100644 apps/largeclock/largeclock.json create mode 100644 apps/largeclock/largeclock.png create mode 100644 apps/largeclock/settings.js diff --git a/apps.json b/apps.json index e9e38a1a1..643d68a8e 100644 --- a/apps.json +++ b/apps.json @@ -1550,5 +1550,36 @@ {"name":"hidjoystick.app.js","url":"app.js"}, {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "largeclock", + "name": "Large Clock", + "icon": "largeclock.png", + "version": "0.01", + "description": "A readable and informational digital watch, with date, seconds and moon phase", + "readme": "README.md", + "tags": "clock", + "type": "clock", + "allow_emulator": true, + "storage": [ + { + "name": "largeclock.app.js", + "url": "largeclock.js" + }, + { + "name": "largeclock.img", + "url": "largeclock-icon.js", + "evaluate": true + }, + { + "name": "largeclock.settings.js", + "url": "settings.js" + }, + { + "name": "largeclock.json", + "url": "largeclock.json", + "evaluate": true + } + ] } ] diff --git a/apps/largeclock/ChangeLog b/apps/largeclock/ChangeLog new file mode 100644 index 000000000..665c0df6e --- /dev/null +++ b/apps/largeclock/ChangeLog @@ -0,0 +1 @@ +0.01: Init diff --git a/apps/largeclock/README.md b/apps/largeclock/README.md new file mode 100644 index 000000000..c9a325823 --- /dev/null +++ b/apps/largeclock/README.md @@ -0,0 +1,19 @@ +# Large clock + +A readable and informational digital watch, with date, seconds and moon phase and with programmable BTN1 & BTN3 + +## Features + +- Readable +- Informative: hours, minutes, secondsa, date, year and moon phase +- Pairs nicely with any other apps: in setting > large clock any installed app can be assigned to BTN1 and BTN3 in order to open it easily directly from the watch, without the hassle of passing trough the launcher. For example BTN1 can be assigned to alarm and BTN3 to chronometer. + +## How to use it + +- The clock can be used as any other one, if you like it just set it as the default clock app in settings > select clock +- In setting > large clock you can select which app is to be open by BTN1 and BTN3 + +## Credits + +- The clock face is heavily inspired by Big Clock byJeffmer https://jeffmer.github.io/JeffsBangleAppsDev/ +- The moon phase is basically the one from the widget https://github.com/espruino/BangleApps/tree/master/apps/widmp diff --git a/apps/largeclock/largeclock-icon.js b/apps/largeclock/largeclock-icon.js new file mode 100644 index 000000000..22aadc576 --- /dev/null +++ b/apps/largeclock/largeclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4ArmYAQCwkDC6MwFyowFC/4XKnGIAAIQFBAWDC5INCBwggEEIYXdxAODnAYCAYIgDDAQXECoIrDE4YrEBwYX/C/4X/C/4X8BwIKBAAM4DgQDBBAQDBBAIXFE4QOCA4QrCAAQHCC7wODCwYhEEAYXGACAX/C5cDCyMwC4YwSCwgA/AH4AlA=")) diff --git a/apps/largeclock/largeclock.js b/apps/largeclock/largeclock.js new file mode 100644 index 000000000..da98c6647 --- /dev/null +++ b/apps/largeclock/largeclock.js @@ -0,0 +1,198 @@ +const REFRESH_RATE = 1000; + +let interval; +let lastMoonPhase; +let lastMinutes; + +const moonR = 12; +const moonX = 215; +const moonY = 50; + +const settings = require("Storage").readJSON("largeclock.json", 1); +const BTN1app = settings.BTN1; +const BTN3app = settings.BTN3; +console.log("BTN1app", BTN1app); +console.log("BTN3app", BTN3app); + +function drawMoon(d) { + const BLACK = 0, + MOON = 0x41f, + MC = 29.5305882, + NM = 694039.09; + + var moon = { + // reset + 0: () => { + g.setColor(BLACK).fillRect( + moonX - moonR, + moonY - moonR, + moonX + moonR, + moonY + moonR + ); + }, + // new moon + 1: () => { + moon[0](); + g.setColor(MOON).drawCircle(moonX, moonY, moonR); + }, + // 1/4 ascending + 2: () => { + moon[3](); + g.setColor(BLACK).fillEllipse( + moonX - moonR / 2, + moonY - moonR, + moonX + moonR / 2, + moonY + moonR + ); + }, + // 1/2 ascending + 3: () => { + moon[0](); + g.setColor(MOON) + .fillCircle(moonX, moonY, moonR) + .setColor(BLACK) + .fillRect(moonX, moonY - moonR, moonX + moonR + moonR, moonY + moonR); + }, + // 3/4 ascending + 4: () => { + moon[7](); + g.setColor(MOON).fillEllipse( + moonX - moonR / 2, + moonY - moonR, + moonX + moonR / 2, + moonY + moonR + ); + }, + // Full moon + 5: () => { + moon[0](); + g.setColor(MOON).fillCircle(moonX, moonY, moonR); + }, + // 3/4 descending + 6: () => { + moon[3](); + g.setColor(MOON).fillEllipse( + moonX - moonR / 2, + moonY - moonR, + moonX + moonR / 2, + moonY + moonR + ); + }, + // 1/2 descending + 7: () => { + moon[0](); + g.setColor(MOON) + .fillCircle(moonX, moonY, moonR) + .setColor(BLACK) + .fillRect(moonX - moonR, moonY - moonR, moonX, moonY + moonR); + }, + // 1/4 descending + 8: () => { + moon[7](); + g.setColor(BLACK).fillEllipse( + moonX - moonR / 2, + moonY - moonR, + moonX + moonR / 2, + moonY + moonR + ); + } + }; + + function moonPhase(d) { + var tmp, + month = d.getMonth(), + year = d.getFullYear(), + day = d.getDate(); + if (month < 3) { + year--; + month += 12; + } + tmp = (365.25 * year + 30.6 * ++month + day - NM) / MC; + return Math.round((tmp - (tmp | 0)) * 7 + 1); + } + + const currentMoonPhase = moonPhase(d); + if (currentMoonPhase != lastMoonPhase) { + moon[currentMoonPhase](); + lastMoonPhase = currentMoonPhase; + } +} + +function drawTime(d) { + const da = d.toString().split(" "); + const time = da[4].substr(0, 5).split(":"); + const dow = da[0]; + const month = da[1]; + const day = da[2]; + const year = da[3]; + const hours = time[0]; + const minutes = time[1]; + const seconds = d.getSeconds(); + if (minutes != lastMinutes) { + g.clearRect(0, 24, moonX - moonR - 10, 239); + g.setColor(1, 1, 1); + g.setFontAlign(-1, -1); + g.setFont("Vector", 100); + g.drawString(hours, 50, 24, true); + g.setColor(1, 50, 1); + g.drawString(minutes, 50, 135, true); + g.setFont("Vector", 20); + g.setRotation(3); + g.drawString(`${dow} ${day} ${month}`, 50, 15, true); + g.drawString(year, 75, 205, true); + lastMinutes = minutes; + } + g.setRotation(0); + g.setFont("Vector", 20); + g.setColor(1, 1, 1); + g.setFontAlign(0, -1); + g.clearRect(200, 210, 240, 240); + g.drawString(seconds, 215, 215); +} + +function drawClockFace() { + const d = new Date(); + drawTime(d); + drawMoon(d); +} + +Bangle.on("lcdPower", function(on) { + if (on) { + g.clear(); + Bangle.drawWidgets(); + drawClockFace(); + interval = setInterval(drawClockFace, REFRESH_RATE); + } else { + clearInterval(interval); + lastMinutes = undefined; + lastMoonPhase = undefined; + } +}); + +Bangle.setLCDMode(); + +// Show launcher when middle button pressed +clearWatch(); +setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" }); +setWatch( + function() { + load(BTN1app); + }, + BTN1, + { repeat: false, edge: "rising" } +); +setWatch( + function() { + load(BTN3app); + }, + BTN3, + { repeat: false, edge: "rising" } +); + +g.clear(); +clearInterval(); +drawClockFace(); +interval = setInterval(drawClockFace, REFRESH_RATE); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/largeclock/largeclock.json b/apps/largeclock/largeclock.json new file mode 100644 index 000000000..7c38d59de --- /dev/null +++ b/apps/largeclock/largeclock.json @@ -0,0 +1,4 @@ +{ + "BTN1": "timer.app.js", + "BTN3": "calendar.app.js" +} diff --git a/apps/largeclock/largeclock.png b/apps/largeclock/largeclock.png new file mode 100644 index 0000000000000000000000000000000000000000..32e87e76810c1dc23ef3135bb2a7725eff0fa299 GIT binary patch literal 513 zcmV+c0{;DpP)~Kz}4g%!===mgrd z4d4pELAsU%zQEAYJY3(DibMXP5WqL@J^`$djz3vuV?C?YTBk?v!D>e6)28k1GY)FZ=6~| zB2S~`x&zN;ze3K^i%8XQP%-R<-Xyi81NZ{|8;H6^^{kxK**#9JIXizNlMWDd*`D8` zRA`C67T-SP<14^CD=v86%g^W}br { + var a = s.readJSON(app, 1); + return ( + a && { + n: a.name, + t: a.type, + src: a.src + } + ); + }) + .filter(app => app && (app.t == "app" || app.t == "clock" || !app.t)) + .map(a => { + return { n: a.n, src: a.src }; + }); + apps.sort((a, b) => { + if (a.n < b.n) return -1; + if (a.n > b.n) return 1; + return 0; + }); + + const settings = s.readJSON("largeclock.json", 1) || { + BTN1: "", + BTN3: "" + }; + + function showApps(btn) { + function format(v) { + return v === settings[btn] ? "*" : ""; + } + + function onchange(v) { + settings[btn] = v; + s.write("largeclock.json", settings); + } + + const btnMenu = { + "": { + title: `Apps for ${btn}` + }, + "< Back": () => E.showMenu(mainMenu) + }; + + if (apps.length > 0) { + for (let i = 0; i < apps.length; i++) { + btnMenu[apps[i].n] = { + value: apps[i].src, + format: format, + onchange: onchange + }; + } + } else { + btnMenu["...No Apps..."] = { + value: undefined, + format: () => "", + onchange: () => {} + }; + } + return E.showMenu(btnMenu); + } + + const mainMenu = { + "": { title: "Large Clock Settings" }, + "< Back": back, + "BTN1 app": () => showApps("BTN1"), + "BTN3 app": () => showApps("BTN3") + }; + E.showMenu(mainMenu); +});