Merge pull request #3560 from whenlit/master

New app: Dutch clock
pull/3563/head
thyttan 2024-09-07 22:59:41 +02:00 committed by GitHub
commit e353a4b6f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 391 additions and 0 deletions

View File

@ -0,0 +1 @@
0.20: First release

22
apps/dutchclock/README.md Normal file
View File

@ -0,0 +1,22 @@
# Dutch Clock
This clock shows the time, in words, the way a Dutch person might respond when asked what time it is. Useful when learning Dutch and/or pretending to know Dutch.
Dedicated to my wife, who will sometimes insist I tell her exactly what time it says on the watch and not just an approximation.
## Options
- Three modes:
- exact time ("zeven voor half zes / twee voor tien")
- approximate time, rounded to the nearest 5-minute mark ("bijna vijf voor half zes / tegen tienen") (the default)
- hybrid mode, rounded when close to the quarter marks and exact otherwise ("zeven voor half zes / tegen tienen")
- Option to turn top widgets on/off (on by default)
- Option to show digital time at the bottom (off by default)
- Option to show the date at the bottom (on by default)
The app respects top and bottom widgets, but it gets a bit crowded when you add the time/date and you also have bottom widgets turned on.
When you turn widgets off, you can still see the top widgets by swiping down from the top.
## Screenshots
![](screenshotbangle1-2.png)
![](screenshotbangle2.png)
![](screenshotbangle1.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/AE0/Ao/4sccAoX79NtAofttIFD8dsAof3t1/GZ397oGE/YLE6IFDloFE1vbAoeNAondAon/z4FE356U/nNxhZC/drlpLDscNAoX4ue9C4f3L4oAKt4FEQ4qxE/0skIGDtg7DAoNtAocsAogAX94POA"))

260
apps/dutchclock/app.js Normal file
View File

@ -0,0 +1,260 @@
// Load libraries
const storage = require("Storage");
const locale = require('locale');
const widget_utils = require('widget_utils');
// Define constants
const DATETIME_SPACING_HEIGHT = 5;
const TIME_HEIGHT = 8;
const DATE_HEIGHT = 8;
const BOTTOM_SPACING = 2;
const MINS_IN_HOUR = 60;
const MINS_IN_DAY = 24 * MINS_IN_HOUR;
const VARIANT_EXACT = 'exact';
const VARIANT_APPROXIMATE = 'approximate';
const VARIANT_HYBRID = 'hybrid';
const DEFAULTS_FILE = "dutchclock.default.json";
const SETTINGS_FILE = "dutchclock.json";
// Load settings
const settings = Object.assign(
storage.readJSON(DEFAULTS_FILE, true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
// Define global variables
const textBox = {};
let date, mins;
// Define functions
function initialize() {
// Reset the state of the graphics library
g.clear(true);
// Tell Bangle this is a clock
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
// Show widgets, or not
if (settings.showWidgets) {
Bangle.drawWidgets();
} else {
widget_utils.swipeOn();
}
const dateTimeHeight = (settings.showDate || settings.showTime ? DATETIME_SPACING_HEIGHT : 0)
+ (settings.showDate ? DATE_HEIGHT : 0)
+ (settings.showTime ? TIME_HEIGHT : 0);
Object.assign(textBox, {
x: Bangle.appRect.x + Bangle.appRect.w / 2,
y: Bangle.appRect.y + (Bangle.appRect.h - dateTimeHeight) / 2,
w: Bangle.appRect.w - 2,
h: Bangle.appRect.h - dateTimeHeight
});
// draw immediately at first
tick();
// 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()) % MINS_IN_DAY;
if (m !== mins) {
mins = m;
draw();
}
}
function draw() {
// work out how to display the current time
const timeLines = getTimeLines(mins);
const bottomLines = getBottomLines();
g.reset().clearRect(Bangle.appRect);
// draw the current time (4x size 7 segment)
setFont(timeLines);
g.setFontAlign(0,0); // align center top
g.drawString(timeLines.join("\n"), textBox.x, textBox.y, false);
if (bottomLines.length) {
// draw the time and/or 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
g.drawString(bottomLines.join('\n'), Bangle.appRect.w / 2, Bangle.appRect.y2 - BOTTOM_SPACING, false);
}
}
function setFont(timeLines) {
const size = textBox.h / timeLines.length;
g.setFont("Vector", size);
let width = g.stringWidth(timeLines.join('\n'));
if (width > textBox.w) {
g.setFont("Vector", Math.floor(size * (textBox.w / width)));
}
}
function getBottomLines() {
const lines = [];
if (settings.showTime) {
lines.push(locale.time(date, 1));
}
if (settings.showDate) {
lines.push(locale.date(date));
}
return lines;
}
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) {
if (m === 0) {
return ['middernacht'];
}
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 lines = getExactTimeLines(roundMinutes.minutes);
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 % MINS_IN_DAY,
offset: m - nearest
};
}
function addApproximateDescription(lines, offset) {
if (offset === 0) {
return lines;
}
if (lines.length === 1 || lines[1] === 'uur') {
const singular = lines[0];
const plural = getPlural(singular);
return {
'-2': ['tegen', plural],
'-1': ['iets voor', singular],
'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 {
middernacht: 'middernacht',
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;
}
// Let's go
initialize();

BIN
apps/dutchclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,6 @@
{
"variant": "approximate",
"showWidgets": true,
"showTime": false,
"showDate": true
}

View File

@ -0,0 +1,28 @@
{
"id": "dutchclock",
"name": "Dutch Clock",
"shortName":"Dutch Clock",
"icon": "app.png",
"version":"0.20",
"description": "A clock that displays the time the way a Dutch person would respond when asked what time it is.",
"type": "clock",
"tags": "clock,dutch,text",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"screenshots": [
{"url":"screenshotbangle1-2.png"},
{"url":"screenshotbangle2.png"},
{"url":"screenshotbangle1.png"}
],
"storage": [
{"name":"dutchclock.app.js","url":"app.js"},
{"name":"dutchclock.settings.js","url":"settings.js"},
{"name":"dutchclock.default.json","url":"default.json"},
{"name":"dutchclock.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"dutchclock.json"}
],
"readme":"README.md"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,73 @@
(function(back) {
const storage = require("Storage");
const VARIANT_EXACT = 'exact';
const VARIANT_APPROXIMATE = 'approximate';
const VARIANT_HYBRID = 'hybrid';
const DEFAULTS_FILE = "dutchclock.default.json";
const SETTINGS_FILE = "dutchclock.json";
// Load settings
const settings = Object.assign(
storage.readJSON(DEFAULTS_FILE, true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
function writeSettings() {
require('Storage').writeJSON(SETTINGS_FILE, settings);
}
function writeSetting(setting, value) {
settings[setting] = value;
writeSettings();
}
function writeOption(setting, value) {
writeSetting(setting, value);
showMainMenu();
}
function getOption(label, setting, value) {
return {
title: label,
value: settings[setting] === value,
onchange: () => {
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,
onchange: v => writeSetting('showWidgets', v)
},
{
title: 'Show time?',
value: settings.showTime,
onchange: v => writeSetting('showTime', v)
},
{
title: 'Show date?',
value: settings.showDate,
onchange: v => writeSetting('showDate', v)
}
];
mainMenu[""] = {
title : "Dutch Clock",
back: back
};
E.showMenu(mainMenu);
}
showMainMenu();
})