From bdb681d8486b18e18f2599ba30919fc9f0af0954 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Tue, 3 Sep 2024 00:41:50 +0200 Subject: [PATCH] Add dutchclock app --- apps/dutchclock/app-icon.js | 1 + apps/dutchclock/app.js | 228 ++++++++++++++++++++++++++++++++++ apps/dutchclock/app.png | Bin 0 -> 1436 bytes apps/dutchclock/metadata.json | 19 +++ apps/dutchclock/settings.js | 61 +++++++++ 5 files changed, 309 insertions(+) create mode 100644 apps/dutchclock/app-icon.js create mode 100644 apps/dutchclock/app.js create mode 100644 apps/dutchclock/app.png create mode 100644 apps/dutchclock/metadata.json create mode 100644 apps/dutchclock/settings.js diff --git a/apps/dutchclock/app-icon.js b/apps/dutchclock/app-icon.js new file mode 100644 index 000000000..7d6e655e8 --- /dev/null +++ b/apps/dutchclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/AE0/Ao/4sccAoX79NtAofttIFD8dsAof3t1/GZ397oGE/YLE6IFDloFE1vbAoeNAondAon/z4FE356U/nNxhZC/drlpLDscNAoX4ue9C4f3L4oAKt4FEQ4qxE/0skIGDtg7DAoNtAocsAogAX94POA")) \ No newline at end of file diff --git a/apps/dutchclock/app.js b/apps/dutchclock/app.js new file mode 100644 index 000000000..a25d349fa --- /dev/null +++ b/apps/dutchclock/app.js @@ -0,0 +1,228 @@ +const SCREEN_WIDTH = g.getWidth(); +const SCREEN_HEIGHT = g.getHeight(); +const MINS_IN_HOUR = 60; + +const VARIANT_EXACT = 'exact'; +const VARIANT_APPROXIMATE = 'approximate'; +const VARIANT_HYBRID = 'hybrid'; + +const SETTINGS_FILE = "dutchclock.json"; + +let date, mins; + +var settings = Object.assign({ + // default values + variant: VARIANT_APPROXIMATE, + showWidgets: true +}, require('Storage').readJSON(SETTINGS_FILE, true) || {}); + +const maxFontSize = SCREEN_HEIGHT + - 20 // For the date + - (settings.showWidgets ? 25 : 0); + +const X = SCREEN_WIDTH / 2; +const Y = SCREEN_HEIGHT / 2 + + (settings.showWidgets ? 5 : -5); + + +function initialize() { + // draw immediately at first + tick(); + +// for (let mm = 0; mm < 60; mm++) { +// console.log(23, mm); +// console.log(getTimeLines(23 * 60 + mm)); +// } + + // now check every second + let secondInterval = setInterval(tick, 1000); + // Stop updates when LCD is off, restart when on + + Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(tick, 1000); + draw(); // draw immediately + } + }); +} + +function tick() { + date = new Date(); + const m = date.getHours() * MINS_IN_HOUR + date.getMinutes(); + + if (m !== mins) { + mins = m; + draw(); + } +} + +function draw() { + // work out how to display the current time + const timeLines = getTimeLines(mins); + + // Reset the state of the graphics library + g.clear(true); + // draw the current time (4x size 7 segment) + setFont(timeLines); + + g.setFontAlign(0,0); // align center top + g.drawString(timeLines.join("\n"), X, Y, true /*clear background*/); + + // draw the date, in a normal font + g.setFont("6x8"); + g.setFontAlign(0,1); // align center bottom + // pad the date - this clears the background if the date were to change length + var dateStr = " "+require("locale").date(date)+" "; + g.drawString(dateStr, SCREEN_WIDTH/2, SCREEN_HEIGHT - 5, true /*clear background*/); + + /* Show launcher when middle button pressed + This should be done *before* Bangle.loadWidgets so that + widgets know if they're being loaded into a clock app or not */ + Bangle.setUI("clock"); + + if (settings.showWidgets) { + // Load widgets + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } +} + +function setFont(timeLines) { + const size = maxFontSize / timeLines.length; + + g.setFont("Vector", size); + + let width = g.stringWidth(timeLines.join('\n')); + + if (width > SCREEN_WIDTH) { + g.setFont("Vector", Math.floor(size * (SCREEN_WIDTH / width))); + } +} + +function getTimeLines(m) { + switch (settings.variant) { + case VARIANT_EXACT: + return getExactTimeLines(m); + case VARIANT_APPROXIMATE: + return getApproximateTimeLines(m); + case VARIANT_HYBRID: + return distanceFromNearest(15)(m) < 3 + ? getApproximateTimeLines(m) + : getExactTimeLines(m); + default: + console.warn(`Error in settings: unknown variant "${settings.variant}"`); + return getExactTimeLines(m); + } +} + +function getExactTimeLines(m) { + const hour = getHour(m); + const minutes = getMinutes(hour.offset); + + const lines = minutes.concat(hour.lines); + if (lines.length === 1) { + lines.push('uur'); + } + + return lines; +} + +function getApproximateTimeLines(m) { + const roundMinutes = getRoundMinutes(m); + const hour = getHour(roundMinutes.minutes); + + const minutes = getMinutes(hour.offset); + + const lines = minutes.concat(hour.lines); + + if (lines.length === 1) { + lines.push('uur'); + } + + return addApproximateDescription(lines, roundMinutes.offset); +} + +function getHour(minutes) { + const hours = ['twaalf', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen', 'tien', 'elf']; + + const h = Math.floor(minutes / MINS_IN_HOUR), m = minutes % MINS_IN_HOUR; + + if (m <= 15) { + return {lines: [hours[h % 12]], offset: m}; + } + + if (m > 15 && m < 45) { + return { + lines: ['half', hours[(h + 1) % 12]], + offset: m - (MINS_IN_HOUR / 2) + }; + } + + return {lines: [hours[(h + 1) % 12]], offset: m - MINS_IN_HOUR}; +} + +function getMinutes(m) { + const minutes = ['', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen', 'tien', 'elf', 'twaalf', 'dertien', 'veertien', 'kwart']; + + if (m === 0) { + return []; + } + + return [minutes[Math.abs(m)], m > 0 ? 'over' : 'voor']; +} + +function getRoundMinutes(m) { + const nearest = roundTo(5)(m); + + return { + minutes: nearest, + offset: m - nearest + }; +} + +function addApproximateDescription(lines, offset) { + if (offset === 0) { + return lines; + } + + if (lines[1] === 'uur') { + const plural = getPlural(lines[0]); + return { + '-2': ['tegen', plural], + '-1': ['iets voor', plural], + '1': ['iets na', plural], + '2': ['even na', plural] + }[`${offset}`]; + } + + return { + '-2': ['bijna'].concat(lines), + '-1': ['rond'].concat(lines), + '1': ['iets na'].concat(lines), + '2': lines.concat(['geweest']) + }[`${offset}`]; +} + +function getPlural(h) { + return { + een: 'enen', + twee: 'tweeën', + drie: 'drieën', + vijf: 'vijven', + zes: 'zessen', + elf: 'elven', + twaalf: 'twaalven' + }[h] || `${h}en`; +} + +function distanceFromNearest(x) { + return n => Math.abs(n - roundTo(x)(n)); +} + +function roundTo(x) { + return n => Math.round(n / x) * x; +} + +initialize(); diff --git a/apps/dutchclock/app.png b/apps/dutchclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..faacb7eaf13b86d8144a49f9afa9f0d9a49958ce GIT binary patch literal 1436 zcmV;N1!MY&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1u;oPK~!i%?U`99 zbzc;~m*HMhd_)! zl(CD(UB&boW;HSA-?IPi@z7p~uy9|KIyj{$NS z92}&NR7`Hb#KZ)Bq$0_HU3RptiP_R;Qz*1Du?kXl`g|s4z%ae=`W$0cwHzFc} z3`94>vB1DUnj0J(EXmG_ke03OKCDLQ$`E#xujIsje=H^tG8DeE+MH{-Utqph>r7Obs%U^Tl@KZ{dn*XN( zO-)Ty>v(y2Q3;KdmzP6&dOFFSot+RK9!~Ps))qA_xF3p(iz)kJ#>R3S_k*vmFRZMr z&|FF29{32nKx1}xc5HTbmU>+5Z4q8N=o1;w4f_5!FE5XALs?Bt4J9yMcCg{$Va8iZ z#xpeIg`K`mlEQ}#z|8CF>iUq$GN9@q-VG2B49~;KMG2 zZi9VoK|ui&78btirn0iKpr@yYdSFLKN7CWAr>7@0G&E3wmy?r2dShYnn(kTI;Njr`RaI4V!hoPZ9~>Nzao*nEq>GM@rn4WcMzMv&{eY(tSg9r^Cej=^{Nw>s zBg+8vX$+@(dwX=BpPx^Cz*i#60Q5erLPamk$jG20ldnXU0psK2bZu{M2j0ApEP`z$ z9zWu-qG%w>nB=fKJ3KrjoosLuOQo!=jAA>FN|eZWh-5rmvZtpf=I7_fA|oRi51~x0 zR?~dU3BP{*V#&$L%-PwQ4us_*YXvawBhAgtbWSRQJ@ESaIvuOv(Yi*X5k`RW!O+kU zo#gfR_X}F-$T9%ydaQBqAq^4rxF2w1t5hmshH(=q0ucPoD_2PH_d`Tz;SuVtz7nqxy1D#F>cXxMGTHuDo3KTmve5J%= zKWuDlkOAlC=kz@+uP-kzVS9TUDk>^SCly&MfTwV1e_C1^ { + writeOption(setting, value); + } + }; + } + + // Show the menu + function showMainMenu() { + const mainMenu = [ + getOption('Exact', 'variant', VARIANT_EXACT), + getOption('Approximate', 'variant', VARIANT_APPROXIMATE), + getOption('Hybrid', 'variant', VARIANT_HYBRID), + { + title: 'Show widgets?', + value: settings.showWidgets ?? true, + onchange: v => writeSetting('showWidgets', v) + } + ]; + + mainMenu[""] = { + title : "Dutch Clock", + back: back + }; + + E.showMenu(mainMenu); + } + + showMainMenu(); + }) \ No newline at end of file