diff --git a/apps.json b/apps.json index 1b5f8a868..b08f2d4be 100644 --- a/apps.json +++ b/apps.json @@ -2550,5 +2550,19 @@ {"name":"hrrawexp.app.js","url":"app.js"}, {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "lazyclock", + "name": "Lazy Clock", + "icon": "lazyclock.png", + "version":"0.01", + "readme": "README.md", + "description": "Tells the time, roughly", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, + {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} + ] } ] diff --git a/apps/lazyclock/ChangeLog b/apps/lazyclock/ChangeLog new file mode 100644 index 000000000..1fc732a04 --- /dev/null +++ b/apps/lazyclock/ChangeLog @@ -0,0 +1 @@ +0.01: Launch app \ No newline at end of file diff --git a/apps/lazyclock/README.md b/apps/lazyclock/README.md new file mode 100644 index 000000000..5f10b1707 --- /dev/null +++ b/apps/lazyclock/README.md @@ -0,0 +1,26 @@ +# Lazy clock + +This clock gives you the time (roughly). + +* 11:05 becomes 'About eleven' +* 15:34 becomes 'Just gone half three' +* 04:12 becomes 'Around quarter past four' + +Phrases have a 10 min 'resolution': +* 10:00 - 10:09: past the hour, +* 10:10 - 10:19: about quarter past +* 10:20 - 10:29: nearly half past +* 10:30 - 10:39: just gone half past +* 10:40 - 10:49: about quarter to +* 10:50 - 10:59: almost the hour + +Various phrases and combinations for each time chunk are provided. + +## Usage + +* Press BTN-1 to see the actual time and date +* Press BTN-3 to cycle through lazy descriptions + +## Attributions + +Icon from https://icons8.com diff --git a/apps/lazyclock/lazyclock-app.js b/apps/lazyclock/lazyclock-app.js new file mode 100644 index 000000000..cd7edf329 --- /dev/null +++ b/apps/lazyclock/lazyclock-app.js @@ -0,0 +1,236 @@ +let secondInterval; +let showRealTime = false; + +const utils = { + random: function(items) { + return items[~~(items.length * Math.random())]; + }, + + oneIn: function(chance) { + return Math.floor(Math.random() * Math.floor(chance + 1)) === chance; + }, + + hours2Word: function(hours, minutes) { + const numbers = [ + 'twelve', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'eleven', + 'twelve', + ]; + + let adjustedHours = hours; + + if (minutes > 40) { + adjustedHours += 1; + } + + if (adjustedHours > 12) { + adjustedHours -= 12; + } + + return numbers[adjustedHours]; + }, + + print: function(str) { + let fontSize = 4; + const width = g.getWidth(); + const height = g.getHeight() - 48; + const lines = str.split(`\n`).length; + let totalHeight; + + do { + g.setFont("6x8", fontSize); + totalHeight = g.getFontHeight() * lines; + if (fontSize === 1 || (g.stringWidth(str) < width && totalHeight < height)) { + break; + } + fontSize--; + + } while (true); + + const x = width / 2; + + const y = (g.getHeight() / 2) - (g.getFontHeight() * ((lines - 1) / 2)); + g.drawString(str, x, y < 25 ? 24 : y); + } +}; + +const words = { + approx: ['\'Bout', 'About', 'Around', `Summat\nlike`, 'Near', 'Close to'], + approach: ['Nearly', `Coming\nup to`, 'Approaching', `A touch\nbefore`], + past: [`A shade\nafter`, `A whisker\nafter`, 'Just gone'], + quarter: ['Quarter', `Fifteen\nminutes`], + half: ['Half', 'Half past'], + exactly: ['exactly', 'on the dot', 'o\' clock'], + ish: ['-ish', `\n(ish)`] +}; + +function switchMode() { + showRealTime = !showRealTime; + refreshTime(); +} + +function drawRealTime(date) { + const pad = (number) => `0${number}`.substr(-2); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + + g.setFontAlign(0,1); + g.setFont("6x8", 8); + g.drawString(`${hours}:${minutes}`, g.getWidth() / 2, g.getHeight() / 2); + + g.setFont("6x8", 3); + g.setFontAlign(0, -1); + g.drawString(date.toISOString().split('T')[0], g.getWidth() / 2, g.getHeight() / 2); + +} + +function drawDumbTime(time) { + const hours = time.getHours(); + const minutes = time.getMinutes(); + + function formatTime(hours, minutes) { + const makeApprox = (str, template) => { + let _template = template || 'approx'; + if (utils.oneIn(2)) { + _template = 'approx'; + + if (utils.oneIn(words.approx.length)) { + const ish = utils.random(words.ish); + return `${str}${ish}`; + } + } + + const approx = `${utils.random(words[_template])} `; + + return `${approx}\n${str.toLowerCase()}`; + }; + + const formatters = { + 'onTheHour': (hoursAsWord) => { + const exactly = utils.random(words.exactly); + + return `${hoursAsWord}\n${exactly}`; + }, + 'nearTheHour': (hoursAsWord) => { + const template = (minutes < 10) ? 'past' : 'approach'; + + return makeApprox(hoursAsWord, template); + }, + 'nearQuarter': (hoursAsWord, minutes) => { + const direction = (minutes > 30) ? 'to' : 'past'; + const quarter = utils.random(words.quarter); + + const formatted = `${quarter} ${direction}\n${hoursAsWord}`; + + return (minutes === 15 || minutes === 45) ? formatted : makeApprox(formatted); + }, + 'nearHalf': (hoursAsWord, minutes) => { + const half = utils.random(words.half); + + const formatted = `${half}\n${hoursAsWord}`; + + const template = (minutes > 30) ? 'past' : 'approach'; + return (minutes === 30) ? formatted : makeApprox(formatted, template); + }, + }; + + function getFormatter(hours, minutes) { + if (minutes === 0) { + return formatters.onTheHour; + } else if (minutes > 50 || minutes < 10) { + return formatters.nearTheHour; + } else if (minutes > 40|| minutes < 20) { + return formatters.nearQuarter; + } else { + return formatters.nearHalf; + } + } + + const hoursAsWord = utils.hours2Word(hours, minutes); + + const formatter = getFormatter(hours, minutes); + + return formatter(hoursAsWord, minutes); + } + + utils.print(formatTime(hours, minutes)); +} + +function cancelTimeout() { + if (secondInterval) { + clearTimeout(secondInterval); + } + + secondInterval = undefined; +} + +function refreshTime() { + cancelTimeout(); + + g.clearRect(0, 24, g.getWidth(), g.getHeight()-24); + g.reset(); + g.setFontAlign(0,0); + + const time = new Date(); + + const method = showRealTime ? drawRealTime : drawDumbTime; + + method(time); + + const secondsTillRefresh = 60 - time.getSeconds(); + + secondInterval = setTimeout(refreshTime, secondsTillRefresh * 1000); +} + + +function startClock() { + const secondsToRefresh = refreshTime(); +} + +function addEvents() { + Bangle.on('lcdPower', (on) => { + cancelTimeout(); + if (on) { + startClock(); + } + }); + + setWatch(switchMode, BTN1, { + repeat: true, + edge: "falling" + }); + + setWatch(Bangle.showLauncher, BTN2, { + repeat: false, + edge: "falling" + }); + + + setWatch(refreshTime, BTN3, { + repeat: true, + edge: "falling" + }); +} + +function init() { + g.clear(); + + startClock(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + addEvents(); +} + + +init(); diff --git a/apps/lazyclock/lazyclock-icon.js b/apps/lazyclock/lazyclock-icon.js new file mode 100644 index 000000000..0ba6eb144 --- /dev/null +++ b/apps/lazyclock/lazyclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A1iIXXDCwXYF1kBIzEQXlfdC9sNC6oWB6BFVFy5dWIqoXV93u8ArThwXB9wLHhGIAAeAFwwwJCwgABC54uFC5XoHQoXQnBTFCwwkFlwXClAjFFhI7CwYWB8YOFRpYMCkfjFwQPDLpYMHABQwFC6IwETQ4TIGAwXOCQIQDIyIRFGARyRGAQXQRIwXCDoTGGXJAKBeQwA/AH4A6A")) \ No newline at end of file diff --git a/apps/lazyclock/lazyclock.png b/apps/lazyclock/lazyclock.png new file mode 100644 index 000000000..4991cd2db Binary files /dev/null and b/apps/lazyclock/lazyclock.png differ