Skip to content

Commit

Permalink
Merge pull request #2964 from tinxx/master
Browse files Browse the repository at this point in the history
add edgeclk
  • Loading branch information
gfwilliams authored Aug 11, 2023
2 parents 5678594 + bcc77f5 commit 53aaffa
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 0 deletions.
1 change: 1 addition & 0 deletions apps/edgeclk/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.01: Initial release.
24 changes: 24 additions & 0 deletions apps/edgeclk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Edge Clock

![Screenshot](screenshot.png)
![Screenshot](screenshot2.png)
![Screenshot](screenshot3.png)

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.
1 change: 1 addition & 0 deletions apps/edgeclk/app-icon.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

306 changes: 306 additions & 0 deletions apps/edgeclk/app.js
Original file line number Diff line number Diff line change
@@ -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 added apps/edgeclk/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions apps/edgeclk/metadata.json
Original file line number Diff line number Diff line change
@@ -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 added apps/edgeclk/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/edgeclk/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/edgeclk/screenshot3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 53aaffa

Please sign in to comment.