Merge branch 'espruino:master' into master

pull/1527/head
Andrew Gregory 2022-02-22 21:16:37 +08:00 committed by GitHub
commit ccefde8e59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 2550 additions and 314 deletions

View File

@ -2,3 +2,4 @@ apps/animclk/V29.LBM.js
apps/banglerun/rollup.config.js
apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js
*.test.js

View File

@ -0,0 +1,356 @@
/*
7x7DotsClock
by Peter Kuppelwieser
*/
let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {});
// position on screen
var Xs = 0, Ys = 30,Xe = 175, Ye=175;
//const Xs = 0, Ys = 0,Xe = 175, Ye=175;
var SegH = (Ye-Ys)/2,SegW = (Xe-Xs)/2;
var Dx = SegW/14, Dy = SegH/16;
const hColor = [1,1,1];
const mColor = [0.3,0.3,1];
const bColor = [0.2,0.2,0.2];
const Font = [
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0],
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,0,0],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[0,0,0,1,1,1,1],
[0,0,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[1,1,0,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,0,1,1,0,0],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,0,0,1,1,0,0],
[0,0,0,1,1,0,0]
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,0,0],
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[1,1,0,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,0,0,0,0,0],
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1]
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1]
],
[
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,0,0,0,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,0,0,0,0,1,1],
[0,0,0,0,0,1,1]
],
];
// Global Vars
var dho = -1, eho = -1, dmo = -1, emo = -1;
function drawHSeg(x1,y1,x2,y2,Num,dColor,Size) {
g.setColor(0,0,0);
g.fillRect(x1, y1, x2, y2);
for (let i = 1; i < 8; i++) {
for (let j = 1; j < 8; j++) {
if (Font[Num][j-1][i-1] == 1) {
g.setColor(dColor[0],dColor[1],dColor[2]);
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,Size);
} else {
g.setColor(bColor[0],bColor[1],bColor[2]);
g.fillCircle(x1+Dx+(i-1)*(x2-x1)/7,y1+Dy+(j-1)*(y2-y1)/7,1);
}
}
}
}
function drawSSeg(x1,y1,x2,y2,Num,dColor,Size) {
for (let i = 1; i < 8; i++) {
for (let j = 1; j < 8; j++) {
if (Font[Num][j-1][i-1] == 1) {
g.setColor(dColor[0],dColor[1],dColor[2]);
g.fillCircle(x1+(i-1)*(x2-x1)/7,y1+(j-1)*(y2-y1)/7,Size);
}
}
}
}
function ShowSecons() {
g.setColor(1,1,1);
g.fillRect((Xe-Xs) / 2 - 14 + Xs -3,
(Ye-Ys) / 2 - 7 + Ys -3,
(Xe-Xs) / 2 + 14 + Xs +1,
(Ye-Ys) / 2 + 7 + Ys +1);
drawSSeg( (Xe-Xs) / 2 - 14 + Xs -1,
(Ye-Ys) / 2 - 7 + Ys ,
(Xe-Xs) / 2 + Xs -1,
(Ye-Ys) / 2 + 7 + Ys,
ds,mColor,1);
drawSSeg( (Xe-Xs) / 2 + Xs +1,
(Ye-Ys) / 2 - 7 + Ys,
(Xe-Xs) / 2 + 14 + Xs +1,
(Ye-Ys) / 2 + 7 + Ys,
es,mColor,1);
}
function draw() {
// work out how to display the current time
var d = new Date();
var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
dh = Math.floor(h/10);
eh = h - dh * 10;
dm = Math.floor(m/10);
em = m - dm * 10;
ds = Math.floor(s/10);
es = s - ds * 10;
// Reset the state of the graphics library
g.reset();
if (dh != dho) {
g.setColor(1,1,1);
drawHSeg(Xs, Ys, Xs+SegW, Ys+SegH,dh,hColor,4);
dho = dh;
}
if (eh != eho) {
g.setColor(1,1,1);
drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,hColor,4);
eho = eh;
}
if (dm != dmo) {
g.setColor(0.3,0.3,1);
drawHSeg(Xs, Ys+SegH+Dy, Xs+SegW, Ys+SegH*2,dm,mColor,4);
dmo = dm;
}
if (em != emo) {
g.setColor(0.3,0.3,1);
drawHSeg(Xs+SegW+Dx, Ys+SegH+Dy, Xs+SegW*2, Ys+SegH*2,em,mColor,4);
emo = em;
}
if (!Bangle.isLocked()) ShowSecons();
}
function actions(v){
if(BTN1.read() === true) {
print("BTN pressed");
Bangle.showLauncher();
}
if(v==-1){
print("up swipe event");
if(settings.swupApp != "") load(settings.swupApp);
print(settings.swupApp);
} else if(v==1) {
print("down swipe event");
if(settings.swdownApp != "") load(settings.swdownApp);
print(settings.swdownApp);
} else {
print("touch event");
}
}
// Get Messages status
var messages = require("Storage").readJSON("messages.json",1)||[];
//var BTconnected = NRF.getSecurityStatus().connected;
//NRF.on('connect',BTconnected = NRF.getSecurityStatus().connected);
//NRF.on('disconnect',BTconnected = NRF.getSecurityStatus().connected);
function drawWidgeds() {
//Bluetooth
//print(BluetoothDevice.connected);
var x1Bt = 160;
var y1Bt = 0;
var x2Bt = x1Bt + 30;
var y2Bt = y2Bt;
if (NRF.getSecurityStatus().connected)
g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
else
g.setColor(g.theme.dark ? "#666" : "#999");
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),x1Bt,y1Bt);
//Battery
//print(E.getBattery());
//print(Bangle.isCharging());
var x1B = 130;
var y1B = 2;
var x2B = x1B + 20;
var y2B = y1B + 15;
g.setColor(g.theme.bg);
g.clearRect(x1B,y1B,x2B,y2B);
g.setColor(g.theme.fg);
g.drawRect(x1B,y1B,x2B,y2B);
g.fillRect(x1B,y1B,x1B+(E.getBattery()*(x2B-x1B)/100),y2B);
g.fillRect(x2B,y1B+(y2B-y1B)/2-3,x2B+4,y1B+(y2B-y1B)/2+3);
//Messages
var x1M = 100;
var y1M = y1B;
var x2M = x1M + 30;
var y2M = y2B;
if (messages.some(m=>m.new)) {
g.setColor(g.theme.fg);
g.fillRect(x1M,y1M,x2M,y2M);
g.setColor(g.theme.bg);
g.drawLine(x1M,y1M,x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2);
g.drawLine(x1M+(x2M-x1M)/2,y1M+(y2M-y1M)/2,x2M,y1M);
}
var strDow = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
var d = new Date();
var dow = d.getDay(),day = d.getDate(), month = d.getMonth() + 1, year = d.getFullYear();
print(strDow[dow] + ' ' + day + '.' + month + ' ' + year);
g.setFontAlign(-1, -1,0);
g.setFont("Vector", 20);
g.drawString(strDow[dow] + ' ' + day, 0, 0, true);
}
function SetFull(on) {
dho = -1; eho = -1; dmo = -1; emo = -1;
g.clear();
if (on === true) {
Ys = 0;
Bangle.setUI("clock");
Bangle.on('swipe', function(direction) { });
} else {
Ys = 30;
Bangle.setUI("updown",actions);
Bangle.on('swipe', function(direction) {
switch (direction) {
case 1:
print("swipe left event");
if(settings.swleftApp != "") load(settings.swleftApp);
print(settings.swleftApp);
break;
case -1:
print("swipe right event");
if(settings.swrightApp != "") load(settings.swrightApp);
print(settings.swrightApp);
break;
default:
print("swipe undefined event");
}
});
}
SegH = (Ye-Ys)/2;
Dy = SegH/16;
draw();
if (on != true) {
//Bangle.loadWidgets();
//Bangle.drawWidgets();
drawWidgeds();
}
}
Bangle.on('lock', function(on) {
SetFull(on);
});
SetFull(Bangle.isLocked());
var secondInterval = setInterval(draw, 1000);

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEBAkTmEzkAHDmcjmQBBmcTmICCgMAiMAkE/+P/mEQgMQgH/n/zAIP/l/yA4QvXC4kDkEjFgIACkcSmMTkMyBoQHBI4kvI6wXBn8wA4c/mfzl8y+cfEoIaBVa5HBAAMQF4UgIoIBBBgJNBAwQ3BkfygSnJSQIUBkECiBoCL48DmCPFAA6PCX40jX4hYEU4LNBX4JHIkBHCBgJHBianKj8wO4IvHgSnBmJ3CHYqGCABcRcYTXLAA5KCFAJfCC4KnDX4anNgUgiSnMkQQBO5hvCl8yO4pHEd4oyBH4QBBU5TXHkcimUTkLXFL44HEiTbBO4MhBoQHBI4KECR45HGBoIFBU4y/BC4c/mYXGMQJHFiBHLEAIHCf5gAKhWg1UB0IEBjUA0MB0EAjQKCiANCCQOg0cxmcSmWjU4MqmcDmSnDBASkBmejCQIXFmYXEmYXHicyhRLC0AEBAIJFBAIIFCBAYHDF65fXR66vImUCnS8IkeinUBgERgEgcIMBgRHDBgLvCBYMQmcjBYIAHfwL7JiQLBichkcSnUSO4MhI4MxI5MSmMjPgMinCnCkRHGIgJHFiUgkUalUCAgMRkUCkIvIkUSkMC0EiBxAAI0UKkBHCkCPDgA+CI5Z3BmYPBAB53CV4MSEgcSiCnOR4cyR5JQEgBHCC4I0BC4UjC4MCxQXGF4IlBxRHB0UAlUK0BMBkIEBI5ILB0ZHBF4czlTXHI4mjCQIXOH4KnDC4MKgGqgGgAgIBBIoJHJBoQ="))

View File

@ -0,0 +1,65 @@
(function(back) {
let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {});
function showMainMenu() {
const mainMenu = {
"": {"title": "7x7 Dots Clock Settings"},
"< Back": ()=>load(),
"sw-up": ()=>showSelAppMenu("swupApp"),
"sw-down": ()=>showSelAppMenu("swdownApp"),
"sw-left": ()=>showSelAppMenu("swleftApp"),
"sw-right": ()=>showSelAppMenu("swrightApp")
};
E.showMenu(mainMenu);
}
function setSetting(key,value) {
print("call " + key + " = " + value);
settings[key] = value;
print("storing settings 7x7dotsclock.json");
storage.write('7x7dotsclock.json', settings);
}
function showSelAppMenu(key) {
var Apps = require("Storage").list(/\.info$/)
.map(app => {var a=storage.readJSON(app, 1);return (
a&&a.name != "Launcher"
&& a&&a.name != "Bootloader"
&& a&&a.type != "clock"
&& a&&a.type !="widget"
)?a:undefined})
.filter(app => app) // filter out any undefined apps
.sort((a, b) => a.sortorder - b.sortorder);
const SelAppMenu = {
'': {
'title': /*LANG*/'Select App',
},
'< Back': ()=>showMainMenu(),
};
Apps.forEach((app, index) => {
var label = app.name;
if (settings[key] === app.src) {
label = "* " + label;
}
SelAppMenu[label] = () => {
if (settings[key] !== app.src) {
setSetting(key,app.src);
showMainMenu();
}
};
});
if (Apps.length === 0) {
SelAppMenu[/*LANG*/"No Apps Found"] = () => { };
}
return E.showMenu(SelAppMenu);
}
showMainMenu();
})

View File

@ -0,0 +1 @@
0.01: Initial version for upload

View File

@ -0,0 +1,17 @@
# 7x7 dots clock
![](dotsfontclock.png)
looks best with dark theme so far
* A Clock with big numbers made of 7x7 dots
* system widgeds ar not (yet) supported
* when screen is locked it shows hours and minutes in full screen mode
![](dotsfontclock-scr1.png)
* when screen is unlocked it shows additional info: bluetooth, battery, new message, date and seconds
* you can configure a app per swipe direction
* when swiping the configured apps are launced
* button press opens launcher

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,17 @@
{ "id": "7x7dotsclock",
"name": "7x7 Dots Clock",
"shortName":"7x7 Dots Clock",
"version":"0.01",
"description": "A clock with a big 7x7 dots Font",
"icon": "dotsfontclock.png",
"tags": "clock",
"type": "clock",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7x7dotsclock.app.js","url":"7x7dotsclock.app.js"},
{"name":"7x7dotsclock.settings.js","url":"7x7dotsclock.settings.js"},
{"name":"7x7dotsclock.img","url":"7x7dotsclock.img.js","evaluate":true}
],
"data": [{"name":"7x7dotsclock.json"}]
}

View File

@ -7,4 +7,5 @@
when weekday name "On": weekday name is cut at 6th position and .#<week num> is added
0.06: fixes #1271 - wrong settings name
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
week is buffered until date or timezone changes
week is buffered until date or timezone changes
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)

View File

@ -1,7 +1,7 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.06",
"version": "0.07",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",

View File

@ -38,7 +38,7 @@
},
"< Back": () => back(),
"Seconds...": () => E.showMenu(secmenu),
"Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]),
"Date": stringInSettings("dateOnMain", ["Long", "Short", "ISO8601"]),
"Show Weekday": {
value: (settings.weekDay !== undefined ? settings.weekDay : true),
format: v => v ? "On" : "Off",
@ -56,7 +56,7 @@
}
},
"Uppercase": {
value: (settings.upperCase !== undefined ? settings.upperCase : false),
value: (settings.upperCase !== undefined ? settings.upperCase : true),
format: v => v ? "On" : "Off",
onchange: v => {
settings.upperCase = v;
@ -81,7 +81,7 @@
"< Back": () => E.showMenu(mainmenu),
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
"With \":\"": {
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false),
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : true),
format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsWithColon = v;
@ -89,14 +89,14 @@
}
},
"Color": {
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false),
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : true),
format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsColoured = v;
writeSettings();
}
},
"Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"])
"Date": stringInSettings("dateOnSecs", ["Year", "Weekday", "No"])
};
// Actually display the menu

View File

@ -1,2 +1,3 @@
0.01: New game! BTN4- Hit card, BTN5- Stand
0.02: ignore buttons on pauses
0.02: Ignore buttons on pauses
0.03: Support Bangle.js 2

207
apps/blackjack/appb2.js Normal file
View File

@ -0,0 +1,207 @@
var Clubs = require("heatshrink").decompress(atob("j0ewcBkmSpICipEAiQLHwA3BBY8gBQMEEA1AJwQgGyAKChILGBQUCFgxwDJpEAO5AVCII44CAQI1GAAg1GAAZQCWxCDEAAqJBQYQAFRIJWCAApcCR4YADPoRWCgQdBPopfCwAdBTw47BcBAvBU44vDfBDUIRIbUHATuQ"));
var Spades = require("heatshrink").decompress(atob("j0ewcBkmSpICuoALJIQILHpAKBJQ+QLIUJBYsgMoY1GBQcCBYmAPgkSEBEAgggIKApBDIg4KFHAZiCAAgsDBQw4DFitJFhQ4FTwplBgRoCSQoRBBYJ6EF4jgUwDUHAVOQA=="));
var Hearts = require("heatshrink").decompress(atob("j0ewY96gMkyAEByVIBQcSpILBhMkBYkEyQLBAQYKCCIQLEEwQgCBYuAEBFJkBBCBYw4CEA44CgQLHIYQsHLJsAEBJEHSQhxENwQADMQoAEKAdAWowLCYJESXggAFGowA/AAQ"));
var Diamonds = require("heatshrink").decompress(atob("j0ewY1ykgKJhIKJiVIEBOSoAKHpILBBQ+SBYOQBIsBCgILBwAKEgQgCAQIKEggICAQMgKwgUDAQI1GBY4IFLgoLGJpGSPoo4EMoxNIMoqSHiR6HLgizIPoLgfAFA"));
var deck = [];
var player = {Hand:[]};
var computer = {Hand:[]};
var ctx = {ready:true};
function createDeck() {
var suits = ["Spades", "Hearts", "Diamonds", "Clubs"];
var values = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];
var dck = [];
for (var i = 0 ; i < values.length; i++) {
for(var x = 0; x < suits.length; x++) {
dck.push({ Value: values[i], Suit: suits[x] });
}
}
return dck;
}
function shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
function EndGameMessdage(msg){
ctx.ready = false;
g.clearRect(0,160,176,176);
g.setColor(255,255,255);
g.fillRect(0,160,176,176);
g.setColor(0,0,0);
g.drawString(msg, 12, 155);
setTimeout(function(){
startGame();
}, 2500);
}
function hitMe() {
if (!ctx.ready) return;
player.Hand.push(deck.pop());
renderOnScreen(1);
var playerWeight = calcWeight(player.Hand, 0);
if(playerWeight == 21)
EndGameMessdage('WINNER');
else if(playerWeight > 21)
EndGameMessdage('LOSER');
}
function calcWeight(hand, hideCard) {
if(hideCard === 1) {
if (hand[0].Value == "J" || hand[0].Value == "Q" || hand[0].Value == "K")
return "10 +";
else if (hand[0].Value == "A")
return "11 +";
else
return parseInt(hand[0].Value) +" +";
}
else {
var weight = 0;
for(i=0; i<hand.length; i++){
if (hand[i].Value == "J" || hand[i].Value == "Q" || hand[i].Value == "K") {
weight += 10;
}
else if (hand[i].Value == "A") {
weight += 1;
}
else
weight += parseInt(hand[i].Value);
}
// Find count of aces because it may be 11 or 1
var numOfAces = hand.filter(function(x){ return x.Value === "A"; }).length;
for (var j = 0; j < numOfAces; j++) {
if (weight + 10 <= 21) {
weight +=10;
}
}
return weight;
}
}
function stand(){
if (!ctx.ready) return;
ctx.ready = false;
function sleepFor( sleepDuration ){
console.log("Sleeping...");
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }
}
renderOnScreen(0);
var playerWeight = calcWeight(player.Hand, 0);
var bangleWeight = calcWeight(computer.Hand, 0);
while(bangleWeight<17){
sleepFor(500);
computer.Hand.push(deck.pop());
renderOnScreen(0);
bangleWeight = calcWeight(computer.Hand, 0);
}
if (bangleWeight == playerWeight)
EndGameMessdage('TIES');
else if(playerWeight==21 || bangleWeight > 21 || bangleWeight < playerWeight)
EndGameMessdage('WINNER');
else if(bangleWeight > playerWeight)
EndGameMessdage('LOOSER');
}
function renderOnScreen(HideCard) {
const fontName = "6x8";
g.clear(); // clear screen
g.reset(); // default draw styles
g.setFont(fontName, 1);
g.setColor(255,255,255);
g.fillRect(Bangle.appRect);
g.setColor(0,0,0);
g.drawString('Hit', 176/4-10, 160);
g.drawString('Stand', 176/4+176/2-10, 160);
g.setFont(fontName, 3);
for(i=0; i<computer.Hand.length; i++){
g.drawImage(eval(computer.Hand[i].Suit), i*40, -1);
if(i == 1 && HideCard == 1)
g.drawString("?", i*40+8, 30);
else
g.drawString(computer.Hand[i].Value, i*40+8, 30);
}
g.setFont(fontName, 2);
g.drawString('AI has '+ calcWeight(computer.Hand, HideCard), 5, 55);
g.setFont(fontName, 3);
for(i=0; i<player.Hand.length; i++){
g.drawImage(eval(player.Hand[i].Suit), i*40, 83);
g.drawString(player.Hand[i].Value, i*40+8, 110);
}
g.setFont(fontName, 2);
g.drawString('You have ' + calcWeight(player.Hand, 0), 5, 133);
}
function dealHands() {
player.Hand= [];
computer.Hand= [];
ctx.ready = false;
setTimeout(function(){
player.Hand.push(deck.pop());
renderOnScreen(0);
}, 500);
setTimeout(function(){
computer.Hand.push(deck.pop());
renderOnScreen(1);
}, 1000);
setTimeout(function(){
player.Hand.push(deck.pop());
renderOnScreen(1);
}, 1500);
setTimeout(function(){
computer.Hand.push(deck.pop());
renderOnScreen(1);
ctx.ready = true;
}, 2000);
}
function startGame(){
deck = createDeck();
deck = shuffle(deck);
dealHands();
}
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.2);
var right = g.getWidth() - left;
var is_left = e.x < left;
var is_right = e.x > right;
if(is_left){
hitMe();
} else if(is_right){
stand();
}
});
setWatch(startGame, BTN1, {repeat:true, edge:"falling"});
startGame();

View File

@ -2,15 +2,16 @@
"id": "blackjack",
"name": "Black Jack game",
"shortName": "Black Jack game",
"version": "0.02",
"version": "0.03",
"description": "Simple implementation of card game Black Jack",
"icon": "blackjack.png",
"tags": "game",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"bangle1-black-jack-game-screenshot.png"}],
"allow_emulator": true,
"storage": [
{"name":"blackjack.app.js","url":"blackjack.app.js"},
{"name":"blackjack.app.js","url":"blackjack.app.js","supports": ["BANGLEJS"]},
{"name":"blackjack.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
{"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true}
]
}

View File

@ -2,3 +2,4 @@
0.03: Tweak for more efficient rendering, and firmware 2v06
0.04: Work with themes, smaller screens
0.05: Adjust hand lengths to be within 'tick' points
0.06: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".

View File

@ -129,14 +129,6 @@ Bangle.on('lcdPower', (on) => {
clearTimers();
}
});
Bangle.on('faceUp',function(up){
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
if (up && !Bangle.isLCDOn()) {
//console.log("faceUp and LCD off");
clearTimers();
Bangle.setLCDPower(true);
}
});
g.clear();
Bangle.loadWidgets();

View File

@ -1,7 +1,7 @@
{
"id": "boldclk",
"name": "Bold Clock",
"version": "0.05",
"version": "0.06",
"description": "Simple, readable and practical clock",
"icon": "bold_clock.png",
"screenshots": [{"url":"screenshot_bold.png"}],

View File

@ -4,3 +4,4 @@
0.22: Changed timing code, original "Nunito" Font is back!
0.23: Customizer! Unused fonts no longer take up precious memory.
0.24: Added previews to the customizer.
0.25: Fixed a bug that would let widgets change the color of the clock.

View File

@ -10,6 +10,7 @@ if (settings.fontIndex==undefined) {
function draw() {
var date = new Date();
// Draw day of the week
g.reset();
g.setFont("Teletext10x18Ascii");
g.clearRect(0,138,g.getWidth()-1,176);
g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18);

View File

@ -1,7 +1,7 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
"version":"0.24",
"version":"0.25",
"icon": "app.png",
"description": "A Minimalist clockface with large Digits. Now with more fonts!",
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],

View File

@ -7,3 +7,5 @@
0.07: Fix "previous" button image
0.08: Fix scrolling title background color
0.09: Move event listener from widget to boot code, stops music from showing up in messages
0.10: Simplify touch events
Remove date+time

View File

@ -3,9 +3,10 @@
If you have an Android phone with Gadgetbridge, this app allows you to view
and control music playback.
| Bangle.js 1 | Bangle.js 2 |
|:-------------------------------------------|:-------------------------------------------|
| ![Screenshot: Bangle 1](screenshot_v1.png) | ![Screenshot: Bangle 2](screenshot_v2.png) |
| Bangle.js 1 | Bangle.js 2 |
|:---------------------------------------------------------|:---------------------------------------------------------|
| ![Screenshot: Bangle 1 Dark theme](screenshot_v1_d.png) | ![Screenshot: Bangle 2 Darm theme](screenshot_v2_d.png) |
| ![Screenshot: Bangle 1 Light theme](screenshot_v1_l.png) | ![Screenshot: Bangle 2 Light theme](screenshot_v2_l.png) |
Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/).
@ -14,7 +15,6 @@ Download the [latest Gadgetbridge for Android here](https://f-droid.org/packages
* Dynamic colors based on Track/Artist/Album name
* Scrolling display for long titles
* Automatic start when music plays
* Time and date display
## Settings
@ -40,9 +40,7 @@ Disable double/triple pressing Middle Button: always simply toggle play/pause.
* Button 3 (*Bangle.js 1*): Volume down
### Touch
* Left: Pause/previous song
* Right: Next song/resume
* Center: Toggle play/pause
* Touch: Toggle play/pause
* Swipe left/right: Next/previous song
* Swipe up/down (*Bangle.js 2*): Volume up/down

View File

@ -10,15 +10,15 @@ const BANGLE2 = process.env.HWVERSION===2;
/**
* @param {string} text
* @return {number} Maximum font size to make text fit on screen
* @param {number} w Width to fit text in
* @return {number} Maximum font size to make text fit
*/
function fitText(text) {
function fitText(text, w) {
if (!text.length) {
return Infinity;
}
// make a guess, then shrink/grow until it fits
const w = Bangle.appRect.w,
test = (s) => g.setFont("Vector", s).stringWidth(text);
const test = (s) => g.setFont("Vector", s).stringWidth(text);
let best = Math.floor(100*w/test(100));
if (test(best)===w) { // good guess!
return best;
@ -106,7 +106,7 @@ function rTitle(l) {
rScroller(l); // already scrolling
return;
}
let size = fitText(l.label);
let size = fitText(l.label, l.w);
if (size<l.h/2) {
// the title is too long: start the scroller
scrollStart();
@ -119,7 +119,7 @@ function rTitle(l) {
* @param l
*/
function rInfo(l) {
let size = fitText(l.label);
let size = fitText(l.label, l.w);
if (size>l.h) {
size = l.h;
}
@ -182,23 +182,17 @@ function makeUI() {
type: "v", c: [
{
type: "h", fillx: 1, c: [
{id: "time", type: "txt", label: "88:88", valign: -1, halign: -1, font: "8%", bgCol: g.theme.bg},
{fillx: 1},
{id: "num", type: "txt", label: "88:88", valign: -1, halign: 1, font: "12%", bgCol: g.theme.bg},
BANGLE2 ? {} : {id: "up", type: "txt", label: " +", font: "6x8:2"},
{id: "num", type: "txt", label: "", valign: -1, halign: -1, font: "12%", bgCol: g.theme.bg},
BANGLE2 ? {} : {id: "up", type: "txt", label: " +", halign: 1, font: "6x8:2"},
],
},
{id: "title", type: "custom", label: "", fillx: 1, filly: 2, offset: null, font: "Vector:20%", render: rTitle, bgCol: g.theme.bg},
{id: "artist", type: "custom", label: "", fillx: 1, filly: 1, size: 30, render: rInfo, bgCol: g.theme.bg},
{id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg},
{height: 10},
{
type: "h", c: [
{width: 3},
{id: "prev", type: "custom", height: 15, width: 15, icon: "previous", render: rIcon, bgCol: g.theme.bg},
{id: "date", type: "txt", halign: 0, valign: 1, label: "", font: "8%", fillx: 1, bgCol: g.theme.bg},
{id: "next", type: "custom", height: 15, width: 15, icon: "next", render: rIcon, bgCol: g.theme.bg},
BANGLE2 ? {width: 3} : {id: "down", type: "txt", label: " -", font: "6x8:2"},
{id: "album", type: "custom", label: "", fillx: 1, filly: 1, size: 20, render: rInfo, bgCol: g.theme.bg},
BANGLE2 ? {} : {id: "down", type: "txt", label: " -", font: "6x8:2"},
],
},
{height: 10},
@ -211,20 +205,6 @@ function makeUI() {
// Self-repeating timeouts
///////////////////////
// Clock
let tock = -1;
function tick() {
if (!BANGLE2 && !Bangle.isLCDOn()) {
return;
}
const now = new Date();
if (now.getHours()*60+now.getMinutes()!==tock) {
drawDateTime();
tock = now.getHours()*60+now.getMinutes();
}
setTimeout(tick, 1000); // we only show minute precision anyway
}
// Fade out while paused and auto closing
let fade = null;
function fadeOut() {
@ -271,40 +251,12 @@ function scrollStop() {
////////////////////
// Drawing functions
////////////////////
/**
* Draw date and time
*/
function drawDateTime() {
const now = new Date();
const l = require("locale");
const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
if (is12) {
const d12 = new Date(now.getTime());
const hour = d12.getHours();
if (hour===0) {
d12.setHours(12);
} else if (hour>12) {
d12.setHours(hour-12);
}
layout.time.label = l.time(d12, true)+l.meridian(now);
} else {
layout.time.label = l.time(now, true);
}
layout.date.label = require("locale").date(now, true);
layout.render();
}
function drawControls() {
let l = layout;
if (BANGLE2) return;
const cc = a => (a ? "#f00" : "#0f0"); // control color: red for active, green for inactive
if (!BANGLE2) {
l.up.col = cc("volumeup" in tCommand);
l.down.col = cc("volumedown" in tCommand);
}
l.prev.icon = (stat==="play") ? "pause" : "previous";
l.prev.col = cc("prev" in tCommand || "pause" in tCommand);
l.next.icon = (stat==="play") ? "next" : "play";
l.next.col = cc("next" in tCommand || "play" in tCommand);
layout.up.col = cc("volumeup" in tCommand);
layout.down.col = cc("volumedown" in tCommand);
layout.render();
}
@ -339,6 +291,7 @@ function info(info) {
layout.album.col = infoColor("album");
layout.artist.col = infoColor("artist");
layout.num.label = formatNum(info);
layout.update();
layout.render();
rTitle(layout.title); // force redraw of title, or scroller might break
// reset auto exit interval
@ -473,37 +426,16 @@ function sendCommand(command) {
drawControls();
}
// touch/swipe: navigation
function togglePlay() {
sendCommand(stat==="play" ? "pause" : "play");
}
function pausePrev() {
sendCommand(stat==="play" ? "pause" : "previous");
}
function nextPlay() {
sendCommand(stat==="play" ? "next" : "play");
}
/**
* Setup touch+swipe for Bangle.js 1
*/
function touch1() {
Bangle.on("touch", side => {
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
switch(side) {
case 1:
pausePrev();
break;
case 2:
nextPlay();
break;
default:
togglePlay();
break;
}
});
Bangle.on("touch", togglePlay);
Bangle.on("swipe", dir => {
if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware
sendCommand(dir===1 ? "previous" : "next");
});
}
@ -511,16 +443,7 @@ function touch1() {
* Setup touch+swipe for Bangle.js 2
*/
function touch2() {
Bangle.on("touch", (side, xy) => {
const ar = Bangle.appRect;
if (xy.x<ar.x+ar.w/3) {
pausePrev();
} else if (xy.x>ar.x+ar.w*2/3) {
nextPlay();
} else {
togglePlay();
}
});
Bangle.on("touch", togglePlay);
// swiping
let drag;
Bangle.on("drag", e => {
@ -595,7 +518,6 @@ function startWatches() {
function start() {
makeUI();
startWatches();
tick();
startEmulator();
}

View File

@ -2,10 +2,11 @@
"id": "gbmusic",
"name": "Gadgetbridge Music Controls",
"shortName": "Music Controls",
"version": "0.09",
"version": "0.10",
"description": "Control the music on your Gadgetbridge-connected phone",
"icon": "icon.png",
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
"screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"},
{"url":"screenshot_v2_d.png"},{"url":"screenshot_v2_l.png"}],
"type": "app",
"tags": "tools,bluetooth,gadgetbridge,music",
"supports": ["BANGLEJS","BANGLEJS2"],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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

@ -15,7 +15,7 @@
0.10: Respect the 'new' attribute if it was set from iOS integrations
0.11: Open app when touching the widget (Bangle.js 2 only)
0.12: Extra app-specific notification icons
New animated notifcationicon (instead of large blinking 'MESSAGES')
New animated notification icon (instead of large blinking 'MESSAGES')
Added screenshots
0.13: Add /*LANG*/ comments for internationalisation
Add 'Delete All' option to message options
@ -31,3 +31,6 @@
0.19: Use a larger font for message text if it'll fit
0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031)
0.21: Improve list readability on dark theme
0.22: Add Home Assistant icon
0.22: Allow repeat to be switched Off, so there is no buzzing repetition.
Also gave the widget a pixel more room to the right

View File

@ -83,6 +83,7 @@ function getMessageImage(msg) {
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
if (s=="facebook") return getFBIcon();
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA=");
if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA==");
if (s=="gmail") return getNotificationImage();
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
@ -114,6 +115,7 @@ function getMessageImageCol(msg,def) {
"facebook": "#4267b2",
"gmail": "#ea4335",
"google home": "#fbbc05",
"home assistant": "#fff", // ha-blue is #41bdf5, but that's the background
"hangouts": "#1ba261",
"instagram": "#dd2a7b",
"messenger": "#0078ff",
@ -123,7 +125,7 @@ function getMessageImageCol(msg,def) {
"telegram": "#0088cc",
"twitter": "#1da1f2",
"whatsapp": "#4fce5d",
"wordfeud": "#dcc8bd",
"wordfeud": "#e7d3c7",
}[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg);
}

View File

@ -1,8 +1,8 @@
{
"id": "messages",
"name": "Messages",
"version": "0.21",
"description": "App to display notifications from iOS and Gadgetbridge",
"version": "0.22",
"description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",
"tags": "tool,system",

View File

@ -4,6 +4,7 @@
if (settings.vibrate===undefined) settings.vibrate=".";
if (settings.repeat===undefined) settings.repeat=4;
if (settings.unreadTimeout===undefined) settings.unreadTimeout=60;
settings.maxUnreadTimeout=240;
return settings;
}
function updateSetting(setting, value) {
@ -13,7 +14,6 @@
}
var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"];
var currentVib = settings().vibrate;
var mainmenu = {
"" : { "title" : /*LANG*/"Messages" },
"< Back" : back,
@ -27,13 +27,13 @@
},
/*LANG*/'Repeat': {
value: settings().repeat,
min: 2, max: 10,
format: v => v+"s",
min: 0, max: 10,
format: v => v?v+"s":/*LANG*/"Off",
onchange: v => updateSetting("repeat", v)
},
/*LANG*/'Unread timer': {
value: settings().unreadTimeout,
min: 0, max: 240, step : 10,
min: 0, max: settings().maxUnreadTimeout, step : 10,
format: v => v?v+"s":/*LANG*/"Off",
onchange: v => updateSetting("unreadTimeout", v)
},

View File

@ -1,12 +1,12 @@
WIDGETS["messages"]={area:"tl", width:0, iconwidth:23,
WIDGETS["messages"]={area:"tl", width:0, iconwidth:24,
draw:function() {
Bangle.removeListener('touch', this.touch);
if (!this.width) return;
var c = (Date.now()-this.t)/1000;
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth);
g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y);
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
console.log("dingen ", typeof(settings.repeat), settings.repeat)
if (settings.repeat===undefined) settings.repeat = 4;
if (c<120 && (Date.now()-this.l)>settings.repeat*1000) {
this.l = Date.now();

View File

@ -1 +1,4 @@
0.01: Initial release
0.02: Optional fullscreen mode
0.03: Optional show lock status via color
0.04: Ensure that widgets are always hidden in 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,14 @@ 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.
### Show lock status
If enabled, color changes when unlocked to detect the lock state easily.

View File

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

View File

@ -8,6 +8,19 @@
* Created: February 2022
*/
let settings = {
thickness: 4,
io: 0,
showDate: 1,
fullscreen: false,
showLock: false,
};
let saved_settings = require('Storage').readJSON('neonx.json', 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
const digits = {
0:[[15,15,85,15,85,85,15,85,15,15]],
1:[[85,15,85,85]],
@ -21,6 +34,7 @@ const digits = {
9:[[15,50,15,15,85,15,85,85,15,85]],
};
const colors = {
x: [
["#FF00FF", "#00FFFF"],
@ -31,16 +45,14 @@ const colors = {
["#00FF00", "#00FFFF"]
]
};
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;
let interval = 0;
let showingDate = false;
function drawLine(poly, thickness){
for (let i = 0; i < poly.length; i = i + 2){
if (poly[i + 2] === undefined) {
@ -58,34 +70,35 @@ function drawLine(poly, thickness){
}
}
let settings = require('Storage').readJSON('neonx.json', 1);
if (!settings) {
settings = {
thickness: 4,
io: 0,
showDate: 1
};
}
function drawClock(num){
let tx, ty;
if(settings.fullscreen){
g.clearRect(0,0,screenWidth,screenHeight);
} else {
g.clearRect(0,24,240,240);
}
for (let x = 0; x <= 1; x++) {
for (let y = 0; y <= 1; y++) {
const current = ((y + 1) * 2 + x - 1);
let newScale = scale;
g.setColor(colors[settings.io ? 'io' : 'x'][y][x]);
let xc = settings.showLock && !Bangle.isLocked() ? Math.abs(x-1) : x;
let c = colors[settings.io ? 'io' : 'x'][y][xc];
g.setColor(c);
if (!settings.io) {
tx = (x * 100 + 18) * newScale;
ty = (y * 100 + 32) * newScale;
newScale *= settings.fullscreen ? 1.20 : 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++) {
@ -95,59 +108,81 @@ function drawClock(num){
}
}
function draw(date){
queueDraw();
// Depending on the settings, we clear all widgets or draw those.
if(settings.fullscreen){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else {
Bangle.drawWidgets();
}
// Now lets draw the time/date
let d = new Date();
let l1, l2;
showingDate = date;
if (date) {
setUpdateInt(0);
l1 = ('0' + (new Date()).getDate()).substr(-2);
l2 = ('0' + ((new Date()).getMonth() + 1)).substr(-2);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
setTimeout(_ => {
draw();
setUpdateInt(1);
}, 5000);
} else {
l1 = ('0' + (d.getHours() % (is12hour ? 12 : 24))).substr(-2);
l2 = ('0' + d.getMinutes()).substr(-2);
}
g.clearRect(0,24,240,240);
drawClock([l1, l2]);
}
function setUpdateInt(set){
if (interval) {
clearInterval(interval);
}
if (set) {
interval = setInterval(draw, REFRESH_RATE);
}
/*
* Draw watch face
*/
var drawTimeout;
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
g.clear(1);
Bangle.setUI("clock");
setUpdateInt(1);
draw();
/*
* Event handlers
*/
if (settings.showDate) {
Bangle.on('touch', () => draw(!showingDate));
}
Bangle.on('lcdPower', function(on){
if (on){
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
if (on) {
draw();
setUpdateInt(1);
} else setUpdateInt(0);
}
});
Bangle.on('lock', function(isLocked) {
draw();
});
/*
* Draw first time
*/
g.clear(1);
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();

View File

@ -7,7 +7,9 @@
neonXSettings = {
thickness: 4,
io: 0,
showDate: 1
showDate: 1,
fullscreen: false,
showLock: false,
};
updateSettings();
@ -17,7 +19,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 +50,23 @@
neonXSettings.showDate = v;
updateSettings();
}
}
},
'Fullscreen': {
value: false | neonXSettings.fullscreen,
format: () => (neonXSettings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
neonXSettings.fullscreen = !neonXSettings.fullscreen;
updateSettings();
},
},
'Show lock': {
value: false | neonXSettings.showLock,
format: () => (neonXSettings.showLock ? 'Yes' : 'No'),
onchange: () => {
neonXSettings.showLock = !neonXSettings.showLock;
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();
},
}
})
})

1
apps/pie/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A/ABl3ABQVJg4WLC/QWMC/4X/U7NVqIXTgtVC4MXC6QWBAAQYLCxQAGqByJIoQAKC8IWMJBIuNGBIXvCxxgIC/4XH1QAO0AX/C/4X/C/4X/C6Q"))

56
apps/pie/app.js Normal file
View File

@ -0,0 +1,56 @@
function end(){
clearInterval(m);
clearWatch(w);
gfx.clear();
gfx.setColor(1,0,0);
gfx.setFont("Vector30");
gfx.drawString('Game over!\n Score: '+score+'\nPress BTN1', gfx.getWidth()*0.15,gfx.getHeight()*0.4);
setWatch(function(){init();}, BTN1);
}
function scrollX(){
gfx.clearRect(0,gfx.getHeight()*(1/4),gfx.getWidth(),0);
gfx.scroll(0,gfx.getHeight()/4);
score++;
if(typeof(m) != undefined && score>0){
clearInterval(m);
m = setInterval(scrollY,Math.abs(100/score+15-0.1*score));}
gfx.setColor(1,1,1);
gfx.drawString(score,gfx.getWidth()*(4.2/5),gfx.getHeight()*(0.5/5));
gfx.setColor(Math.random(),Math.random(),Math.random());
gfx.setColor(col[0],col[1],col[2]);
gfx.fillRect(colm[0],colm[1],colm[2],colm[3]);
col = [Math.random(),Math.random(),Math.random()];
gfx.setColor(col[0],col[1],col[2]);
block[0] = gfx.getWidth();
}
function scrollY(){
block[0] -= 2;
block[2] = block[0]+colm[2]-colm[0];
gfx.clearRect(block[2], block[1], gfx.getWidth(), block[3]);
gfx.fillRect(block[0],block[1],block[2],block[3]);
if(block[2]<colm[0])end();
}
function coldet(){
if(block[0]<colm[2]){
gfx.clearRect(block[0],block[1],block[2],block[3]);
if(colm[2]>block[2] && colm[0]<block[2])colm[2]=block[2];
if(colm[0]<block[0] && block[0]<colm[2])colm[0]=block[0];
scrollX();
}else{end();}
}
function init(){
gfx = Graphics.getInstance();
col = [Math.random(),Math.random(),Math.random()];
gfx.clear();
colm = [gfx.getWidth()*(1/5),gfx.getHeight()*(3/4),gfx.getWidth()*(4/5),gfx.getHeight()/2];
block = [gfx.getWidth(),gfx.getHeight()/4,gfx.getWidth(),gfx.getHeight()/2];
score = -3;
gfx.setFont("Vector15");
gfx.fillPoly(colm);
scrollX();
scrollX();
scrollX();
w = setWatch(coldet, BTN1, {repeat:true});
m = setInterval(scrollY,110);
}
init();

BIN
apps/pie/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

14
apps/pie/metadata.json Normal file
View File

@ -0,0 +1,14 @@
{ "id": "pie",
"name": "In this game you need to make highest pie",
"shortName":"Pie maker",
"version":"0.01",
"description": "In this game you will be making pie out of different pieces",
"icon": "app.png",
"type": "app",
"tags": "game",
"supports" : ["BANGLEJS"],
"storage": [
{"name":"pie.app.js","url":"app.js"},
{"name":"pie.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -5,3 +5,4 @@
0.04: Use the exstats module, and make what is displayed configurable
0.05: exstats updated so update 'distance' label is updated, option for 'speed'
0.06: Add option to record a run using the recorder app automatically
0.07: Fix crash if an odd number of active boxes are configured (fix #1473)

View File

@ -62,14 +62,14 @@ for (var i=0;i<statIDs.length;i+=2) {
var sa = exs.stats[statIDs[i+0]];
var sb = exs.stats[statIDs[i+1]];
lc.push({ type:"h", filly:1, c:[
{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol },
{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }
sa?{type:"txt", font:fontHeading, label:sa.title.toUpperCase(), fillx:1, col:headingCol }:{},
sb?{type:"txt", font:fontHeading, label:sb.title.toUpperCase(), fillx:1, col:headingCol }:{}
]}, { type:"h", filly:1, c:[
{type:"txt", font:fontValue, label:sa.getString(), id:sa.id, fillx:1 },
{type:"txt", font:fontValue, label:sb.getString(), id:sb.id, fillx:1 }
sa?{type:"txt", font:fontValue, label:sa.getString(), id:sa.id, fillx:1 }:{},
sb?{type:"txt", font:fontValue, label:sb.getString(), id:sb.id, fillx:1 }:{}
]});
sa.on('changed', e=>layout[e.id].label = e.getString());
sb.on('changed', e=>layout[e.id].label = e.getString());
if (sa) sa.on('changed', e=>layout[e.id].label = e.getString());
if (sb) sb.on('changed', e=>layout[e.id].label = e.getString());
}
// At the bottom put time/GPS state/etc
lc.push({ type:"h", filly:1, c:[

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.06",
"version":"0.07",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -1 +1,2 @@
0.01: first release
0.02: Adjust for touch events outside of screen g dimensions

View File

@ -1,7 +1,7 @@
{
"id": "stopwatch",
"name": "Stopwatch Touch",
"version": "0.01",
"version": "0.02",
"description": "A touch based stop watch for Bangle JS 2",
"icon": "stopwatch.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],

View File

@ -185,17 +185,27 @@ resetBtn.setImage(pause_img);
Bangle.on('touch', function(button, xy) {
var x = xy.x;
var y = xy.y;
// adjust for outside the dimension of the screen
// http://forum.espruino.com/conversations/371867/#comment16406025
if (y > h) y = h;
if (y < 0) y = 0;
if (x > w) x = w;
if (x < 0) x = 0;
// not running, and reset
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return;
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
// paused and hit play
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return;
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
// paused and press reset
if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return;
if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
// must be running
if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return;
if (running && bigPlayPauseBtn.check(x, y)) return;
});
// Stop updates when LCD is off, restart when on

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -0,0 +1,9 @@
# Terminal clock
A clock displayed as a terminal cli.
It can display :
- time
- date
- hrm
- activity
- steps

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwghC/AHsHu93uAX/C+wACsAaTC/4Xiu1mswXRCYNnDQQxTgwX/C8QABuAaTg4X/C7Z3Xa/4Xds1ms4XUCgV2DYIXUsBdTC/4AEg4VCC61wIiYX/C74A/AGIA=="))

138
apps/terminalclock/app.js Normal file
View File

@ -0,0 +1,138 @@
var locale = require("locale");
var fontColor = g.theme.dark ? "#0f0" : "#000";
var startY = 24;
var paddingY = 2;
var font6x8At4Size = 32;
var font6x8At2Size = 18;
var heartRate = 0;
function setFontSize(pos){
if(pos == 1)
g.setFont("6x8", 4);
else
g.setFont("6x8", 2);
}
function clearField(pos){
var yStartPos = startY +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos-1) +
font6x8At2Size * Math.max(0, pos-2);
var yEndPos = startY +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos) +
font6x8At2Size * Math.max(0, pos-1);
g.clearRect(0, yStartPos, 240, yEndPos);
}
function clearWatchIfNeeded(now){
if(now.getMinutes() % 10 == 0)
g.clearRect(0, startY, 240, 240);
}
function drawLine(line, pos){
setFontSize(pos);
var yPos = startY +
paddingY * (pos - 1) +
font6x8At4Size * Math.min(1, pos-1) +
font6x8At2Size * Math.max(0, pos-2);
g.drawString(line, 5, yPos, true);
}
function drawTime(now, pos){
var h = now.getHours();
var m = now.getMinutes();
var time = ">" + (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
drawLine(time, pos);
}
function drawDate(now, pos){
var dow = locale.dow(now, 1);
var date = locale.date(now, 1).substr(0,6) + locale.date(now, 1).substr(-2);
var locale_date = ">" + dow + " " + date;
drawLine(locale_date, pos);
}
function drawInput(now, pos){
clearField(pos);
drawLine(">", pos);
}
function drawStepCount(pos){
var health = Bangle.getHealthStatus("day");
var steps_formated = ">Steps: " + health.steps;
drawLine(steps_formated, pos);
}
function drawHRM(pos){
clearField(pos);
if(heartRate != 0)
drawLine(">HR: " + parseInt(heartRate), pos);
else
drawLine(">HR: unknown", pos);
}
function drawActivity(pos){
clearField(pos);
var health = Bangle.getHealthStatus('last');
var steps_formated = ">Activity: " + parseInt(health.movement/10);
drawLine(steps_formated, pos);
}
function draw(){
var curPos = 1;
g.reset();
g.setFontAlign(-1, -1);
g.setColor(fontColor);
var now = new Date();
clearWatchIfNeeded(now); // mostly to not have issues when changing days
drawTime(now, curPos);
curPos++;
if(settings.showDate){
drawDate(now, curPos);
curPos++;
}
if(settings.showHRM){
drawHRM(curPos);
curPos++;
}
if(settings.showActivity){
drawActivity(curPos);
curPos++;
}
if(settings.showStepCount){
drawStepCount(curPos);
curPos++;
}
drawInput(now, curPos);
}
Bangle.on('HRM',function(hrmInfo) {
if(hrmInfo.confidence >= settings.HRMinConfidence)
heartRate = hrmInfo.bpm;
});
// Clear the screen once, at startup
g.clear();
// load the settings
var settings = Object.assign({
// default values
HRMinConfidence: 50,
showDate: true,
showHRM: true,
showActivity: true,
showStepCount: true,
}, require('Storage').readJSON("terminalclock.json", true) || {});
// draw immediately at first
draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
var secondInterval = setInterval(draw, 10000);

BIN
apps/terminalclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -0,0 +1,24 @@
{
"id": "terminalclock",
"name": "Terminal Clock",
"shortName":"Terminal Clock",
"description": "A terminal cli like clock displaying multiple sensor data",
"version":"0.01",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name": "terminalclock.app.js","url": "app.js"},
{"name": "terminalclock.settings.js","url": "settings.js"},
{"name": "terminalclock.img","url": "app-icon.js","evaluate": true}
],
"data": [
{"name": "terminalclock.json"}
],
"screenshots": [
{"url": "screenshot1.png"},
{"url": "screenshot2.png"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,61 @@
(function(back) {
var FILE = "terminalclock.json";
// Load settings
var settings = Object.assign({
HRMinConfidence: 50,
showDate: true,
showHRM: true,
showActivity: true,
showStepCount: true,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"" : { "title" : "Terminal Clock" },
"< Back" : () => back(),
'HR confidence': {
value: 50|settings.HRMinConfidence, // 0| converts undefined to 0
min: 0, max: 100,
onchange: v => {
settings.HRMinConfidence = v;
writeSettings();
}
},
'Show date': {
value: !!settings.showDate,
format: v => v?"Yes":"No",
onchange: v => {
settings.showDate = v;
writeSettings();
}
},
'Show HRM': {
value: !!settings.showHRM,
format: v => v?"Yes":"No",
onchange: v => {
settings.showHRM = v;
writeSettings();
}
},
'Show Activity': {
value: !!settings.showActivity,
format: v => v?"Yes":"No",
onchange: v => {
settings.showActivity = v;
writeSettings();
}
},
'Show Steps': {
value: !!settings.showStepCount,
format: v => v?"Yes":"No",
onchange: v => {
settings.showStepCount = v;
writeSettings();
}
}
});
})

10
apps/timecal/ChangeLog Normal file
View File

@ -0,0 +1,10 @@
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
0.05: bugfix: default settings

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.05",
"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.settings.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(FILE, 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] = DEFAULTS[k]; //assign missing defaults fixed
var chngdSttngs = Object.assign({}, validSttngs);
var saveExitSettings = () => {
require('Storage').writeJSON(FILE, chngdSttngs);
exit();
};
var cancelExitSettings = () => {
require('Storage').writeJSON(FILE, validSttngs);
exit();
};
var showMainMenu = () => {
E.showMenu({
"": {
"title": "TimeCal "+ /*LANG*/"settings"
},
/*LANG*/"< Save": () => saveExitSettings(),
/*LANG*/"Show date": {
value: chngdSttngs.shwDate,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"locale", /*LANG*/"M", /*LANG*/"m.Y #W"][v],
onchange: v => chngdSttngs.shwDate = v
},
/*LANG*/"Start wday": {
value: chngdSttngs.wdStrt,
min: -1, max: 6,
format: v => v>=0 ? ABR_DAY[v] : /*LANG*/"today",
onchange: v => chngdSttngs.wdStrt = v
},
/*LANG*/"Su color": {
value: chngdSttngs.suClr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => chngdSttngs.suClr = v
},
/*LANG*/"Border": {
value: chngdSttngs.calBrdr,
format: v => v ? /*LANG*/"show" : /*LANG*/"none",
onchange: v => chngdSttngs.calBrdr = v
},
/*LANG*/"Today settings": () => {
showTodayMenu();
},
/*LANG*/"< Cancel": () => cancelExitSettings()
});
};
var showTodayMenu = () => {
E.showMenu({
"": {
"title": /*LANG*/"Today settings"
},
"< Back": () => showMainMenu(),
/*LANG*/"Color": {
value: chngdSttngs.tdyNumClr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => chngdSttngs.tdyNumClr = v
},
/*LANG*/"Marker": {
value: chngdSttngs.tdyMrkr,
min: 0, max: 3,
format: v => [/*LANG*/"none", /*LANG*/"circle", /*LANG*/"rectangle", /*LANG*/"filled"][v],
onchange: v => chngdSttngs.tdyMrkr = v
},
/*LANG*/"Mrk.Color": {
value: chngdSttngs.tdyMrkClr,
min: 0, max: 2,
format: v => [/*LANG*/"red", /*LANG*/"green", /*LANG*/"blue"][v],
onchange: v => chngdSttngs.tdyMrkClr = v
},
/*LANG*/"Mrk.Size": {
value: chngdSttngs.tdyMrkPxl,
min: 1, max: 10,
format: v => v+"px",
onchange: v => chngdSttngs.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;

2
core

@ -1 +1 @@
Subproject commit bd894bfdcee8293c97763de2b4d105a6b6e5415e
Subproject commit a7a80a13fa187a4ff5f89669992babca2d95812c

View File

@ -202,7 +202,6 @@ window.addEventListener('load', (event) => {
}
var selectLang = document.getElementById("settings-lang");
console.log(languages);
languages.forEach(lang => {
selectLang.innerHTML += `<option value="${lang.code}" ${SETTINGS.language==lang.code?"selected":""}>${lang.name} (${lang.code})</option>`;
});

View File

@ -232,7 +232,7 @@ Layout.prototype.render = function (l) {
function render(l) {"ram"
g.reset();
if (l.col) g.setColor(l.col);
if (l.col!==undefined) g.setColor(l.col);
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
cb[l.type](l);
}
@ -264,7 +264,7 @@ Layout.prototype.render = function (l) {
x,y+4
], bg = l.selected?g.theme.bgH:g.theme.bg2;
g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly);
if (l.col) g.setColor(l.col);
if (l.col!==undefined) g.setColor(l.col);
if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad));
else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
}, "img":function(l){

View File

@ -207,7 +207,6 @@ exports.getStats = function(statIDs, options) {
};
}
if (statIDs.includes("caden")) {
needGPS = true;
stats["caden"]={
title : "Cadence",
getValue : function() { return state.stepsPerMin; },