mirror of https://github.com/espruino/BangleApps
Add Mystic Dock v1.00
parent
d570261e47
commit
5dad6caa1d
15
apps.json
15
apps.json
|
@ -3258,5 +3258,20 @@
|
|||
{"name":"gbtwist.app.js","url":"app.js"},
|
||||
{"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "mysticdock",
|
||||
"name": "Mystic Dock",
|
||||
"icon": "mystic-dock.png",
|
||||
"version":"1.0",
|
||||
"description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.",
|
||||
"tags": "dock",
|
||||
"type":"dock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"mysticdock.app.js","url":"mystic-dock-app.js"},
|
||||
{"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"},
|
||||
{"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"},
|
||||
{"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
1.00: First published version.
|
|
@ -0,0 +1,43 @@
|
|||
# Mystic Dock for Bangle.js
|
||||
|
||||
A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.
|
||||
|
||||
## Features
|
||||
|
||||
- Screensaver-like dock mode while charging (displays the current time for 8 seconds and a blank screen for 2, changing text placement with each draw)
|
||||
- 24 or 12-hour time (adjustable via the Settings menu)
|
||||
- Variable colors (also in the Settings)
|
||||
- Interactive watchface display (use upper and lower watch-buttons to activate it and rotate between values at the bottom)
|
||||
- International localization of watchface date (which can be disabled via the Settings if memory becomes an issue)
|
||||
- Automatic watchface reload when unplugged (toggleable via the Settings menu)
|
||||
- Rotates display 90 degrees if it detects it is sideways (for use in a charging dock)
|
||||
|
||||
When in interactive display mode, the bottom 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
|
||||
|
||||
- [Bluetooth Dock](https://github.com/espruino/BangleApps/tree/master/apps/bluetoothdock)
|
||||
- [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 [this one](https://publicdomainvectors.org/en/free-clipart/Digital-clock-display-vector-image/10845.html) and [this one](https://publicdomainvectors.org/en/free-clipart/Vector-image-of-power-manager-icon/20141.html) from [Public Domain Vectors](https://publicdomainvectors.org).
|
||||
|
||||
|
||||
## Changelog
|
||||
|
||||
- 1.00: First published version. (June 2021)
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
Eric Wooodward https://itsericwoodward.com/
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* Mystic Dock 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 ypos = [
|
||||
45, // Time
|
||||
105, // Date
|
||||
145, // Symbol
|
||||
210 // Info
|
||||
];
|
||||
|
||||
const settings = require('Storage').readJSON('mysticdock.json', 1) ||
|
||||
require('Storage').readJSON('mysticclock.json', 1) || {};
|
||||
const colors = ['white', 'blue', 'green', 'purple', 'red', 'teal', 'other'];
|
||||
const color = settings.color ? colors[settings.color] : 0;
|
||||
|
||||
const yposMax = 190;
|
||||
const yposMin = 60;
|
||||
let y = yposMax;
|
||||
|
||||
let lastButtonPressTime;
|
||||
let wasInActiveMode = false;
|
||||
|
||||
|
||||
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 drawInfo() {
|
||||
if (infoData[infoMode] && infoData[infoMode].calc) {
|
||||
// clear info
|
||||
g.setColor(0, 0, 0);
|
||||
g.fillRect(0, ypos[3] - 8, 239, ypos[3] + 25);
|
||||
|
||||
// draw info
|
||||
g.setFont(font, dataFontSize);
|
||||
setColor();
|
||||
g.drawString((infoData[infoMode].calc()), xyCenter, ypos[3], true);
|
||||
}
|
||||
}
|
||||
|
||||
function drawImage() {
|
||||
setColor();
|
||||
g.drawPoly([xyCenter - 100, ypos[2], xyCenter + 100, ypos[2], xyCenter, ypos[2] + 30], true);
|
||||
}
|
||||
|
||||
function drawClock() {
|
||||
|
||||
// default draw styles
|
||||
g.reset();
|
||||
|
||||
// get date
|
||||
const d = new Date();
|
||||
const dLocal = d.toString().split(" ");
|
||||
|
||||
const minutes = (`0${d.getMinutes()}`).substr(-2);
|
||||
const seconds = (`0${d.getSeconds()}`).substr(-2);
|
||||
|
||||
const useLocale = !settings.useLocale;
|
||||
|
||||
let hours = (`0${d.getHours()}`).substr(-2);
|
||||
let meridian = "";
|
||||
|
||||
if (d.getSeconds() % 10 === 0) {
|
||||
y = Math.floor(Math.random() * (yposMax - yposMin)) + yposMin;
|
||||
}
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
// setup color
|
||||
setColor();
|
||||
|
||||
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);
|
||||
|
||||
if (lastButtonPressTime && ((d.getTime() - lastButtonPressTime) / 1000) < 5) {
|
||||
|
||||
// clear screen when switching modes
|
||||
if (!wasInActiveMode) {
|
||||
g.clear();
|
||||
wasInActiveMode = true;
|
||||
}
|
||||
|
||||
// draw clock in center w/ seconds
|
||||
// show date (locale'd, based on settings)
|
||||
// show info line below it
|
||||
g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - 15, ypos[0], true);
|
||||
g.setFont(font, dataFontSize);
|
||||
|
||||
if (settings.use12Hour) {
|
||||
g.drawString(seconds, xyCenter + 97, ypos[0] - 10, true);
|
||||
g.drawString(meridian, xyCenter + 97, ypos[0] + 10, true);
|
||||
}
|
||||
else {
|
||||
g.drawString(seconds, xyCenter + 97, ypos[0] + 10, true);
|
||||
}
|
||||
|
||||
// draw DoW, name of month, date, year
|
||||
g.setFont(font, dataFontSize);
|
||||
g.drawString([
|
||||
useLocale ? require('locale').dow(d, 1) : dLocal[0],
|
||||
useLocale ? require('locale').month(d, 1) : dLocal[1],
|
||||
d.getDate(),
|
||||
d.getFullYear()
|
||||
].join(' '), xyCenter, ypos[1], true);
|
||||
|
||||
drawInfo();
|
||||
drawImage();
|
||||
}
|
||||
else if (d.getSeconds() % 10 === 8) {
|
||||
g.clear();
|
||||
wasInActiveMode = false;
|
||||
}
|
||||
else if (d.getSeconds() % 10 !== 9) {
|
||||
// clear screen when switching modes
|
||||
if (wasInActiveMode) {
|
||||
g.clear();
|
||||
wasInActiveMode = false;
|
||||
}
|
||||
g.drawString(`${hours}${(d.getSeconds() % 2) ? ' ' : ':'}${minutes}`, xyCenter - (settings.use12Hour ? 15 : 0), y, true);
|
||||
g.setFont(font, dataFontSize);
|
||||
if (settings.use12Hour) {
|
||||
g.drawString(meridian, xyCenter + 97, y + 10, true);
|
||||
}
|
||||
g.drawString(`BATT: ${E.getBattery() === 100 ? '100' : ('0' + E.getBattery()).substr(-2)}%`, xyCenter, y + 35, true);
|
||||
}
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
function nextInfo() {
|
||||
lastButtonPressTime = Date.now();
|
||||
let idx = infoList.indexOf(infoMode);
|
||||
|
||||
if (idx > -1) {
|
||||
if (idx === infoList.length - 1) infoMode = infoList[0];
|
||||
else infoMode = infoList[idx + 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function prevInfo() {
|
||||
lastButtonPressTime = Date.now();
|
||||
let idx = infoList.indexOf(infoMode);
|
||||
|
||||
if (idx > -1) {
|
||||
if (idx === 0) infoMode = infoList[infoList.length - 1];
|
||||
else infoMode = infoList[idx - 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Bangle.getAccel().x < -0.7) {
|
||||
g.setRotation(3); // assume watch in charge cradle
|
||||
}
|
||||
|
||||
g.clear();
|
||||
|
||||
setInterval(drawClock, 1000);
|
||||
drawClock();
|
||||
|
||||
if (Bangle.isCharging()) {
|
||||
Bangle.on("charging", isCharging => {
|
||||
const reloadOnUplug = !settings.reloadOnUplug;
|
||||
|
||||
if (!isCharging && reloadOnUplug) load();
|
||||
});
|
||||
}
|
||||
|
||||
// show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
||||
// change to "active mode" and rotate through info when the buttons are pressed
|
||||
setWatch(() => {
|
||||
nextInfo();
|
||||
drawClock();
|
||||
}, BTN3, { repeat: true });
|
||||
|
||||
setWatch(() => {
|
||||
prevInfo();
|
||||
drawClock();
|
||||
}, BTN1, { repeat: true });
|
|
@ -0,0 +1 @@
|
|||
Bangle.on("charging", isCharging => { if (isCharging) load("mysticdock.app.js"); });
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBIf4A6g93u9gs4DCBBIAFu9ms9wAYYIJAAt2FAN2BYMHEwIIIAAkGBQV3AYNns1mBAwXGg4KCIgYTEBAZ2JCYQABBBIXJQoRcCBA0GDQpPCBAUGuwTBBAwfCUwgTDMoVmBA8GQIIXGWoJ9DBA4vHAAIOBcoYIHC4xqCCQR2BBBEGJAKSGAH4Adb4SIBDCYXCUwQwVDCjJCXYS/CDh4SCAAoxPDA72CPaQdCTB57CLgQYCGCIdFJJ4QFTIQXUGwpHQJAapQI4qQPCIqtDVCQECMCR5BJgN2bSArCuACCbSIRCIobZQOgZMCgx4OJIjvCCyAYCCYJMBYB4zHC6oA/AE4="))
|
|
@ -0,0 +1,48 @@
|
|||
// make sure to enclose the function in parentheses
|
||||
(function (back) {
|
||||
|
||||
const settings = require('Storage').readJSON('mysticdock.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('mysticdock.json',settings);
|
||||
}
|
||||
|
||||
const appMenu = {
|
||||
'': {'title': 'Dock 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)}
|
||||
},
|
||||
'Reload on Unplug': {
|
||||
value: 0|settings['reloadOnUplug'],
|
||||
min:0,
|
||||
max:1,
|
||||
format: m => onoff[m],
|
||||
onchange: m => {save('reloadOnUplug', 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: 1.8 KiB |
Loading…
Reference in New Issue