From a99f21aab7f7f5cfd461a0767a3670bc2a6d8655 Mon Sep 17 00:00:00 2001 From: paul-arg Date: Sat, 23 Mar 2024 16:56:11 +0100 Subject: [PATCH] initial Elapsed Time Clock commit Update metadata.json Add files via upload Update README.md Update README.md Update README.md Add files via upload Update metadata.json Update app.js Update app.js Update settings.js Update app-icon.js Update metadata.json Update metadata.json Update README.md Add files via upload Create metadata.json --- apps/elapsed_t/ChangeLog | 1 + apps/elapsed_t/README.md | 22 ++ apps/elapsed_t/app-icon.js | 1 + apps/elapsed_t/app.js | 438 +++++++++++++++++++++++++++++++++ apps/elapsed_t/app.png | Bin 0 -> 2264 bytes apps/elapsed_t/metadata.json | 21 ++ apps/elapsed_t/screenshot1.png | Bin 0 -> 3719 bytes apps/elapsed_t/screenshot2.png | Bin 0 -> 3323 bytes apps/elapsed_t/screenshot3.png | Bin 0 -> 3893 bytes apps/elapsed_t/settings.js | 55 +++++ 10 files changed, 538 insertions(+) create mode 100644 apps/elapsed_t/ChangeLog create mode 100644 apps/elapsed_t/README.md create mode 100644 apps/elapsed_t/app-icon.js create mode 100644 apps/elapsed_t/app.js create mode 100644 apps/elapsed_t/app.png create mode 100644 apps/elapsed_t/metadata.json create mode 100644 apps/elapsed_t/screenshot1.png create mode 100644 apps/elapsed_t/screenshot2.png create mode 100644 apps/elapsed_t/screenshot3.png create mode 100644 apps/elapsed_t/settings.js diff --git a/apps/elapsed_t/ChangeLog b/apps/elapsed_t/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/elapsed_t/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/elapsed_t/README.md b/apps/elapsed_t/README.md new file mode 100644 index 000000000..b085cd70f --- /dev/null +++ b/apps/elapsed_t/README.md @@ -0,0 +1,22 @@ +# Elapsed Time Clock +A clock that calculates the time difference between now (in blue) and any given target date (in red). + +The results is show in years, months, days, hours, minutes, seconds. To save battery life, the seconds are shown only when the watch is unlocked, or can be disabled entirely. + +The time difference is positive if the target date is in the past and negative if it is in the future. + +![Screenshot 1](screenshot1.png) +![Screenshot 2](screenshot2.png) +![Screenshot 3](screenshot3.png) + +# Settings +## Time and date formats: +- time can be shown in 24h or in AM/PM format +- date can be shown in DD/MM/YYYY, MM/DD/YYYY or YYYY-MM-DD format + +## Display years and months +You can select if the difference is shown with years, months and days, or just days. + +# TODO +- add the option to set an alarm to the target date +- add an offset to said alarm (e.g. x hours/days... before/after) diff --git a/apps/elapsed_t/app-icon.js b/apps/elapsed_t/app-icon.js new file mode 100644 index 000000000..0e9a434fc --- /dev/null +++ b/apps/elapsed_t/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcA/4A/AH8kyVJARAQE/YRLn4RD/IRT5cs2QCEEbQgFAQYjIrMlAQwjR5JHIsv2pNkz3JsgjKl/yEAO/I5l/+REBz/7I5f/EYf/I5Vf//2rNlz//8gjJAgIjE/hHIy7xEAAQjIDoIAG+RHHCA///wjHCJIjHMoI1HEY+zCI6zJv4dCFIX9R5PPR4vsEZNJCILXC/77JyXLn4jD/b7KpMnI4fZBARHHpcsEYW2AQIjKARBHIDoICECJIjRpZKCAQYjbCMH/CJVLCAgA/AHYA==")) diff --git a/apps/elapsed_t/app.js b/apps/elapsed_t/app.js new file mode 100644 index 000000000..8237ef32c --- /dev/null +++ b/apps/elapsed_t/app.js @@ -0,0 +1,438 @@ +const APP_NAME = "elapsed_t"; + +const COLOUR_BLACK = 0x0; +const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25) +const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5) +const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75) +const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0) +const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1) +const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0) +const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1) +const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0) +const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5) +const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0) + +const SCREEN_WIDTH = g.getWidth(); +const SCREEN_HEIGHT = g.getHeight(); +const BIG_FONT_SIZE = 38; +const SMALL_FONT_SIZE = 22; + +var arrowFont = atob("BwA4AcAOAHADgBwA4McfOf3e/+P+D+A+AOA="); // contains only the > character + +var now = new Date(); + +var settings = Object.assign({ + // default values + displaySeconds: true, + displayMonthsYears: true, + dateFormat: 0, + time24: true +}, require('Storage').readJSON(APP_NAME + ".settings.json", true) || {}); + +var temp_displaySeconds = settings.displaySeconds; + +var data = Object.assign({ + // default values + target: { + isSet: false, + Y: now.getFullYear(), + M: now.getMonth() + 1, // Month is zero-based, so add 1 + D: now.getDate(), + h: now.getHours(), + m: now.getMinutes(), + s: now.getSeconds() + } +}, require('Storage').readJSON(APP_NAME + ".data.json", true) || {}); + +function writeData() { + require('Storage').writeJSON(APP_NAME + ".data.json", data); +} + +function writeSettings() { + require('Storage').writeJSON(APP_NAME + ".settings.json", settings); + temp_displaySeconds = settings.temp_displaySeconds; +} + +let inMenu = false; + +Bangle.on('touch', function (zone, e) { + if (!inMenu) { + if (drawTimeout) clearTimeout(drawTimeout); + E.showMenu(menu); + inMenu = true; + } +}); + +function pad2(number) { + return (String(number).padStart(2, '0')); +} + +function formatDateTime(date, dateFormat, time24, showSeconds) { + var formattedDateTime = { + date: "", + time: "" + }; + + var DD = pad2(date.getDate()); + var MM = pad2(date.getMonth() + 1); // Month is zero-based + var YYYY = date.getFullYear(); + var h = date.getHours(); + var hh = pad2(date.getHours()); + var mm = pad2(date.getMinutes()); + var ss = pad2(date.getSeconds()); + + switch (dateFormat) { + case 0: + formattedDateTime.date = `${DD}/${MM}/${YYYY}`; + break; + + case 1: + formattedDateTime.date = `${MM}/${DD}/${YYYY}`; + break; + + case 2: + formattedDateTime.date = `${YYYY}-${MM}-${DD}`; + break; + + default: + formattedDateTime.date = `${YYYY}-${MM}-${DD}`; + break; + } + + if (time24) { + formattedDateTime.time = `${hh}:${mm}${showSeconds ? `:${ss}` : ''}`; + } else { + var ampm = (h >= 12 ? 'PM' : 'AM'); + var h_ampm = h % 12; + h_ampm = (h_ampm == 0 ? 12 : h_ampm); + formattedDateTime.time = `${h_ampm}:${mm}${showSeconds ? `:${ss}` : ''}${ampm}`; + } + + return formattedDateTime; +} + +function howManyDaysInMonth(month, year) { + return new Date(year, month, 0).getDate(); +} + +function handleExceedingDay() { + var maxDays = howManyDaysInMonth(data.target.M, data.target.Y); + menu.Day.max = maxDays; + if (data.target.D > maxDays) { + menu.Day.value = maxDays; + data.target.D = maxDays; + } +} + +function setTarget(set) { + if (set) { + target = new Date( + data.target.Y, + data.target.M - 1, + data.target.D, + data.target.h, + data.target.m, + data.target.s + ); + data.target.isSet = true; + } else { + target = new Date(); + Object.assign( + data, + { + target: { + isSet: false, + Y: now.getFullYear(), + M: now.getMonth() + 1, // Month is zero-based, so add 1 + D: now.getDate(), + h: now.getHours(), + m: now.getMinutes(), + s: now.getSeconds() + } + } + ); + } + + writeData(); +} + +var target; +setTarget(data.target.isSet); + +var drawTimeout; +var queueMillis = 1000; + +var menu = { + "": { + "title": "Set target", + back: function () { + E.showMenu(); + Bangle.setUI("clock"); + inMenu = false; + draw(); + } + }, + 'Day': { + value: data.target.D, + min: 1, max: 31, wrap: true, + onchange: v => { + data.target.D = v; + } + }, + 'Month': { + value: data.target.M, + min: 1, max: 12, noList: true, wrap: true, + onchange: v => { + data.target.M = v; + handleExceedingDay(); + } + }, + 'Year': { + value: data.target.Y, + min: 1900, max: 2100, + onchange: v => { + data.target.Y = v; + handleExceedingDay(); + } + }, + 'Hours': { + value: data.target.h, + min: 0, max: 23, wrap: true, + onchange: v => { + data.target.h = v; + }, + format: function (v) { return pad2(v); } + }, + 'Minutes': { + value: data.target.m, + min: 0, max: 59, wrap: true, + onchange: v => { + data.target.m = v; + }, + format: function (v) { return pad2(v); } + }, + 'Seconds': { + value: 0, + min: 0, max: 59, wrap: true, + onchange: v => { + data.target.s = v; + }, + format: function (v) { return pad2(v); } + }, + 'Save': function () { + E.showMenu(); + inMenu = false; + Bangle.setUI("clock"); + setTarget(true); + writeSettings(); + temp_displaySeconds = settings.displaySeconds; + updateQueueMillis(settings.displaySeconds); + draw(); + }, + 'Reset': function () { + E.showMenu(); + inMenu = false; + Bangle.setUI("clock"); + setTarget(false); + updateQueueMillis(settings.displaySeconds); + draw(); + } +}; + +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + var delay = queueMillis - (Date.now() % queueMillis); + if (queueMillis == 60000 && signIsNegative()) { + delay += 1000; + } + + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, delay); +} + +function updateQueueMillis(displaySeconds) { + if (displaySeconds) { + queueMillis = 1000; + } else { + queueMillis = 60000; + } +} + +Bangle.on('lock', function (on, reason) { + if (on) { // screen is locked + temp_displaySeconds = false; + updateQueueMillis(false); + draw(); + } else { // screen is unlocked + temp_displaySeconds = settings.displaySeconds; + updateQueueMillis(temp_displaySeconds); + draw(); + } +}); + +function signIsNegative() { + var now = new Date(); + return (now < target); +} + +function diffToTarget() { + var diff = { + sign: "+", + Y: "0", + M: "0", + D: "0", + hh: "00", + mm: "00", + ss: "00" + }; + + if (!data.target.isSet) { + return (diff); + } + + var now = new Date(); + diff.sign = now < target ? '-' : '+'; + + if (settings.displayMonthsYears) { + var start; + var end; + + if (now > target) { + start = target; + end = now; + } else { + start = now; + end = target; + } + + diff.Y = end.getFullYear() - start.getFullYear(); + diff.M = end.getMonth() - start.getMonth(); + diff.D = end.getDate() - start.getDate(); + diff.hh = end.getHours() - start.getHours(); + diff.mm = end.getMinutes() - start.getMinutes(); + diff.ss = end.getSeconds() - start.getSeconds(); + + // Adjust negative differences + if (diff.ss < 0) { + diff.ss += 60; + diff.mm--; + } + if (diff.mm < 0) { + diff.mm += 60; + diff.hh--; + } + if (diff.hh < 0) { + diff.hh += 24; + diff.D--; + } + if (diff.D < 0) { + var lastMonthDays = new Date(end.getFullYear(), end.getMonth(), 0).getDate(); + diff.D += lastMonthDays; + diff.M--; + } + if (diff.M < 0) { + diff.M += 12; + diff.Y--; + } + + + } else { + var timeDifference = target - now; + timeDifference = Math.abs(timeDifference); + + // Calculate days, hours, minutes, and seconds + diff.D = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); + diff.hh = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + diff.mm = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); + diff.ss = Math.floor((timeDifference % (1000 * 60)) / 1000); + } + + // add zero padding + diff.hh = pad2(diff.hh); + diff.mm = pad2(diff.mm); + diff.ss = pad2(diff.ss); + + return diff; +} + +function draw() { + + var nowFormatted = formatDateTime(new Date(), settings.dateFormat, settings.time24, temp_displaySeconds); + var targetFormatted = formatDateTime(target, settings.dateFormat, settings.time24, true); + var diff = diffToTarget(); + + var diffYMD; + if (settings.displayMonthsYears) + diffYMD = `${diff.sign}${diff.Y}Y ${diff.M}M ${diff.D}D`; + else + diffYMD = `${diff.sign}${diff.D}D`; + + var diff_hhmm = `${diff.hh}:${diff.mm}`; + + g.clearRect(0, 24, SCREEN_WIDTH, SCREEN_HEIGHT); + //console.log("drawing"); + + let y = 24; //Bangle.getAppRect().y; + + // draw current date + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_BLUE); + g.drawString(nowFormatted.date, 4, y); + y += SMALL_FONT_SIZE; + + // draw current time + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_BLUE); + g.drawString(nowFormatted.time, 4, y); + y += SMALL_FONT_SIZE; + + // draw arrow + g.setFontCustom(arrowFont, 62, 16, 13).setFontAlign(-1, -1).setColor(COLOUR_RED); + g.drawString(">", 4, y + 3); + + if (data.target.isSet) { + // draw target date + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_RED); + g.drawString(targetFormatted.date, 4 + 16 + 6, y); + } else { + // draw NOT SET + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_RED); + g.drawString("NOT SET", 4 + 16 + 6, y); + } + + y += SMALL_FONT_SIZE; + + // draw target time + if (data.target.isSet) { + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_RED); + g.drawString(targetFormatted.time, 4, y); + } + y += SMALL_FONT_SIZE + 4; + + // draw separator + g.setColor(COLOUR_BLACK); + g.drawLine(0, y - 4, SCREEN_WIDTH, y - 4); + + // draw diffYMD + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(0, -1).setColor(COLOUR_BLACK); + g.drawString(diffYMD, SCREEN_WIDTH / 2, y); + y += SMALL_FONT_SIZE; + + // draw diff_hhmm + g.setFont("Vector", BIG_FONT_SIZE).setFontAlign(0, -1).setColor(COLOUR_BLACK); + g.drawString(diff_hhmm, SCREEN_WIDTH / 2, y); + + // draw diff_ss + if (temp_displaySeconds) { + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(COLOUR_GREY); + g.drawString(diff.ss, SCREEN_WIDTH / 2 + 52, y + 13); + } + + queueDraw(); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +Bangle.setUI("clock"); + +draw(); diff --git a/apps/elapsed_t/app.png b/apps/elapsed_t/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cac4fa1011902362aa04d029aaf57fd86ba646 GIT binary patch literal 2264 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 A!~g&Q literal 0 HcmV?d00001 diff --git a/apps/elapsed_t/screenshot2.png b/apps/elapsed_t/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a10ab62b2d99e0374c69b4633e7d5c987711b6 GIT binary patch literal 3323 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 back(), + 'Show\nseconds': { + value: !!settings.displaySeconds, + onchange: v => { + settings.displaySeconds = v; + writeSettings(); + } + }, + 'Show months/\nyears': { + value: !!settings.displayMonthsYears, + onchange: v => { + settings.displayMonthsYears = v; + writeSettings(); + } + }, + 'Time format': { + value: !!settings.time24, + onchange: v => { + settings.time24 = v; + writeSettings(); + }, + format: function (v) {return v ? "24h" : "AM/PM";} + }, + 'Date format': { + value: settings.dateFormat, + min: 0, max: 2, wrap: true, + onchange: v => { + settings.dateFormat = v; + writeSettings(); + }, + format: function (v) {return dateFormats[v];} + } + }); +})