mirror of https://github.com/espruino/BangleApps
Add Mystic Clock v1.00
parent
d570261e47
commit
111dd0b898
15
apps.json
15
apps.json
|
@ -3258,5 +3258,20 @@
|
||||||
{"name":"gbtwist.app.js","url":"app.js"},
|
{"name":"gbtwist.app.js","url":"app.js"},
|
||||||
{"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
|
{"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{ "id": "mysticclock",
|
||||||
|
"name": "Mystic Clock",
|
||||||
|
"icon": "mystic-clock.png",
|
||||||
|
"version":"1.00",
|
||||||
|
"description": "A retro-inspired watchface featuring time, date, and an interactive data display line.",
|
||||||
|
"tags": "clock",
|
||||||
|
"type":"clock",
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"mysticclock.app.js","url":"mystic-clock-app.js"},
|
||||||
|
{"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"},
|
||||||
|
{"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
1.00: First published version.
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Mystic Clock for Bangle.js
|
||||||
|
|
||||||
|
A retro-inspired watchface featuring time, date, and an interactive data display line.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 24 or 12-hour time (adjustable via the Settings menu)
|
||||||
|
- Variable colors (also in the Settings)
|
||||||
|
- Interactive data display line (use upper and lower watch-buttons to rotate between values)
|
||||||
|
- Cover watch screen with your hand to put it to sleep (the watch, not your hand)
|
||||||
|
- International localization of date (which can be disabled via the Settings if memory becomes an issue)
|
||||||
|
|
||||||
|
The interactive line rotates between the following items:
|
||||||
|
|
||||||
|
- Current time zone
|
||||||
|
- Battery charge level
|
||||||
|
- Device ID (derived from the last 4 of the MAC)
|
||||||
|
- Memory usage
|
||||||
|
- Firmware version
|
||||||
|
|
||||||
|
|
||||||
|
## Inspirations
|
||||||
|
|
||||||
|
- [CLI Clock](https://github.com/espruino/BangleApps/tree/master/apps/cliock)
|
||||||
|
- [Dev Clock](https://github.com/espruino/BangleApps/tree/master/apps/dclock)
|
||||||
|
- [Digital Clock](https://github.com/espruino/BangleApps/tree/master/apps/digiclock)
|
||||||
|
- [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock)
|
||||||
|
- [Simplest Clock](https://github.com/espruino/BangleApps/tree/master/apps/simplest)
|
||||||
|
|
||||||
|
Icon adapted from [Public Domain Vectors](https://publicdomainvectors.org/en/free-clipart/Digital-clock-display-vector-image/10845.html).
|
||||||
|
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- 1.00: First published version. (June 2021)
|
||||||
|
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Eric Wooodward https://itsericwoodward.com/
|
|
@ -0,0 +1,215 @@
|
||||||
|
/**
|
||||||
|
* Mystic Clock for Bangle.js
|
||||||
|
*
|
||||||
|
* + Original Author: Eric Wooodward https://itsericwoodward.com/
|
||||||
|
* + see README.md for details
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
const timeFontSize = 6;
|
||||||
|
const dataFontSize = 2;
|
||||||
|
const font = "6x8";
|
||||||
|
|
||||||
|
const xyCenter = g.getWidth() / 2;
|
||||||
|
|
||||||
|
const yposTime = 75;
|
||||||
|
const yposDate = 125;
|
||||||
|
const yposSymbol = 160;
|
||||||
|
const yposInfo = 220;
|
||||||
|
|
||||||
|
const settings = require('Storage').readJSON('mysticclock.json', 1) || {};
|
||||||
|
const colors = ['white', 'blue', 'green', 'purple', 'red', 'teal', 'other'];
|
||||||
|
const color = settings.color ? colors[settings.color] : 0;
|
||||||
|
|
||||||
|
const infoData = {
|
||||||
|
'*GMT_MODE': {
|
||||||
|
calc: () => (new Date()).toString().split(" ")[5],
|
||||||
|
},
|
||||||
|
BATT_MODE: {
|
||||||
|
calc: () => `BATT: ${E.getBattery()}%`,
|
||||||
|
},
|
||||||
|
ID_MODE: {
|
||||||
|
calc: () => {
|
||||||
|
const val = NRF.getAddress().split(":");
|
||||||
|
return `ID: ${val[4]}${val[5]}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MEM_MODE: {
|
||||||
|
calc: () => {
|
||||||
|
const val = process.memory();
|
||||||
|
return `MEM: ${Math.round(val.usage * 100 / val.total)}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VER_MODE: {
|
||||||
|
calc: () => `FW: ${process.env.VERSION}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const infoList = Object.keys(infoData).sort();
|
||||||
|
let infoMode = infoList[0];
|
||||||
|
|
||||||
|
function setColor() {
|
||||||
|
const colorCommands = {
|
||||||
|
white: () => g.setColor(1, 1, 1),
|
||||||
|
blue: () => g.setColor(0, 0, 1),
|
||||||
|
green: () => g.setColor(0, 1, 0),
|
||||||
|
purple: () => g.setColor(1, 0, 1),
|
||||||
|
red: () => g.setColor(1, 0, 0),
|
||||||
|
teal: () => g.setColor(0, 1, 1),
|
||||||
|
other: () => g.setColor(1, 1, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// default if value unknown
|
||||||
|
if (!color || !colorCommands[color]) return colorCommands.white();
|
||||||
|
return colorCommands[color]();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocale() {
|
||||||
|
return require('locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawClock() {
|
||||||
|
|
||||||
|
// default draw styles
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
// drawSting centered
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
|
||||||
|
// setup color
|
||||||
|
setColor();
|
||||||
|
|
||||||
|
// get date
|
||||||
|
const d = new Date();
|
||||||
|
const dLocal = d.toString().split(" ");
|
||||||
|
|
||||||
|
const useLocale = !settings.useLocale;
|
||||||
|
|
||||||
|
const minutes = (`0${d.getMinutes()}`).substr(-2);
|
||||||
|
const seconds = (`0${d.getSeconds()}`).substr(-2);
|
||||||
|
|
||||||
|
let hours = (`0${d.getHours()}`).substr(-2);
|
||||||
|
let meridian = "";
|
||||||
|
|
||||||
|
if (settings.use12Hour) {
|
||||||
|
hours = parseInt(hours, 10);
|
||||||
|
meridian = 'AM';
|
||||||
|
if (hours === 0) {
|
||||||
|
hours = 12;
|
||||||
|
}
|
||||||
|
else if (hours >= 12) {
|
||||||
|
meridian = 'PM';
|
||||||
|
if (hours > 12) hours -= 12;
|
||||||
|
}
|
||||||
|
hours = (' ' + hours).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setFont(font, timeFontSize);
|
||||||
|
g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - 15, yposTime, true);
|
||||||
|
g.setFont(font, dataFontSize);
|
||||||
|
|
||||||
|
if (settings.use12Hour) {
|
||||||
|
g.drawString(seconds, xyCenter + 97, yposTime - 10, true);
|
||||||
|
g.drawString(meridian, xyCenter + 97, yposTime + 10, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.drawString(seconds, xyCenter + 97, yposTime + 10, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw DoW, name of month, date, year
|
||||||
|
g.setFont(font, dataFontSize);
|
||||||
|
g.drawString([
|
||||||
|
useLocale ? getLocale().dow(d, 1) : dLocal[0],
|
||||||
|
useLocale ? getLocale().month(d, 1) : dLocal[1],
|
||||||
|
d.getDate(),
|
||||||
|
d.getFullYear()
|
||||||
|
].join(" "), xyCenter, yposDate, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawInfo() {
|
||||||
|
if (infoData[infoMode] && infoData[infoMode].calc) {
|
||||||
|
// clear info
|
||||||
|
g.setColor(0, 0, 0);
|
||||||
|
g.fillRect(0, yposInfo - 8, 239, yposInfo + 25);
|
||||||
|
|
||||||
|
// draw info
|
||||||
|
g.setFont(font, dataFontSize);
|
||||||
|
setColor();
|
||||||
|
g.drawString((infoData[infoMode].calc()), xyCenter, yposInfo, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawImage() {
|
||||||
|
setColor();
|
||||||
|
g.drawPoly([xyCenter - 100, yposSymbol, xyCenter + 100, yposSymbol, xyCenter, yposSymbol + 30], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawAll() {
|
||||||
|
drawClock();
|
||||||
|
drawInfo();
|
||||||
|
drawImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextInfo() {
|
||||||
|
let idx = infoList.indexOf(infoMode);
|
||||||
|
if (idx > -1) {
|
||||||
|
if (idx === infoList.length - 1) infoMode = infoList[0];
|
||||||
|
else infoMode = infoList[idx + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevInfo() {
|
||||||
|
let idx = infoList.indexOf(infoMode);
|
||||||
|
if (idx > -1) {
|
||||||
|
if (idx === 0) infoMode = infoList[infoList.length - 1];
|
||||||
|
else infoMode = infoList[idx - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let secondInterval;
|
||||||
|
|
||||||
|
// handle LCD power state change
|
||||||
|
Bangle.on('lcdPower', on => {
|
||||||
|
|
||||||
|
// stop running when screen turns off
|
||||||
|
if (secondInterval) clearInterval(secondInterval);
|
||||||
|
secondInterval = undefined;
|
||||||
|
|
||||||
|
// start running
|
||||||
|
if (on) {
|
||||||
|
secondInterval = setInterval(drawAll, 1000);
|
||||||
|
drawAll(); // draw immediately
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// cover screen to put it to sleep
|
||||||
|
Bangle.on('touch', (button) => {
|
||||||
|
if (button === 3 && Bangle.isLCDOn()) Bangle.setLCDPower(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// clean app screen
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// if screen already active, draw now and start interval
|
||||||
|
if (Bangle.isLCDOn()) {
|
||||||
|
secondInterval = setInterval(drawAll, 1000);
|
||||||
|
drawAll(); // draw immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
// show launcher when middle button pressed
|
||||||
|
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||||
|
|
||||||
|
// rotate through info when the buttons are pressed
|
||||||
|
setWatch(() => {
|
||||||
|
nextInfo();
|
||||||
|
drawAll();
|
||||||
|
}, BTN3, { repeat: true });
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
prevInfo();
|
||||||
|
drawAll();
|
||||||
|
}, BTN1, { repeat: true });
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkBIf4A/AH4A/AH4A/AH4ALs1msADCA4MGAgQDBBYIAGg93u92s4DBuEAAYN3swDCC5AhBuwMBg4XBuwEBs4dCC49nHgNwCQREBCYNnEYYXHHQQvBAAJZBAgRPEC5IOCu0GM4YLCuCGDAAREBHwtnJ41gDQIXEOAQvBDoZ7CuwjCWwimTJgLCFZojWEbwbWIAH4A/AH4A/AH4A/AH4AFA"))
|
|
@ -0,0 +1,41 @@
|
||||||
|
// make sure to enclose the function in parentheses
|
||||||
|
(function (back) {
|
||||||
|
|
||||||
|
const settings = require('Storage').readJSON('mysticclock.json',1)||{};
|
||||||
|
const colors = ['White', 'Blue', 'Green', 'Purple', 'Red', 'Teal', 'Yellow'];
|
||||||
|
const offon = ['Off','On'];
|
||||||
|
const onoff = ['On','Off'];
|
||||||
|
|
||||||
|
function save(key, value) {
|
||||||
|
settings[key] = value;
|
||||||
|
require('Storage').writeJSON('mysticclock.json',settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
const appMenu = {
|
||||||
|
'': {'title': 'Clock Settings'},
|
||||||
|
'< Back': back,
|
||||||
|
'Color': {
|
||||||
|
value: 0|settings['color'],
|
||||||
|
min:0,
|
||||||
|
max:6,
|
||||||
|
format: m => colors[m],
|
||||||
|
onchange: m => {save('color', m)}
|
||||||
|
},
|
||||||
|
'12 Hour Clock': {
|
||||||
|
value: 0|settings['use12Hour'],
|
||||||
|
min:0,
|
||||||
|
max:1,
|
||||||
|
format: m => offon[m],
|
||||||
|
onchange: m => {save('use12Hour', m)}
|
||||||
|
},
|
||||||
|
'Use Locale': {
|
||||||
|
value: 0|settings['useLocale'],
|
||||||
|
min:0,
|
||||||
|
max:1,
|
||||||
|
format: m => onoff[m],
|
||||||
|
onchange: m => {save('useLocale', m)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
E.showMenu(appMenu)
|
||||||
|
|
||||||
|
})
|
Binary file not shown.
After Width: | Height: | Size: 981 B |
Loading…
Reference in New Issue