mirror of https://github.com/espruino/BangleApps
add edgeclk
parent
6107e9474c
commit
bcc77f5f25
|
@ -0,0 +1 @@
|
|||
0.01: Initial release.
|
|
@ -0,0 +1,24 @@
|
|||
# Edge Clock
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Tinxx presents you a clock with as many straight edges as possible to allow for a crisp look and perfect readability.
|
||||
It comes with a custom font to display weekday, date, time, and steps. Also displays battery percentage while charging.
|
||||
There are three progress bars that indicate day of the week, time of the day, and daily step goal.
|
||||
The watch face is monochrome and allows for applying your favorite color scheme.
|
||||
|
||||
The appearance is highly configurable. In the settings menu you can:
|
||||
- De-/activate a buzz when the charger is connected while the watch face is active.
|
||||
- Decide if month or day should be displayed first.
|
||||
- Switch between 24h and 12h clock.
|
||||
- Hide or display seconds.*
|
||||
- Show AM/PM in place of the seconds.
|
||||
- Set the daily step goal.
|
||||
- En- or disable the individual progress bars.
|
||||
- Set if your week should start with Monday or Sunday (for week progress bar).
|
||||
|
||||
*) Hiding seconds should further reduce power consumption as the draw interval is prolonged as well.
|
||||
|
||||
The clock implements Fast Loading for faster switching to and fro.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIJGtgEDkEAn0b3P8uF8kUK1P+sFclQFCuFMkULCIMgvk+GRH/ABQRF5feGwMH7/KjcAmUGqfLzYLBg1fBYU+s1f/QRDofbDog7Xj8P/EH4f+Ao/5w4FBg8MgOB4eOAo8AgIFCDoIFJhk4Aok5DogFEGoIFBGoIdDAozgLOKNwAovv33wg0f/AFFsGGmEGwf4seOmCnBuALCg/YqFv30wgGf7AFEA"))
|
|
@ -0,0 +1,306 @@
|
|||
{
|
||||
/* Configuration
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
const settings = Object.assign({
|
||||
buzzOnCharge: true,
|
||||
monthFirst: true,
|
||||
twentyFourH: true,
|
||||
showAmPm: false,
|
||||
showSeconds: true,
|
||||
stepGoal: 10000,
|
||||
stepBar: true,
|
||||
weekBar: true,
|
||||
mondayFirst: true,
|
||||
dayBar: true,
|
||||
}, require('Storage').readJSON('edgeclk.settings.json', true) || {});
|
||||
|
||||
|
||||
/* Runtime Variables
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
let startTimeout;
|
||||
let drawInterval;
|
||||
|
||||
let lcdPower = true;
|
||||
let charging = Bangle.isCharging();
|
||||
|
||||
const font = atob('AA////wDwDwDwD////AAAAAAAAwAwA////AAAAAAAA8/8/wzwzwzwz/z/zAAAA4H4HwDxjxjxj////AAAA/w/wAwAwD/D/AwAwAAAA/j/jxjxjxjxjx/x/AAAA////xjxjxjxjx/x/AAAAwAwAwAwAwA////AAAAAA////xjxjxjxj////AAAA/j/jxjxjxjxj////AAAAAAAAAAMMMMAAAAAAAAAAAAAAABMOMMAAAAAAAAAABgBgDwDwGYGYMMMMAAAAAAGYGYGYGYGYGYAAAAAAMMMMGYGYDwDwBgBgAAAA4A4Ax7x7xgxg/g/gAAAA//gBv9shshv9gF/7AAAA////wwwwwwww////AAAA////xjxjxjxj////AAAA////wDwDwDwD4H4HAAAA////wDwDwD4Hf+P8AAAA////xjxjxjxjwDwDAAAA////xgxgxgxgwAwAAAAA////wDwDwzwz4/4/AAAA////BgBgBgBg////AAAAAAwDwD////wDwDAAAAAAAAwPwPwDwD////AAAAAA////DwH4OccO4HwDAAAA////ADADADADADADAAAA////YAGAGAYA////AAAA////MADAAwAM////AAAA////wDwDwDwD////AAAA////xgxgxgxg/g/gAAAA/+/+wGwOwOwO////AAAA////xgxgxwx8/v/jAAAA/j/jxjxjxjxjx/x/AAAAwAwAwA////wAwAwAAAAA////ADADADAD////AAAA/w/8AOAHAHAO/8/wAAAA////AGAYAYAG////AAAAwD4PecH4H4ec4PwDAAAAwA4AeBH/H/eA4AwAAAAAwPwfw7xzzj3D+D8DAAAAAAAAAAAA////wDAAAAAAAAAABgBgBgBgAAAAAAAAAAwD////AAAAAAAAAAAAAwDwPA8A8APADwAwAAAAAAAAAAAAAAAAAAAAAA');
|
||||
|
||||
const iconSize = [19, 26];
|
||||
const plugIcon = atob('ExoBBxwA44AccAOOAHHAf/8P/+H//D//h//w//4P/4H/8B/8Af8ABwAA4AAcAAOAAHAADgABwAA4AAcAAOAAHAA=');
|
||||
const stepIcon1 = atob('ExoBAfAAPgAHwAD4AB8AAAAB/wD/8D//Bn9wz+cZ/HM/hmfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
|
||||
const stepIcon2 = atob('ExoBAfAAPgMHwfD4dx8ccAcH/8B/8Af8AH8AD+AB/AA/gAfwAP4AAAAD+AD/gBxwB48A4OA8HgcBwcAcOAOGADA=');
|
||||
|
||||
|
||||
/* Draw Functions
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
const drawAll = function () {
|
||||
const date = new Date();
|
||||
|
||||
drawDate(date);
|
||||
if (settings.showSeconds) drawSecs(date);
|
||||
drawTime(date);
|
||||
drawLower();
|
||||
};
|
||||
|
||||
const drawLower = function (stepsOnlyCount) {
|
||||
if (charging) {
|
||||
drawCharge();
|
||||
} else {
|
||||
drawSteps(stepsOnlyCount);
|
||||
}
|
||||
};
|
||||
|
||||
const drawDate = function (date) {
|
||||
const top = 30;
|
||||
g.reset();
|
||||
|
||||
// weekday
|
||||
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
||||
g.setFontAlign(-1, -1); // left top
|
||||
g.drawString(date.toString().slice(0,3).toUpperCase(), 0, top + 12, true);
|
||||
|
||||
// date
|
||||
g.setFontAlign(1, -1); // right top
|
||||
// Note: to save space first and last two lines of ASCII are left out.
|
||||
// That is why '-' is assigned to '\' and ' ' (space) to '_'.
|
||||
if (settings.monthFirst) {
|
||||
g.drawString((date.getMonth()+1).toString().padStart(2, '_')
|
||||
+ '\\'
|
||||
+ date.getDate().toString().padStart(2, 0),
|
||||
g.getWidth(), top + 12, true);
|
||||
} else {
|
||||
g.drawString('_'
|
||||
+ date.getDate().toString().padStart(2, 0)
|
||||
+ '\\'
|
||||
+ (date.getMonth()+1).toString(),
|
||||
g.getWidth(), top + 12, true);
|
||||
}
|
||||
|
||||
// line/progress bar
|
||||
if (settings.weekBar) {
|
||||
let weekday = date.getDay();
|
||||
if (settings.mondayFirst) {
|
||||
if (weekday === 0) { weekday = 7; }
|
||||
} else {
|
||||
weekday += 1;
|
||||
}
|
||||
drawBar(top, weekday/7);
|
||||
} else {
|
||||
drawLine(top);
|
||||
}
|
||||
};
|
||||
|
||||
const drawTime = function (date) {
|
||||
const top = 72;
|
||||
g.reset();
|
||||
|
||||
const h = date.getHours();
|
||||
g.setFontCustom(font, 48, 10, 1024 + 12); // triple size (2<<9)
|
||||
g.setFontAlign(-1, -1); // left top
|
||||
g.drawString((settings.twentyFourH ? h : (h % 12 || 12)).toString().padStart(2, 0),
|
||||
0, top+12, true);
|
||||
g.setFontAlign(0, -1); // center top
|
||||
g.drawString(':', g.getWidth()/2, top+12, false);
|
||||
const m = date.getMinutes();
|
||||
g.setFontAlign(1, -1); // right top
|
||||
g.drawString(m.toString().padStart(2, 0),
|
||||
g.getWidth(), top+12, true);
|
||||
|
||||
if (settings.showAmPm) {
|
||||
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
||||
g.setFontAlign(1, 1); // right bottom
|
||||
g.drawString(h < 12 ? 'AM' : 'PM', g.getWidth(), g.getHeight() - 1, true);
|
||||
}
|
||||
|
||||
if (settings.dayBar) {
|
||||
drawBar(top, (h*60+m)/1440);
|
||||
} else {
|
||||
drawLine(top);
|
||||
}
|
||||
};
|
||||
|
||||
const drawSecs = function (date) {
|
||||
g.reset();
|
||||
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
||||
g.setFontAlign(1, 1); // right bottom
|
||||
g.drawString(date.getSeconds().toString().padStart(2, 0), g.getWidth(), g.getHeight() - 1, true);
|
||||
};
|
||||
|
||||
const drawSteps = function (onlyCount) {
|
||||
g.reset();
|
||||
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
||||
g.setFontAlign(-1, 1); // left bottom
|
||||
|
||||
const steps = Bangle.getHealthStatus('day').steps;
|
||||
g.drawString(steps.toString().padEnd(5, '_'), iconSize[0] + 6, g.getHeight() - 1, true);
|
||||
|
||||
if (onlyCount === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progress = steps / settings.stepGoal;
|
||||
if (settings.stepBar) {
|
||||
drawBar(g.getHeight() - 38, progress);
|
||||
} else {
|
||||
drawLine(g.getHeight() - 38);
|
||||
}
|
||||
|
||||
// icon
|
||||
if (progress < 1) {
|
||||
g.drawImage(stepIcon1, 0, g.getHeight() - iconSize[1]);
|
||||
} else {
|
||||
g.drawImage(stepIcon2, 0, g.getHeight() - iconSize[1]);
|
||||
}
|
||||
};
|
||||
|
||||
const drawCharge = function () {
|
||||
g.reset();
|
||||
g.setFontCustom(font, 48, 10, 512 + 12); // double size (1<<9)
|
||||
g.setFontAlign(-1, 1); // left bottom
|
||||
|
||||
const charge = E.getBattery();
|
||||
g.drawString(charge.toString().padEnd(5, '_'), iconSize[0] + 6, g.getHeight() - 1, true);
|
||||
|
||||
drawBar(g.getHeight() - 38, charge / 100);
|
||||
g.drawImage(plugIcon, 0, g.getHeight() - 26);
|
||||
};
|
||||
|
||||
const drawBar = function (top, progress) {
|
||||
g.drawRect(0, top, g.getWidth()-1, top + 5);
|
||||
g.drawRect(1, top+1, g.getWidth()-2, top + 4);
|
||||
const barLen = progress > 1 ? g.getWidth() : g.getWidth() * progress;
|
||||
g.drawLine(2, top+2, barLen, top + 2);
|
||||
g.drawLine(2, top+3, barLen, top + 3);
|
||||
};
|
||||
|
||||
const drawLine = function (top) {
|
||||
const width = g.getWidth();
|
||||
g.drawLine(0, top+2, width, top + 2);
|
||||
g.drawLine(0, top+3, width, top + 3);
|
||||
};
|
||||
|
||||
|
||||
/* Event Handlers
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
const onSecondInterval = function () {
|
||||
const date = new Date();
|
||||
drawSecs(date);
|
||||
if (date.getSeconds() === 0) {
|
||||
onMinuteInterval();
|
||||
}
|
||||
};
|
||||
|
||||
const onMinuteInterval = function () {
|
||||
const date = new Date();
|
||||
drawTime(date);
|
||||
drawLower(true);
|
||||
};
|
||||
|
||||
const onMinuteIntervalStarter = function () {
|
||||
drawInterval = setInterval(onMinuteInterval, 60000);
|
||||
startTimeout = null;
|
||||
onMinuteInterval();
|
||||
};
|
||||
|
||||
const onLcdPower = function (on) {
|
||||
lcdPower = on;
|
||||
if (on) {
|
||||
drawAll();
|
||||
startTimers();
|
||||
} else {
|
||||
stopTimers();
|
||||
}
|
||||
};
|
||||
|
||||
const onMidnight = function () {
|
||||
if (!lcdPower) return;
|
||||
drawDate(new Date());
|
||||
// Lower part (steps/charge) will be updated every minute.
|
||||
// However, to save power while on battery only step count will get updated.
|
||||
// This will update icon and progress bar as well:
|
||||
if (!charging) drawSteps();
|
||||
};
|
||||
|
||||
const onHealth = function () {
|
||||
if (!lcdPower || charging) return;
|
||||
// This will update progress bar and icon:
|
||||
drawSteps();
|
||||
};
|
||||
|
||||
const onLock = function (locked) {
|
||||
if (locked) return;
|
||||
drawLower();
|
||||
};
|
||||
|
||||
const onCharging = function (isCharging) {
|
||||
charging = isCharging;
|
||||
if (isCharging && settings.buzzOnCharge) Bangle.buzz();
|
||||
if (!lcdPower) return;
|
||||
drawLower();
|
||||
};
|
||||
|
||||
|
||||
/* Lifecycle Functions
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
const registerEvents = function () {
|
||||
// This is for original Bangle.js; version two has always-on display:
|
||||
Bangle.on('lcdPower', onLcdPower);
|
||||
|
||||
// Midnight event is triggered qhen health data is reset and a new day begins:
|
||||
Bangle.on('midnight', onMidnight);
|
||||
|
||||
// Health data is published via 10 mins interval:
|
||||
Bangle.on('health', onHealth);
|
||||
|
||||
// Lock event signals screen (un)lock:
|
||||
Bangle.on('lock', onLock);
|
||||
|
||||
// Charging event signals when charging status changes:
|
||||
Bangle.on('charging', onCharging);
|
||||
};
|
||||
|
||||
const deregisterEvents = function () {
|
||||
Bangle.removeListener('lcdPower', onLcdPower);
|
||||
Bangle.removeListener('midnight', onMidnight);
|
||||
Bangle.removeListener('health', onHealth);
|
||||
Bangle.removeListener('lock', onLock);
|
||||
Bangle.removeListener('charging', onCharging);
|
||||
};
|
||||
|
||||
const startTimers = function () {
|
||||
if (drawInterval) return;
|
||||
if (settings.showSeconds) {
|
||||
drawInterval = setInterval( onSecondInterval, 1000);
|
||||
} else {
|
||||
startTimeout = setTimeout(onMinuteIntervalStarter, (60 - new Date().getSeconds()) * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const stopTimers = function () {
|
||||
if (startTimeout) clearTimeout(startTimeout);
|
||||
if (!drawInterval) return;
|
||||
clearInterval(drawInterval);
|
||||
drawInterval = null;
|
||||
};
|
||||
|
||||
|
||||
/* Startup Process
|
||||
------------------------------------------------------------------------------*/
|
||||
|
||||
g.clear();
|
||||
drawAll();
|
||||
startTimers();
|
||||
registerEvents();
|
||||
|
||||
Bangle.setUI({mode: 'clock', remove: function() {
|
||||
stopTimers();
|
||||
deregisterEvents();
|
||||
}});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "edgeclk",
|
||||
"name": "Edge Clock",
|
||||
"shortName": "Edge Clock",
|
||||
"version": "0.01",
|
||||
"description": "Crisp clock with perfect readability.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot2.png"}, {"url":"screenshot3.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"edgeclk.app.js", "url": "app.js"},
|
||||
{"name":"edgeclk.settings.js", "url": "settings.js"},
|
||||
{"name":"edgeclk.img", "url": "app-icon.js", "evaluate": true}
|
||||
],
|
||||
"data": [{"name":"edgeclk.settings.json"}]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,112 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = 'edgeclk.settings.json';
|
||||
const storage = require('Storage');
|
||||
|
||||
const settings = {
|
||||
buzzOnCharge: true,
|
||||
monthFirst: true,
|
||||
twentyFourH: true,
|
||||
showAmPm: false,
|
||||
showSeconds: true,
|
||||
stepGoal: 10000,
|
||||
stepBar: true,
|
||||
weekBar: true,
|
||||
mondayFirst: true,
|
||||
dayBar: true,
|
||||
};
|
||||
|
||||
const saved_settings = storage.readJSON(SETTINGS_FILE, true);
|
||||
if (saved_settings) {
|
||||
for (const key in saved_settings) {
|
||||
if (!settings.hasOwnProperty(key)) continue;
|
||||
settings[key] = saved_settings[key];
|
||||
}
|
||||
}
|
||||
|
||||
let save = function() {
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Edge Clock' },
|
||||
'< Back': back,
|
||||
'Charge Buzz': {
|
||||
value: settings.buzzOnCharge,
|
||||
onchange: () => {
|
||||
settings.buzzOnCharge = !settings.buzzOnCharge;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Month First': {
|
||||
value: settings.monthFirst,
|
||||
onchange: () => {
|
||||
settings.monthFirst = !settings.monthFirst;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'24h Clock': {
|
||||
value: settings.twentyFourH,
|
||||
onchange: () => {
|
||||
settings.twentyFourH = !settings.twentyFourH;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show AM/PM': {
|
||||
value: settings.showAmPm,
|
||||
onchange: () => {
|
||||
settings.showAmPm = !settings.showAmPm;
|
||||
// TODO can this be visually changed?
|
||||
if (settings.showAmPm && settings.showSeconds) settings.showSeconds = false;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show Seconds': {
|
||||
value: settings.showSeconds,
|
||||
onchange: () => {
|
||||
settings.showSeconds = !settings.showSeconds;
|
||||
// TODO can this be visually changed?
|
||||
if (settings.showSeconds && settings.showAmPm) settings.showAmPm = false;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Step Goal': {
|
||||
value: settings.stepGoal,
|
||||
min: 250,
|
||||
max: 50000,
|
||||
step: 250,
|
||||
onchange: v => {
|
||||
settings.stepGoal = v;
|
||||
save();
|
||||
}
|
||||
},
|
||||
'Step Progress': {
|
||||
value: settings.stepBar,
|
||||
onchange: () => {
|
||||
settings.stepBar = !settings.stepBar;
|
||||
save();
|
||||
}
|
||||
},
|
||||
'Week Progress': {
|
||||
value: settings.weekBar,
|
||||
onchange: () => {
|
||||
settings.weekBar = !settings.weekBar;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Week Start': {
|
||||
value: settings.mondayFirst,
|
||||
format: () => settings.mondayFirst ? 'Monday' : 'Sunday',
|
||||
onchange: () => {
|
||||
settings.mondayFirst = !settings.mondayFirst;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Day Progress': {
|
||||
value: settings.dayBar,
|
||||
onchange: () => {
|
||||
settings.dayBar = !settings.dayBar;
|
||||
save();
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
Loading…
Reference in New Issue