Merge branch 'master' of github.com:espruino/BangleApps

pull/1472/head
Gordon Williams 2022-02-17 16:38:25 +00:00
commit 16f6c577b9
26 changed files with 1432 additions and 137 deletions

1
apps/info/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Release

17
apps/info/README.md Normal file
View File

@ -0,0 +1,17 @@
# Info
A very simple app that shows information on 3 different screens.
Go to the next screen via tab right, go to the previous screen
via tab left and reload the data via tab in the middle of the
screen. Very useful if combined with pattern launcher ;)
![](screenshot_1.png)
![](screenshot_2.png)
![](screenshot_2.png)
## Contributors
- [David Peer](https://github.com/peerdavid).
## Thanks To
<a href="https://www.flaticon.com/free-icons/info" title="info icons">Info icons created by Freepik - Flaticon</a>

108
apps/info/info.app.js Normal file
View File

@ -0,0 +1,108 @@
var s = require("Storage");
const locale = require('locale');
var ENV = process.env;
var W = g.getWidth(), H = g.getHeight();
var screen = 0;
const maxScreen = 2;
function getVersion(file) {
var j = s.readJSON(file,1);
var v = ("object"==typeof j)?j.version:false;
return v?((v?"v"+v:"Unknown")):"NO ";
}
function drawData(name, value, y){
g.drawString(name, 5, y);
g.drawString(value, 100, y);
}
function getSteps(){
try{
return Bangle.getHealthStatus("day").steps;
} catch(e) {
return ">= 2v12";
}
}
function getBpm(){
try{
return Math.round(Bangle.getHealthStatus("day").bpm) + "bpm";
} catch(e) {
return ">= 2v12";
}
}
function drawInfo() {
g.reset().clearRect(Bangle.appRect);
var h=18, y = h;//-h;
// Header
g.setFont("Vector", h+2).setFontAlign(0,-1);
g.drawString("--==|| INFO ||==--", W/2, 0);
g.setFont("Vector",h).setFontAlign(-1,-1);
// Dynamic data
if(screen == 0){
drawData("Steps", getSteps(), y+=h);
drawData("HRM", getBpm(), y+=h);
drawData("Battery", E.getBattery() + "%", y+=h);
drawData("Voltage", E.getAnalogVRef().toFixed(2) + "V", y+=h);
drawData("IntTemp.", locale.temp(parseInt(E.getTemperature())), y+=h);
}
if(screen == 1){
drawData("Charging?", Bangle.isCharging() ? "Yes" : "No", y+=h);
drawData("Bluetooth", NRF.getSecurityStatus().connected ? "Conn." : "Disconn.", y+=h);
drawData("GPS", Bangle.isGPSOn() ? "On" : "Off", y+=h);
drawData("Compass", Bangle.isCompassOn() ? "On" : "Off", y+=h);
drawData("HRM", Bangle.isHRMOn() ? "On" : "Off", y+=h);
}
// Static data
if(screen == 2){
drawData("Firmw.", ENV.VERSION, y+=h);
drawData("Boot.", getVersion("boot.info"), y+=h);
drawData("Settings", getVersion("setting.info"), y+=h);
drawData("Storage", "", y+=h);
drawData(" Total", ENV.STORAGE>>10, y+=h);
drawData(" Free", require("Storage").getFree()>>10, y+=h);
}
if(Bangle.isLocked()){
g.setFont("Vector",h-2).setFontAlign(-1,-1);
g.drawString("Locked", 0, H-h+2);
}
g.setFont("Vector",h-2).setFontAlign(1,-1);
g.drawString((screen+1) + "/3", W, H-h+2);
}
drawInfo();
setWatch(_=>load(), BTN1);
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.3);
var right = g.getWidth() - left;
var isLeft = e.x < left;
var isRight = e.x > right;
if(isRight){
screen = (screen + 1) % (maxScreen+1);
}
if(isLeft){
screen -= 1;
screen = screen < 0 ? maxScreen : screen;
}
drawInfo();
});
Bangle.on('lock', function(isLocked) {
drawInfo();
});
Bangle.loadWidgets();
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
// Bangle.drawWidgets();

1
apps/info/info.icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcBkmSpICDBwcJBYwCDpAhFggRJGg8SCI+ABgU//gSDCI4JBj//AAX4JRAIBg4QDAAPgBIJWGgIQFAAI+BLglAgEPCI/wEgJoEgYQHAAPANwhWFAApcBCIWQgAQJAAMAgSMDCJiSCwB6GQA6eCn5TFL4q5BUgIRF/wuBv4RGkCeGO4IREUgMBCJCVGCISwIWw0BYRLIICLBHHCJRrGCIQIFR44I5LIoRaPpARcdIwRJfYMBCJuACKUkgE/a5f8gEJCJD7FCIeAg78FAAvggFJCIMACJZOBCIOQCJsCCIOSgEfCBP4gESCIZTFOIwRDoDIGaguSCIVIgCkFTwcAggRDpIYBQAx6BgAOCAQYIBLghWBTwQRFFgIABXIIFDBwgCDBYQAENAYCFLgIAEKwpKIIhA="))

BIN
apps/info/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

19
apps/info/metadata.json Normal file
View File

@ -0,0 +1,19 @@
{
"id": "info",
"name": "Info",
"version": "0.01",
"description": "An application that displays information such as battery level, steps etc.",
"icon": "info.png",
"type": "app",
"tags": "tool",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"screenshots": [
{"url":"screenshot_1.png"},
{"url":"screenshot_2.png"},
{"url":"screenshot_3.png"}],
"storage": [
{"name":"info.app.js","url":"info.app.js"},
{"name":"info.img","url":"info.icon.js","evaluate":true}
]
}

BIN
apps/info/screenshot_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
apps/info/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
apps/info/screenshot_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1 +1,2 @@
0.01: Initial release
0.02: Optional fullscreen mode

View File

@ -4,8 +4,8 @@
|---------------------------------|--------------------------------------|
| <center>Neon X</center> | <center>Neon IO X</center> |
This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow.
Can be switched between in the Settings menu, which can be accessed through
This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow.
Can be switched between in the Settings menu, which can be accessed through
the app/widget settings menu of the Bangle.js
## Settings
@ -14,7 +14,11 @@ the app/widget settings menu of the Bangle.js
Activate the Neon IO X clock look, a bit hard to read until one gets used to it.
### Thickness
The thickness of watch lines, from 1 to 5.
The thickness of watch lines, from 1 to 6.
### Date on touch
Shows the current date as DD MM on touch and reverts back to time after 5 seconds or with another touch.
### Fullscreen
Shows the watchface in fullscreen mode.
Note: In fullscreen mode, widgets are hidden, but still loaded.

View File

@ -2,7 +2,7 @@
"id": "neonx",
"name": "Neon X & IO X Clock",
"shortName": "Neon X Clock",
"version": "0.01",
"version": "0.02",
"description": "Pebble Neon X & Neon IO X for Bangle.js",
"icon": "neonx.png",
"type": "clock",

View File

@ -34,6 +34,7 @@ const colors = {
const is12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
const screenWidth = g.getWidth();
const screenHeight = g.getHeight();
const halfWidth = screenWidth / 2;
const scale = screenWidth / 240;
const REFRESH_RATE = 10E3;
@ -58,16 +59,19 @@ function drawLine(poly, thickness){
}
}
let settings = require('Storage').readJSON('neonx.json', 1);
if (!settings) {
settings = {
thickness: 4,
io: 0,
showDate: 1
};
let settings = {
thickness: 4,
io: 0,
showDate: 1,
fullscreen: false,
};
let saved_settings = require('Storage').readJSON('neonx.json', 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function drawClock(num){
let tx, ty;
@ -79,13 +83,15 @@ function drawClock(num){
g.setColor(colors[settings.io ? 'io' : 'x'][y][x]);
if (!settings.io) {
tx = (x * 100 + 18) * newScale;
ty = (y * 100 + 32) * newScale;
newScale *= settings.fullscreen ? 1.18 : 1.0;
let dx = settings.fullscreen ? 0 : 18
tx = (x * 100 + dx) * newScale;
ty = (y * 100 + dx*2) * newScale;
} else {
newScale = 0.33 + current * 0.4;
newScale = 0.33 + current * (settings.fullscreen ? 0.48 : 0.4);
tx = (halfWidth - 139) * newScale + halfWidth;
ty = (halfWidth - 139) * newScale + halfWidth + 12;
tx = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 0);
ty = (halfWidth - 139) * newScale + halfWidth + (settings.fullscreen ? 2 : 12);
}
for (let i = 0; i < digits[num[y][x]].length; i++) {
@ -116,7 +122,11 @@ function draw(date){
l2 = ('0' + d.getMinutes()).substr(-2);
}
g.clearRect(0,24,240,240);
if(settings.fullscreen){
g.clearRect(0,0,screenWidth,screenHeight);
} else {
g.clearRect(0,24,240,240);
}
drawClock([l1, l2]);
}
@ -150,4 +160,9 @@ Bangle.on('lcdPower', function(on){
});
Bangle.loadWidgets();
Bangle.drawWidgets();
if(settings.fullscreen){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else {
Bangle.drawWidgets();
}

View File

@ -7,7 +7,8 @@
neonXSettings = {
thickness: 4,
io: 0,
showDate: 1
showDate: 1,
fullscreen: false,
};
updateSettings();
@ -17,7 +18,7 @@
if (!neonXSettings) resetSettings();
let thicknesses = [1, 2, 3, 4, 5];
let thicknesses = [1, 2, 3, 4, 5, 6];
const menu = {
"" : { "title":"Neon X & IO"},
@ -48,7 +49,15 @@
neonXSettings.showDate = v;
updateSettings();
}
}
},
'Fullscreen': {
value: false | neonXSettings.fullscreen,
format: () => (neonXSettings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
neonXSettings.fullscreen = !neonXSettings.fullscreen;
updateSettings();
},
},
};
E.showMenu(menu);
})

View File

@ -16,3 +16,4 @@
0.14: incorporated lazybones idle timer, configuration settings to come
0.15: fixed tendancy for mylocation to default to London
added setting to enable/disable idle timer warning
0.16: make check_idle boolean setting work properly with new B2 menu

View File

@ -2,7 +2,7 @@
"id": "pastel",
"name": "Pastel Clock",
"shortName": "Pastel",
"version": "0.15",
"version": "0.16",
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
"icon": "pastel.png",
"dependencies": {"mylocation":"app","weather":"app"},

View File

@ -38,38 +38,28 @@
},
},
'Show Grid': {
value: s.grid,
format: () => (s.grid ? 'Yes' : 'No'),
onchange: () => {
s.grid = !s.grid;
value: !!s.grid,
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
s.grid = v;
save();
},
},
'Show Weather': {
value: s.weather,
format: () => (s.weather ? 'Yes' : 'No'),
onchange: () => {
s.weather = !s.weather;
value: !!s.weather,
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
s.weather = v;
save();
},
},
// for use when the new menu system goes live
/*
'Idle Warning': {
value: s.idle_check,
onchange : v => {
value: !!s.idle_check,
format: v => v ? /*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
s.idle_check = v;
save();
},
},
*/
'Idle Warning': {
value: s.idle_check,
format: () => (s.idle_check ? 'Yes' : 'No'),
onchange: () => {
s.idle_check = !s.idle_check;
save();
},
}
})
})

9
apps/timecal/ChangeLog Normal file
View File

@ -0,0 +1,9 @@
0.01: Initial creation of the clock face time and calendar
0.02: Feature Request #1154 and some findings...
-> get rendered time from optimisations
-> *BATT SAFE* only update once a minute instead of once a second
-> *RAM optimized* clean code, corrected minute update (timout, no intervall)
-> locale: weekday name (first two characters) from locale
-> added settings to render cal view begin day (-1: today, 0:sunday, 1:monday [default])
0.03: a lot of more settings for outline, colors and highlights
0.04: finalized README, fixed settings cancel, fixed border-setting

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

@ -0,0 +1,22 @@
# Calendar Clock
## Features
Shows the
* Date
* Time (hh:mm) - respecting 12/24 (uses locale string)
* 3 weeks calendar view (last,current and next week)
### The settings menu
Calendar View can be customized
* < Save: Exist and save the current settings
* Show date: Choose if and how the date is displayed: none, locale (default), monthfull or monthshort.yearshort #weeknum with 0 prefixed
* Start wday: Set day of week start. Values: 0=Sunday, 1=Monday,...,6=Saturday or -1=Relative to today (default 0: Sunday)
* Su color: Set Sundays color. Values: none (default), red, green or blue
* Border: show or none (default)
* Submenu Today settings - choose how today is highlighted
* < Back:
* Color: none, red (default), green or blue
* Marker: Outline today graphically. Values: none (default), circle, rect(angle)
* Mrk.Color: Circle/rectangle color: red (default), green or blue
* Mrk.Size: Circle/rectangle thickness in pixel: min:1, max: 10, default:3
* < Cancel: Exit and no change. Nevertheless missing default settings and superflous settings will be removed and saved.

View File

@ -1,13 +1,16 @@
{ "id": "timecal",
"name": "TimeCal",
"shortName":"TimeCal",
"version":"0.04",
"description": "TimeCal shows the date/time along with a 3 week calendar",
"icon": "icon.png",
"version":"0.01",
"description": "TimeCal shows the Time along with a 3 week calendar",
"tags": "clock",
"type": "clock",
"tags": "clock,calendar",
"supports":["BANGLEJS2"],
"readme": "README.md",
"allow_emulator":true,
"storage": [
{"name":"timecal.app.js","url":"timecal.app.js"}
{"name":"timecal.app.js","url":"timecal.app.js"},
{"name":"timecal.settings.js","url":"timecal.settings.js"}
]
}

View File

@ -0,0 +1,798 @@
//Clock renders date, time and pre,current,next week calender view
class TimeCalClock{
DATE_FONT_SIZE(){ return 20; }
TIME_FONT_SIZE(){ return 40; }
/**
* @param{Date} date optional the date (e.g. for testing)
* @param{Settings} settings optional settings to use e.g. for testing
*/
constructor(date, settings){
if (date)
this.date=date;
if (settings)
this._settings = settings;
else
this._settings = require("Storage").readJSON("timecal.settings.json", 1) || {};
const defaults = {
shwDate:1, //0:none, 1:locale, 2:month, 3:monthshort.year #week
wdStrt:0, //identical to getDay() 0->Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today
tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled
tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkPxl:3, //px
suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
//phColor:"#E00", //public holiday
calBrdr:false
};
for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings
for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults
g.clear();
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
this.centerX = Bangle.appRect.w/2;
this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b
this.ABR_DAY=[];
if (require("locale") && require("locale").dow)
for (let d=0; d<=6; d++) {
var refDay=new Date();
refDay.setFullYear(1972);
refDay.setMonth(0);
refDay.setDate(2+d);
this.ABR_DAY.push(require("locale").dow(refDay));
}
else
this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
}
/**
* @returns {Object} current settings object
*/
settings(){
return this._settings;
}
/*
* Run forest run
**/
draw(){
this.drawTime();
if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset())
this.drawDateAndCal();
}
/**
* draw given or current time from date
* overwatch timezone changes
* schedules itself to update
*/
drawTime(){
d=this.date ? this.date : new Date();
const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10;
d=d?d :new Date();
g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg)
.clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7)
.drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true);
//.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option
setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds());
}
/**
* draws given date and cal
* @param{Date} d provide date or uses today
*/
drawDateAndCal(){
d=this.date ? this.date : new Date();
this.TZOffset=d.getTimezoneOffset();
this.drawDate();
this.drawCal();
if (this.tOutD) //abort exisiting
clearTimeout(this.tOutD);
this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds());
}
/**
* draws given date as defiend in settings
*/
drawDate(){
d=this.date ? this.date : new Date();
const FONT_SIZE=20;
const Y=Bangle.appRect.y;
var render=false;
var dateStr = "";
if (this.settings().shwDate>0) { //skip if exactly -none
const dateSttngs = ["","l","M","m.Y #W"];
for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured
switch (c){
case "l":{ //locale
render=true;
dateStr+=require("locale").date(d,1);
break;
}
case "m":{ //month e.g. Jan.
render=true;
dateStr+=require("locale").month(d,1);
break;
}
case "M":{ //month e.g. January
render=true;
dateStr+=require("locale").month(d,0);
break;
}
case "y":{ //year e.g. 22
render=true;
dateStr+=d.getFullYear().slice(-2);
break;
}
case "Y":{ //year e.g. 2022
render=true;
dateStr+=d.getFullYear();
break;
}
case "w":{ //week e.g. #2
dateStr+=(this.ISO8601calWeek(d));
break;
}
case "W":{ //week e.g. #02
dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2);
break;
}
default: //append c
dateStr+=c;
render=dateStr.length>0;
break; //noop
}
}
}
if (render){
g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y);
}
//g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option
}
/**
* draws calender week view (-1,0,1) for given date
*/
drawCal(){
d=this.date ? this.date : new Date();
const DAY_NAME_FONT_SIZE=10;
const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3;
const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only
const CELL_W=Bangle.appRect.w/7; //cell width
const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth
const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15
g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H);
//draw grid & Headline
const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2
for(var dNo=0; dNo<dNames.length; dNo++){
const dIdx=this.settings().wdStrt>=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7;
const dName=dNames[dIdx];
if(dNo>0)
g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1);
if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt
g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg);
}
var nextY=CAL_Y+DAY_NAME_FONT_SIZE;
for(i=0; i<3; i++){
const y=nextY+i*CELL_H;
g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y);
}
g.setFont("Vector", DAY_NUM_FONT_SIZE);
//write days
const tdyDate=d.getDate();
const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days
var rD=new Date(d.getTime());
rD.setDate(rD.getDate()-days);
var rDate=rD.getDate();
for(var y=0; y<3; y++){
for(var x=0; x<dNames.length; x++){
if(rDate===tdyDate){ //today
g.setColor(this.nrgb[this.settings().tdyMrkClr]); //today marker color or fg color
switch(this.settings().tdyMrkr){ //0:none, 1:circle, 2:rectangle, 3:filled
case 1:
for(m=1; m<=this.settings().tdyMrkPxl&&m<CELL_H-1&&m<CELL_W-1; m++)
g.drawCircle(x*CELL_W+(CELL_W/2)+1, nextY+(CELL_H*y)+(CELL_H/2)+1, Math.min((CELL_W-m)/2, (CELL_H-m)/2)-2);
break;
case 2:
for(m=1; m<=this.settings().tdyMrkPxl&&m<CELL_H-1&&m<CELL_W-1; m++)
g.drawRect(x*CELL_W+m, nextY+CELL_H+m, x*CELL_W+CELL_W-m, nextY+CELL_H+CELL_H-m);
break;
case 3:
g.fillRect(x*CELL_W+1, nextY+CELL_H+1, x*CELL_W+CELL_W-1, nextY+CELL_H+CELL_H-1);
break;
default:
break;
}
g.setColor(this.nrgb[this.settings().tdyNumClr]); //today color or fg color
}else if(this.settings().suClr && rD.getDay()==0){ //sundays
g.setColor(this.nrgb[this.settings().suClr]);
}else{ //default
g.setColor(g.theme.fg);
}
g.drawString(rDate, x*CELL_W+((CELL_W-g.stringWidth(rDate))/2)+2, nextY+((CELL_H-DAY_NUM_FONT_SIZE+2)/2)+(CELL_H*y));
rD.setDate(rDate+1);
rDate=rD.getDate();
}
}
if (this.settings().calBrdr) {
g.setColor(g.theme.fg).drawRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H-1);
}
}
/**
* calculates current ISO8601 week number e.g. 2
* @param{Date} date for the date
* @returns{Number}} e.g. 2
*/
ISO8601calWeek(date){ //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4){
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return Number(1 + Math.ceil((firstThursday - tdt) / 604800000));
}
}
//*************************************************************************************
//*************************************************************************************
//*************************************************************************************
//Copy ABOVE the src code of clock-app class and load via espruino WEB IDE
//*************************************************************************************
//*************************************************************************************
//*************************************************************************************
/**
* Severity for logging
*/
const LogSeverity={
DEBUG: 5,
INFO: 4,
WARNING: 3,
ERROR: 2,
EXCEPTION: 1
};
/**
* Exception: Mandatory Field not provided
*/
class EmptyMandatoryError extends Error{
/**
* Create Exception
* @param {String} name of the field
* @param {*} given data e.g. an object
* @param {*} expected *optional* an working example
*/
constructor(name, given, expected) {
this.field = name;
this.got = given;
this.message = "Missing mandatory '"+ name +"'. given '"+JSON.stringify(given)+"'";
if (expected) {
this.message+= " != expected: '"+JSON.stringify(expected)+"'";
this.sample = expected;
}
Error(this.message);
}
toString() {
return this.message;
}
}
/**
* Exception: Invalid Function
*/
class InvalidMethodName extends Error{
/**
* Create Exception
* @param {String} name of the field
* @param {*} given data e.g. an object
* @param {*} expected *optional* an working example
*/
constructor(className, methodName) {
this.class = className;
this.method = methodName;
this.message = "Function '"+methodName+"' not found in '"+className+"'";
Error(this.message);
}
toString() {
return this.message;
}
}
/*************************************************************************/
/**
* All Test Masterclass
*/
class Test{
}
/*************************************************************************/
/**
* Test Settings - use this if you want e.g. test draw/render function(s)
*/
class TestSetting extends Test{
TEST_SETTING_SAMPLE() {
return {
setting: "<settingName>",
cases: [
{
value: "required,<settingValue>",
beforeTxt: "optional,<textToDisplayBeforeTest>",
beforeExpression: "optional,<expressionExpectedTrue>",
afterText: "optional,<textToDisplayAfterTest>",
afterExpression: "optional,<expressionExpectedTrue>"
}
],
constructorParams: ["optional: <cpar1>","|TEST_SETTINGS|","..."], //TEST_SETTINGS will be replcaed with each current {setting: case}
functionNames: ["required, <function under test>", "..."],
functionParams: ["optional: <fpar1>","|TEST_SETTINGS|","..."]
};
}
constructor(data){
this._validate(data);
this.setting = data.setting;
this.cases = data.cases.map((entry) => {
return {
value: entry.value,
beforeTxt: entry.beforeTxt||"",
beforeExpression: entry.beforeExpression||true,
afterTxt: entry.afterTxt||"",
afterExpression: entry.afterExpression||true
};
});
this.constructorParams = data.constructorParams;
this.functionNames = data.functionNames;
this.functionParams = data.functionParams;
}
/**
* validates the given data config
*/
_validate(data){
//validate given config
if (!data.setting) throw new EmptyMandatoryError("setting", data, this.TEST_SETTING_SAMPLE());
if (!(data.cases instanceof Array) || data.cases.length==0) throw new EmptyMandatoryError("cases", data, this.TEST_SETTING_SAMPLE());
if (!(data.functionNames instanceof Array) || data.functionNames==0) throw new EmptyMandatoryError("functionNames", data, this.TEST_SETTING_SAMPLE());
data.cases.forEach((entry,idx) => {
if (entry.value === undefined) throw new EmptyMandatoryError("cases["+idx+"].value", entry, this.TEST_SETTING_SAMPLE());
});
}
}
/*************************************************************************/
/**
* Testing a Bangle object
*/
class BangleTestRunner{
/**
* create for ObjClass
* @param {Class} objClass
* @param {LogSeverity} minSeverity to Log
*/
constructor(objClass, minSeverity){
this.TESTCASE_MSG_BEFORE_TIMEOUT = 1000; //5s
this.TESTCASE_RUN_TIMEOUT = 1000; //5s
this.TESTCASE_MSG_AFTER_TIMEOUT = 1000; //5s
this.oClass = objClass;
this.minSvrty = minSeverity;
this.tests = [];
this.currentCaseNum = this.currentTestNum = this.currentTest = this.currentCase = undefined;
}
/**
* add a Setting Test, return instance for chaining
* @param {TestSetting}
*/
addTestSettings(sttngs) {
this.tests.push(new TestSetting(sttngs));
return this;
}
/**
* Test execution of all tests
*/
execute() {
this._init();
while (this._nextTest()) {
this._beforeTest();
while (this._nextCase()) {
this._beforeCase();
this._runCase();
this._afterCase();
}
this._afterTest();
this._firstCase();
}
this._exit();
}
/**
* global prepare - before all test
*/
_init() {
console.log(this._nowTime(), ">>init");
this.currentTestNum=-1;
this.currentCaseNum=-1;
}
/**
* before each test
*/
_beforeTest() {
console.log(this._nowTime(), ">>test #" + this.currentTestNum);
}
/**
* befor each testcase
*/
_beforeCase() {
console.log(this.currentTest);
console.log(this._nowTime(), ">>case #" + this.currentTestNum + "." + this.currentCaseNum + "/" + (this.currentTest.cases.length-1));
if (this.currentTest instanceof TestSetting)
console.log(this.currentTest.setting+"="+this.currentCase.value+"/n"+(this.currentCase.beforeTxt ? "#"+this.currentCase.beforeTxt : ""));
}
/**
* testcase runner
*/
_runCase() {
console.log(this._nowTime(), ">>running...");
var returns = [];
this.currentTest.functionNames.forEach((fName) => {
var settings={}; settings[this.currentTest.setting] = this.currentCase.value;
var cParams = this.currentTest.constructorParams||[];
cParams = cParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params
var fParams = this.currentTest.functionParams||[];
fParams = fParams.map((v) => (v && v instanceof String && v==="|TEST_SETTINGS|") ? settings : v);//replace settings in call params
var creatorFunc = new Function("console.log('Constructor params:', arguments); return new " + this.oClass + "(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9])"); //prepare spwan arguments[0],arguments[1]
let instance = creatorFunc.call(this.oClass, cParams[0], cParams[1], cParams[2], cParams[3], cParams[4], cParams[5], cParams[6], cParams[7], cParams[8], cParams[9]); //spwan
console.log(">>"+this.oClass+"["+fName+"]()");
console.log('Instance:', instance);
console.log('Function params:', fParams);
returns.push(instance[fName](fParams[0], fParams[1], fParams[2], fParams[3], fParams[4], fParams[5], fParams[6], fParams[7], fParams[8], fParams[9])); //run method and store result
g.dump();
console.log("<<"+this.oClass+"["+fName+"]()");
});
console.log(this._nowTime(), "<<...running");
}
/**
* after each testcase
*/
_afterCase() {
if (this.currentTest instanceof TestSetting)
if (this.currentCase.afterTxt.length>0)
console.log("++EXPECTED:" + this.currentCase.afterTxt + "EXPECTED++");
console.log(this._nowTime(), "<<case #" + this.currentTestNum + "." + this.currentCaseNum + "/" + (this.currentTest.cases.length-1));
}
/**
* after each test
*/
_afterTest() {
console.log(this._nowTime(), "<<test #" + this.currentTestNum);
}
/**
* after all tests
*/
_exit() {
console.log(this._nowTime(), "<<exit");
}
/**
* delays for x seconds
* @param {Number} sec to delay
*/
_delay(sec) {
return new Promise(resolve => setTimeout(resolve, sec));
}
_waits(sec) {
this._delay(1).then();
}
_log() {
}
_nextTest() {
if (this.currentTestNum>=-1 && (this.currentTestNum+1)<this.tests.length) {
this.currentTestNum++; this.currentTest = this.tests[this.currentTestNum];
return true;
}
return false;
}
_firstCase() {
this.currentCaseNum=-1;
}
_nextCase() {
if (this.currentCaseNum>=-1 && (this.currentCaseNum+1)<this.currentTest.cases.length) {
this.currentCaseNum++; this.currentCase = this.currentTest.cases[this.currentCaseNum];
return true;
}
return false;
}
_nowTime() {
d = new Date();
return(("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2) + ":" + ("0" + d.getSeconds()).slice(-2) + "." + ("00" + d.getMilliseconds()).slice(-3));
}
}
/**
* TEST all Settings
*/
new BangleTestRunner("TimeCalClock", LogSeverity.INFO)
/*
.addTestSettings({
setting: "shwDate",
cases: [
{ value: 0, beforeTxt:"No date display?", afterTxt: "top area should be 'emtpy'" },
{ value: 1, beforeTxt:"Locale date display?", afterTxt: "date should be 06/05/1234" },
{ value: 2, beforeTxt:"Month longname?", afterTxt: "date should be June" },
{ value: 3, beforeTxt:"Monthshort yearshort #week", afterTxt: "date should be Jun.34 #23" }
],
constructorParams: [new Date(1234,5,6,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawDate"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,3,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,4,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,5,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,6,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,7,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: 0, beforeTxt:"Week start Sunday?" , afterTxt: "Calendar first day is Sunday" },
{ value: 1, beforeTxt:"Week start Monday?" , afterTxt: "Calendar first day is Monday"},
{ value: 2, beforeTxt:"Week start Tuesday?" , afterTxt: "Calendar first day is Tuesday"},
{ value: 3, beforeTxt:"Week start Wednesday?" , afterTxt: "Calendar first day is Wednesday"},
{ value: 4, beforeTxt:"Week start Thursday?" , afterTxt: "Calendar first day is Thursday"},
{ value: 5, beforeTxt:"Week start Friday?" , afterTxt: "Calendar first day is Friday"},
{ value: 6, beforeTxt:"Week start Saturday?" , afterTxt: "Calendar first day is Saturday"},
],
constructorParams: [new Date(1234,5,8,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Sunday in mid?" , afterTxt: "Calendar focus today: Sunday" },
],
constructorParams: [new Date(1234,5,3,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Monday in mid?" , afterTxt: "Calendar focus today: Monday" },
],
constructorParams: [new Date(1234,5,4,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Tuesday in mid?" , afterTxt: "Calendar focus today: Tuesday" },
],
constructorParams: [new Date(1234,5,5,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Wednesday in mid?" , afterTxt: "Calendar focus today: Wednesday" },
],
constructorParams: [new Date(1234,5,6,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Thursday in mid?" , afterTxt: "Calendar focus today: Thursday" },
],
constructorParams: [new Date(1234,5,7,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Friday in mid?" , afterTxt: "Calendar focus today: Friday" },
],
constructorParams: [new Date(1234,5,8,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
.addTestSettings({
setting: "wdStrt",
cases: [
{ value: -1, beforeTxt:"Saturday in mid?" , afterTxt: "Calendar focus today: Saturday" },
],
constructorParams: [new Date(1234,5,9,7,8,9),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "tdyNumClr",
cases: [
{ value: 1, beforeTxt:"Today color: red?" , afterTxt: "Today is marked red" },
{ value: 2, beforeTxt:"Today color: green?" , afterTxt: "Today is marked green" },
{ value: 3, beforeTxt:"Today color: blue?" , afterTxt: "Today is marked blue" },
],
constructorParams: [new Date(),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "tdyMrkr",
cases: [
{ value: 1, beforeTxt:"Today highlight cricle?" , afterTxt: "Today circled." },
{ value: 2, beforeTxt:"Today highlight rectangle?" , afterTxt: "Today rectangled." },
{ value: 3, beforeTxt:"Today highlight filled?" , afterTxt: "Today filled." },
],
constructorParams: [new Date(),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "suClr",
cases: [
{ value: 1, beforeTxt:"Sundays color: red?" , afterTxt: "Sundays are red" },
{ value: 2, beforeTxt:"Sundays color: green?" , afterTxt: "Sundays are green" },
{ value: 3, beforeTxt:"Sundays color: blue?" , afterTxt: "Sundays are blue" },
],
constructorParams: [new Date(),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
/*
.addTestSettings({
setting: "calBrdr",
cases: [
{ value: false, beforeTxt:"Calendar without border?" , afterTxt: "No outer border." },
{ value: true, beforeTxt:"Calendar with border?" , afterTxt: "Outer border." },
],
constructorParams: [new Date(),"|TEST_SETTINGS|"],
functionNames: ["drawCal"],
functionParams: [],
})
*/
.execute();

View File

@ -1,94 +1,274 @@
var center = g.getWidth() / 2;
var lastDayDraw;
var lastTimeDraw;
//Clock renders date, time and pre,current,next week calender view
class TimeCalClock{
DATE_FONT_SIZE(){ return 20; }
TIME_FONT_SIZE(){ return 40; }
/**
* @param{Date} date optional the date (e.g. for testing)
* @param{Settings} settings optional settings to use e.g. for testing
*/
constructor(date, settings){
if (date)
this.date=date;
var fontColor = g.theme.fg;
var accentColor = "#FF0000";
var locale = require("locale");
if (settings)
this._settings = settings;
else
this._settings = require("Storage").readJSON("timecal.settings.json", 1) || {};
function loop() {
var d = new Date();
var cleared = false;
if(lastDayDraw != d.getDate()){
lastDayDraw = d.getDate();
drawDate(d);
drawCal(d);
}
if(lastTimeDraw != d.getMinutes() || cleared){
lastTimeDraw = d.getMinutes();
drawTime(d);
}
}
function drawTime(d){
var hour = ("0" + d.getHours()).slice(-2);
var min = ("0" + d.getMinutes()).slice(-2);
g.setFontAlign(0,-1,0);
g.setFont("Vector",40);
g.setColor(fontColor);
g.clearRect(0,50,g.getWidth(),90);
g.drawString(hour + ":" + min,center,50);
}
function drawDate(d){
var day = ("0" + d.getDate()).slice(-2);
var month = ("0" + d.getMonth()).slice(-2);
var dateStr = locale.date(d,1);
g.clearRect(0,24,g.getWidth(),44);
g.setFont("Vector",20);
g.setColor(fontColor);
g.setFontAlign(0,-1,0);
g.drawString(dateStr,center,24);
}
const defaults = {
shwDate:1, //0:none, 1:locale, 2:month, 3:monthshort.year #week
wdStrt:0, //identical to getDay() 0->Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today
tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled
tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkPxl:3, //px
suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
//phColor:"#E00", //public holiday
calBrdr:false
};
for (const k in this._settings) if (!defaults.hasOwnProperty(k)) delete this._settings[k]; //remove invalid settings
for (const k in defaults) if(!this._settings.hasOwnProperty(k)) this._settings[k] = defaults[k]; //assign missing defaults
g.clear();
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
this.centerX = Bangle.appRect.w/2;
this.nrgb = [g.theme.fg, "#E00", "#0E0", "#00E"]; //fg, r ,g , b
this.ABR_DAY=[];
if (require("locale") && require("locale").dow)
for (let d=0; d<=6; d++) {
var refDay=new Date();
refDay.setFullYear(1972);
refDay.setMonth(0);
refDay.setDate(2+d);
this.ABR_DAY.push(require("locale").dow(refDay));
function drawCal(d){
var calStart = 101;
var cellSize = g.getWidth() / 7;
var halfSize = cellSize / 2;
g.clearRect(0,calStart,g.getWidth(),g.getHeight());
g.drawLine(0,calStart,g.getWidth(),calStart);
var days = ["Mo","Tu","We","Th","Fr","Sa","Su"];
g.setFont("Vector",10);
g.setColor(fontColor);
g.setFontAlign(-1,-1,0);
for(var i = 0; i < days.length;i++){
g.drawString(days[i],i*cellSize+5,calStart -11);
if(i!=0){
g.drawLine(i*cellSize,calStart,i*cellSize,g.getHeight());
}
}
var cellHeight = (g.getHeight() -calStart ) / 3;
for(var i = 0;i < 3;i++){
var starty = calStart + i * cellHeight;
g.drawLine(0,starty,g.getWidth(),starty);
}
g.setFont("Vector",15);
var dayOfWeek = d.getDay();
var dayRem = d.getDay() - 1;
if(dayRem <0){
dayRem = 0;
}
var start = new Date();
start.setDate(start.getDate()-(7+dayRem));
g.setFontAlign(0,-1,0);
for (var y = 0;y < 3; y++){
for(var x = 0;x < 7; x++){
if(start.getDate() === d.getDate()){
g.setColor(accentColor);
}else{
g.setColor(fontColor);
}
g.drawString(start.getDate(),x*cellSize +(cellSize / 2) + 2,calStart+(cellHeight*y) + 5);
start.setDate(start.getDate()+1);
else
this.ABR_DAY=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
}
/**
* @returns {Object} current settings object
*/
settings(){
return this._settings;
}
/*
* Run forest run
**/
draw(){
this.drawTime();
if (this.TZOffset===undefined || this.TZOffset!==d.getTimezoneOffset())
this.drawDateAndCal();
}
/**
* draw given or current time from date
* overwatch timezone changes
* schedules itself to update
*/
drawTime(){
d=this.date ? this.date : new Date();
const Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10;
d=d?d :new Date();
g.setFontAlign(0, -1).setFont("Vector", this.TIME_FONT_SIZE()).setColor(g.theme.fg)
.clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7)
.drawString(("0" + require("locale").time(d, 1)).slice(-5), this.centerX, Y, true);
//.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+this.TIME_FONT_SIZE()-7); //DEV-Option
setTimeout(this.draw.bind(this), 60000-(d.getSeconds()*1000)-d.getMilliseconds());
}
/**
* draws given date and cal
* @param{Date} d provide date or uses today
*/
drawDateAndCal(){
d=this.date ? this.date : new Date();
this.TZOffset=d.getTimezoneOffset();
this.drawDate();
this.drawCal();
if (this.tOutD) //abort exisiting
clearTimeout(this.tOutD);
this.tOutD=setTimeout(this.drawDateAndCal.bind(this), 86400000-(d.getHours()*24*60*1000)-(d.getMinutes()*60*1000)-d.getSeconds()-d.getMilliseconds());
}
/**
* draws given date as defiend in settings
*/
drawDate(){
d=this.date ? this.date : new Date();
const FONT_SIZE=20;
const Y=Bangle.appRect.y;
var render=false;
var dateStr = "";
if (this.settings().shwDate>0) { //skip if exactly -none
const dateSttngs = ["","l","M","m.Y #W"];
for (let c of dateSttngs[this.settings().shwDate]) { //add part as configured
switch (c){
case "l":{ //locale
render=true;
dateStr+=require("locale").date(d,1);
break;
}
case "m":{ //month e.g. Jan.
render=true;
dateStr+=require("locale").month(d,1);
break;
}
case "M":{ //month e.g. January
render=true;
dateStr+=require("locale").month(d,0);
break;
}
case "y":{ //year e.g. 22
render=true;
dateStr+=d.getFullYear().slice(-2);
break;
}
case "Y":{ //year e.g. 2022
render=true;
dateStr+=d.getFullYear();
break;
}
case "w":{ //week e.g. #2
dateStr+=(this.ISO8601calWeek(d));
break;
}
case "W":{ //week e.g. #02
dateStr+=("0"+this.ISO8601calWeek(d)).slice(-2);
break;
}
default: //append c
dateStr+=c;
render=dateStr.length>0;
break; //noop
}
}
}
if (render){
g.setFont("Vector", FONT_SIZE).setColor(g.theme.fg).setFontAlign(0, -1).clearRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3).drawString(dateStr,this.centerX,Y);
}
//g.drawRect(Bangle.appRect.x, Y, Bangle.appRect.x2, Y+FONT_SIZE-3); //DEV-Option
}
/**
* draws calender week view (-1,0,1) for given date
*/
drawCal(){
d=this.date ? this.date : new Date();
const DAY_NAME_FONT_SIZE=10;
const CAL_Y=Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3;
const CAL_AREA_H=Bangle.appRect.h-CAL_Y+24; //+24: top widegtes only
const CELL_W=Bangle.appRect.w/7; //cell width
const CELL_H=(CAL_AREA_H-DAY_NAME_FONT_SIZE)/3; //cell heigth
const DAY_NUM_FONT_SIZE=Math.min(CELL_H-1,15); //size down, max 15
g.setFont("Vector", DAY_NAME_FONT_SIZE).setColor(g.theme.fg).setFontAlign(-1, -1).clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H);
//draw grid & Headline
const dNames = this.ABR_DAY.map((a) => a.length<=2 ? a : a.substr(0, 2)); //force shrt 2
for(var dNo=0; dNo<dNames.length; dNo++){
const dIdx=this.settings().wdStrt>=0 ? (dNo+this.settings().wdStrt)%7 : (dNo+d.getDay()+4)%7;
const dName=dNames[dIdx];
if(dNo>0)
g.drawLine(dNo*CELL_W, CAL_Y, dNo*CELL_W, CAL_Y+CAL_AREA_H-1);
if (dIdx==0) g.setColor(this.nrgb[this.settings().suClr]); //sunday maybe colorize txt
g.drawString(dName, dNo*CELL_W+(CELL_W-g.stringWidth(dName))/2+2, CAL_Y+1).setColor(g.theme.fg);
}
var nextY=CAL_Y+DAY_NAME_FONT_SIZE;
for(i=0; i<3; i++){
const y=nextY+i*CELL_H;
g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y);
}
g.setFont("Vector", DAY_NUM_FONT_SIZE);
//write days
const tdyDate=d.getDate();
const days=this.settings().wdStrt>=0 ? 7+((7+d.getDay()-this.settings().wdStrt)%7) : 10;//start day (week before=7 days + days in this week realtive to week start) or fixed 7+3 days
var rD=new Date(d.getTime());
rD.setDate(rD.getDate()-days);
var rDate=rD.getDate();
for(var y=0; y<3; y++){
for(var x=0; x<dNames.length; x++){
if(rDate===tdyDate){ //today
g.setColor(this.nrgb[this.settings().tdyMrkClr]); //today marker color or fg color
switch(this.settings().tdyMrkr){ //0:none, 1:circle, 2:rectangle, 3:filled
case 1:
for(m=1; m<=this.settings().tdyMrkPxl&&m<CELL_H-1&&m<CELL_W-1; m++)
g.drawCircle(x*CELL_W+(CELL_W/2)+1, nextY+(CELL_H*y)+(CELL_H/2)+1, Math.min((CELL_W-m)/2, (CELL_H-m)/2)-2);
break;
case 2:
for(m=1; m<=this.settings().tdyMrkPxl&&m<CELL_H-1&&m<CELL_W-1; m++)
g.drawRect(x*CELL_W+m, nextY+CELL_H+m, x*CELL_W+CELL_W-m, nextY+CELL_H+CELL_H-m);
break;
case 3:
g.fillRect(x*CELL_W+1, nextY+CELL_H+1, x*CELL_W+CELL_W-1, nextY+CELL_H+CELL_H-1);
break;
default:
break;
}
g.setColor(this.nrgb[this.settings().tdyNumClr]); //today color or fg color
}else if(this.settings().suClr && rD.getDay()==0){ //sundays
g.setColor(this.nrgb[this.settings().suClr]);
}else{ //default
g.setColor(g.theme.fg);
}
g.drawString(rDate, x*CELL_W+((CELL_W-g.stringWidth(rDate))/2)+2, nextY+((CELL_H-DAY_NUM_FONT_SIZE+2)/2)+(CELL_H*y));
rD.setDate(rDate+1);
rDate=rD.getDate();
}
}
if (this.settings().calBrdr) {
g.setColor(g.theme.fg).drawRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H-1);
}
}
/**
* calculates current ISO8601 week number e.g. 2
* @param{Date} date for the date
* @returns{Number}} e.g. 2
*/
ISO8601calWeek(date){ //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4){
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return Number(1 + Math.ceil((firstThursday - tdt) / 604800000));
}
}
g.clear();
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
loop();
setInterval(loop,1000);
timeCalClock = new TimeCalClock(); timeCalClock.draw();
//hook on settime to redraw immediatly
var _setTime = setTime;
var setTime = function(t) {
_setTime(t);
timeCalClock.draw(true);
};

View File

@ -0,0 +1,109 @@
// Settings menu for Time calendar clock
(function(exit) {
ABR_DAY = require("locale") && require("locale").abday ? require("locale").abday : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var FILE = "timecal.validSttngs.json";
const DEFAULTS = {
shwDate:1, //0:none, 1:locale, 2:month, 3:monthshort.year #week
wdStrt:0, //identical to getDay() 0->Su, 1->Mo, ... //Issue #1154: weekstart So/Mo, -1 for use today
tdyNumClr:3, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkr:0, //0:none, 1:circle, 2:rectangle, 3:filled
tdyMrkClr:2, //1:red=#E00, 2:green=#0E0, 3:blue=#00E
tdyMrkPxl:3, //px
suClr:1, //0:fg, 1:red=#E00, 2:green=#0E0, 3:blue=#00E
//phColor:"#E00", //public holiday
calBrdr:false
};
validSttngs = require("Storage").readJSON("timecal.validSttngs.json", 1) || {};
for (const k in validSttngs) if (!DEFAULTS.hasOwnProperty(k)) delete this.validSttngs[k]; //remove invalid settings
for (const k in DEFAULTS) if(!validSttngs.hasOwnProperty(k)) validSttngs[k] = validSttngs[k]; //assign missing defaults
var changedSttngs = Object.assign({}, validSttngs);
var saveExitSettings = () => {
require('Storage').writeJSON(FILE, changedSttngs);
exit();
};
var cancelExitSettings = () => {
require('Storage').writeJSON(FILE, validSttngs);
exit();
};
var showMainMenu = () => {
E.showMenu({
"": {
"title": "TimeCal "+ /*LANG*/"settings"
},
/*LANG*/"< Save": () => saveExitSettings(),
/*LANG*/"Show date": {
value: validSttngs.shwDate,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"locale", /*LANG*/"M", /*LANG*/"m.Y #W"][v],
onchange: v => validSttngs.shwDate = v
},
/*LANG*/"Start wday": {
value: validSttngs.wdStrt,
min: -1, max: 6,
format: v => v>=0 ? ABR_DAY[v] : /*LANG*/"today",
onchange: v => validSttngs.wdStrt = v
},
/*LANG*/"Su color": {
value: validSttngs.suClr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => validSttngs.suClr = v
},
/*LANG*/"Border": {
value: validSttngs.calBrdr,
format: v => v ? /*LANG*/"show" : /*LANG*/"none",
onchange: v => validSttngs.calBrdr = v
},
/*LANG*/"Today settings": () => {
showTodayMenu();
},
/*LANG*/"< Cancel": () => cancelExitSettings()
});
};
var showTodayMenu = () => {
E.showMenu({
"": {
"title": /*LANG*/"Today settings"
},
"< Back": () => showMainMenu(),
/*LANG*/"Color": {
value: validSttngs.tdyNumClr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => validSttngs.tdyNumClr = v
},
/*LANG*/"Marker": {
value: validSttngs.tdyMrkr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"circle", /*LANG*/"rectangle", /*LANG*/"filled"][v],
onchange: v => validSttngs.tdyMrkr = v
},
/*LANG*/"Mrk.Color": {
value: validSttngs.tdyMrkClr,
min: 0, max: 2,
format: v => [/*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => validSttngs.tdyMrkClr = v
},
/*LANG*/"Mrk.Size": {
value: validSttngs.tdyMrkPxl,
min: 1, max: 10,
format: v => v+"px",
onchange: v => validSttngs.tdyMrkPxl = v
},
/*LANG*/"< Cancel": () => cancelExitSettings()
});
};
showMainMenu();
});

View File

@ -1 +1,3 @@
0.01: New Widget!
0.02: Battery bar turns yellow on charge
Memory status bar does not trigger garbage collect

View File

@ -1,7 +1,7 @@
{
"id": "widbars",
"name": "Bars Widget",
"version": "0.01",
"version": "0.02",
"description": "Display several measurements as vertical bars.",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -42,19 +42,25 @@
if (top) g .clearRect(x,y, x+w-1,y+top-1); // erase above bar
if (f) g.setColor(col).fillRect(x,y+top, x+w-1,y+h-1); // even for f=0.001 this is still 1 pixel high
}
let batColor='#0f0';
function draw() {
g.reset();
const x = this.x, y = this.y,
m = process.memory();
m = process.memory(false);
let b=0;
// ==HRM== bar(x+(w*b++),y,'#f00'/*red */,bpm/200); // >200 seems very unhealthy; if we have no valid bpm this will just be empty space
// ==Temperature== bar(x+(w*b++),y,'#ff0'/*yellow */,E.getTemperature()/50); // you really don't want to wear a watch that's hotter than 50°C
bar(x+(w*b++),y,g.theme.dark?'#0ff':'#00f'/*cyan/blue*/,1-(require('Storage').getFree() / process.env.STORAGE));
bar(x+(w*b++),y,'#f0f'/*magenta*/,m.usage/m.total);
bar(x+(w*b++),y,'#0f0'/*green */,E.getBattery()/100);
bar(x+(w*b++),y,batColor,E.getBattery()/100);
}
let redraw;
Bangle.on('charging', function(charging) {
batColor=charging?'#ff0':'#0f0';
WIDGETS["bars"].draw();
});
Bangle.on('lcdPower', on => {
if (redraw) clearInterval(redraw)
redraw = undefined;