Merge branch 'espruino:master' into master
|
@ -2,3 +2,5 @@ apps/animclk/V29.LBM.js
|
|||
apps/banglerun/rollup.config.js
|
||||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
apps/qrcode/qr-scanner.umd.min.js
|
||||
*.test.js
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
name: Node CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository and submodules
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: install testing dependencies
|
||||
run: npm i
|
||||
- name: test all apps and widgets
|
||||
run: npm run test
|
||||
- name: install typescript dependencies
|
||||
working-directory: ./typescript
|
||||
run: npm ci
|
||||
- name: build types
|
||||
working-directory: ./typescript
|
||||
run: npm run build:types
|
||||
- name: build all TS apps and widgets
|
||||
working-directory: ./typescript
|
||||
run: npm run build
|
|
@ -1,3 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
|
@ -243,6 +243,7 @@ and which gives information about the app for the Launcher.
|
|||
"screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
|
||||
"type":"...", // optional(if app) -
|
||||
// 'app' - an application
|
||||
// 'clock' - a clock - required for clocks to automatically start
|
||||
// 'widget' - a widget
|
||||
// 'launch' - replacement launcher app
|
||||
// 'bootloader' - code that runs at startup only
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
7x7DotsClock
|
||||
|
||||
by Peter Kuppelwieser
|
||||
|
||||
*/
|
||||
|
||||
let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "", ColorMinutes: ""}, 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;
|
||||
|
||||
switch(settings.ColorMinutes) {
|
||||
case "blue":
|
||||
var mColor = [0.3,0.3,1];
|
||||
var sColor = [0,0,1];
|
||||
var sbColor = [1,1,1];
|
||||
break;
|
||||
case "pink":
|
||||
var mColor = [1,0.3,1];
|
||||
var sColor = [1,0,1];
|
||||
var sbColor = [1,1,1];
|
||||
break;
|
||||
case "green":
|
||||
var mColor = [0.3,1,0.3];
|
||||
var sColor = [0,1,0];
|
||||
var sbColor = [1,1,1];
|
||||
break;
|
||||
case "yellow":
|
||||
var mColor = [1,1,0.3];
|
||||
var sColor = [1,1,0];
|
||||
var sbColor = [0,0,0];
|
||||
break;
|
||||
default:
|
||||
var sColor = [0,0,1];
|
||||
var mColor = [0.3,0.3,1];
|
||||
var sbColor = [1,1,1];
|
||||
}
|
||||
const bColor = [0.3,0.3,0.3];
|
||||
|
||||
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,Color,Size) {
|
||||
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
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) {
|
||||
if (Color == "fg") {
|
||||
g.setColor(g.theme.fg);
|
||||
} else {
|
||||
g.setColor(mColor[0],mColor[1],mColor[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,Color,Size) {
|
||||
for (let i = 1; i < 8; i++) {
|
||||
for (let j = 1; j < 8; j++) {
|
||||
if (Font[Num][j-1][i-1] == 1) {
|
||||
if (Color == "fg") {
|
||||
g.setColor(sColor[0],sColor[1],sColor[2]);
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
//g.setColor(0.7,0.7,0.7);
|
||||
}
|
||||
g.fillCircle(x1+(i-1)*(x2-x1)/7,y1+(j-1)*(y2-y1)/7,Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ShowSeconds() {
|
||||
|
||||
g.setColor(sbColor[0],sbColor[1],sbColor[2]);
|
||||
|
||||
g.fillRect((Xe-Xs) / 2 - 14 + Xs -4,
|
||||
(Ye-Ys) / 2 - 7 + Ys -4,
|
||||
(Xe-Xs) / 2 + 14 + Xs +4,
|
||||
(Ye-Ys) / 2 + 7 + Ys +4);
|
||||
|
||||
|
||||
drawSSeg( (Xe-Xs) / 2 - 14 + Xs -1,
|
||||
(Ye-Ys) / 2 - 7 + Ys +1,
|
||||
(Xe-Xs) / 2 + Xs -1,
|
||||
(Ye-Ys) / 2 + 7 + Ys +1,
|
||||
ds,"fg",1);
|
||||
|
||||
drawSSeg( (Xe-Xs) / 2 + Xs +2,
|
||||
(Ye-Ys) / 2 - 7 + Ys +1,
|
||||
(Xe-Xs) / 2 + 14 + Xs +2,
|
||||
(Ye-Ys) / 2 + 7 + Ys +1,
|
||||
es,"fg",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,"fg",4);
|
||||
dho = dh;
|
||||
}
|
||||
|
||||
if (eh != eho) {
|
||||
g.setColor(1,1,1);
|
||||
drawHSeg(Xs+SegW+Dx, Ys, Xs+SegW*2, Ys+SegH,eh,"fg",4);
|
||||
eho = eh;
|
||||
}
|
||||
|
||||
if (dm != dmo) {
|
||||
g.setColor(0.3,0.3,1);
|
||||
drawHSeg(Xs, Ys+SegH+Dy, Xs+SegW, Ys+SegH*2,dm,"",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,"",4);
|
||||
emo = em;
|
||||
}
|
||||
|
||||
if (!Bangle.isLocked()) ShowSeconds();
|
||||
|
||||
}
|
||||
|
||||
|
||||
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 + 25;
|
||||
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.setColor(g.theme.fg);
|
||||
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);
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkEBAkTmEzkAHDmcjmQBBmcTmICCgMAiMAkE/+P/mEQgMQgH/n/zAIP/l/yA4QvXC4kDkEjFgIACkcSmMTkMyBoQHBI4kvI6wXBn8wA4c/mfzl8y+cfEoIaBVa5HBAAMQF4UgIoIBBBgJNBAwQ3BkfygSnJSQIUBkECiBoCL48DmCPFAA6PCX40jX4hYEU4LNBX4JHIkBHCBgJHBianKj8wO4IvHgSnBmJ3CHYqGCABcRcYTXLAA5KCFAJfCC4KnDX4anNgUgiSnMkQQBO5hvCl8yO4pHEd4oyBH4QBBU5TXHkcimUTkLXFL44HEiTbBO4MhBoQHBI4KECR45HGBoIFBU4y/BC4c/mYXGMQJHFiBHLEAIHCf5gAKhWg1UB0IEBjUA0MB0EAjQKCiANCCQOg0cxmcSmWjU4MqmcDmSnDBASkBmejCQIXFmYXEmYXHicyhRLC0AEBAIJFBAIIFCBAYHDF65fXR66vImUCnS8IkeinUBgERgEgcIMBgRHDBgLvCBYMQmcjBYIAHfwL7JiQLBichkcSnUSO4MhI4MxI5MSmMjPgMinCnCkRHGIgJHFiUgkUalUCAgMRkUCkIvIkUSkMC0EiBxAAI0UKkBHCkCPDgA+CI5Z3BmYPBAB53CV4MSEgcSiCnOR4cyR5JQEgBHCC4I0BC4UjC4MCxQXGF4IlBxRHB0UAlUK0BMBkIEBI5ILB0ZHBF4czlTXHI4mjCQIXOH4KnDC4MKgGqgGgAgIBBIoJHJBoQ="))
|
|
@ -0,0 +1,88 @@
|
|||
(function(back) {
|
||||
|
||||
let settings = Object.assign({ swupApp: "",swdownApp: "", swleftApp: "", swrightApp: "",ColorMinutes: ""}, require("Storage").readJSON("7x7dotsclock.json", true) || {});
|
||||
|
||||
|
||||
|
||||
function setSetting(key,value) {
|
||||
print("call " + key + " = " + value);
|
||||
settings[key] = value;
|
||||
|
||||
print("storing settings 7x7dotsclock.json");
|
||||
storage.write('7x7dotsclock.json', settings);
|
||||
}
|
||||
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(key, startvalue, values) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
setSetting(key,values[v]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(name,settings[name], values);
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
const mainMenu = {
|
||||
"": {"title": "7x7 Dots Clock Settings"},
|
||||
"< Back": ()=>load(),
|
||||
"Minutes": stringInSettings("ColorMinutes", ["blue","pink","green","yellow"]),
|
||||
"swipe-up": ()=>showSelAppMenu("swupApp"),
|
||||
"swipe-down": ()=>showSelAppMenu("swdownApp"),
|
||||
"swipe-left": ()=>showSelAppMenu("swleftApp"),
|
||||
"swipe-right": ()=>showSelAppMenu("swrightApp")
|
||||
|
||||
};
|
||||
|
||||
E.showMenu(mainMenu);
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
|
||||
})
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial version for upload
|
||||
0.02: better theme support, configurable colors, small improvements
|
|
@ -0,0 +1,15 @@
|
|||
# 7x7 dots clock
|
||||
|
||||

|
||||
|
||||
* 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
|
||||
* adjustable color for minutes and seconds
|
||||
|
||||

|
||||
|
||||
* when screen is unlocked it shows additional info: bluetooth, battery, new message state, date and seconds
|
||||
* you can configure an app per swipe direction
|
||||
* when swiping the configured apps are launched
|
||||
* button press opens launcher
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,19 @@
|
|||
{ "id": "7x7dotsclock",
|
||||
"name": "7x7 Dots Clock",
|
||||
"shortName":"7x7 Dots Clock",
|
||||
"version":"0.02",
|
||||
"description": "A clock with a big 7x7 dots Font",
|
||||
"icon": "dotsfontclock.png",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"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"}],
|
||||
"screenshots": [{"url":"dotsfontclock.png"},{"url":"dotsfontclock-scr1.png"},{"url":"dotsfontclock-scr2.png"}]
|
||||
}
|
|
@ -13,3 +13,4 @@
|
|||
Widgets now shown on Alarm screen
|
||||
0.13: Alarm widget state now updates when setting/resetting an alarm
|
||||
0.14: Order of 'back' menu item
|
||||
0.15: Fix hour/minute wrapping code for new menu system
|
||||
|
|
|
@ -73,12 +73,12 @@ function editAlarm(alarmIndex) {
|
|||
'': { 'title': /*LANG*/'Alarm' },
|
||||
/*LANG*/'< Back' : showMainMenu,
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
value: hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => hrs=v
|
||||
},
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
value: mins, min : 0, max : 59, wrap : true,
|
||||
onchange: v => mins=v
|
||||
},
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
|
@ -138,12 +138,12 @@ function editTimer(alarmIndex) {
|
|||
const menu = {
|
||||
'': { 'title': /*LANG*/'Timer' },
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
value: hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => hrs=v
|
||||
},
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
value: mins, min : 0, max : 59, wrap : true,
|
||||
onchange: v => mins=v
|
||||
},
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Default Alarm & Timer",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "Set and respond to alarms and timers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,widget",
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
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
|
||||
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
0.01: New App!
|
||||
0.02: Icons, loading screen
|
||||
0.03: Random icon, Shorter "loading" screen
|
||||
0.04: Support for light and dark Themes
|
||||
0.05: Small bugfix
|
||||
0.06: Formatting
|
||||
0.07: Added potato GLaDOS and quote functionality when you tap her
|
||||
0.08: Fixed drawing issues with the quotes and added more
|
|
@ -0,0 +1,11 @@
|
|||
# Description
|
||||
|
||||
This is a simple clock based on the Portal Series.
|
||||
|
||||
# Features
|
||||
|
||||
The button in the center of the screen is interactable and the warning image will change when it is pressed.
|
||||
|
||||
Potato GLaDOS in the bottom left corner is interactable and will display a quote when tapped. (You can add more quotes by editing the `aptsciclkquotes.txt` file seperating each quote with a `^`)
|
||||
|
||||
When the app loads the Apeture Science Logo is displayed.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgNKxAACEaIVDDKWAhAXGwAtODA4HBLR4YFD4QWICIhABGAoMBJRBZHC4wwHOQ4IFAgQwGUQ4YBAg4uMJIwDDGAjRLIgYLHc5gXJIwbKLC4hICb4gZKfAhgETJKHJLwwXRUooWKCImAJogXRMopGMNwkIC4oWLYYqtHC5rFJC5h0GIxwsGFyD8CC4wwOIxBIQFwoeBCxrwEFwYXTFgTXReI4uQC4apPC4xNERqBlGFx4XCeJ4nHD4kIIxY3KPoIxNBwYXEJRInEP44iGOgwXFBYYcDChCHHC4wMBC5BnJEoouMGAYXEJJCCJC4pOEcpYKBFIpJFZRQXGD4gWKXBUICxjdFIhwyJOJMAA="))
|
|
@ -0,0 +1,368 @@
|
|||
const big = g.getWidth()>200;
|
||||
const timeFontSize = big?5:4;
|
||||
const dateFontSize = big?3:2;
|
||||
const gmtFontSize = 2;
|
||||
const font = "6x8";
|
||||
|
||||
const xyCenter = g.getWidth() / 2;
|
||||
const yposTime = xyCenter*0.73;
|
||||
const yposDate = xyCenter*0.48;
|
||||
const yposYear = xyCenter*1.8;
|
||||
|
||||
const buttonTolerance = 20;
|
||||
const buttonX = 88;
|
||||
const buttonY = 104;
|
||||
|
||||
var pause = false; //set to true to pause any sort of drawing (except for quotes)
|
||||
|
||||
function getImg(img){
|
||||
if (img == "w0"){//drink
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOZFIQOD4EABwnwgEDBwf8g/4h4ODwYQBv4OC+AbDAIP+j/HAQIOC4Hwj4RBBwP8o8B/+PBwWOkEP/l/BwP4+JCB44OCj+Ih/+n4OB+PEoP38YOB/0YkUXGgIOB8cBi9f+IOCkEI+XvBwXigFG64OEg0/t4OEuP7BwkHx/PBwWigF8voOC+Uwg/ig4OCkMgv8QsIOB+cfSoOGLIUR/E/4ljBwPxx/B/0kO4UI/0P+J3C/HHVQOISoWEn+D/iPBBwIwC8IOCwcP84IBBwU4TAMHBwfAv+AcARBBgD3CBwX8gDnBBwfwewIODAgIABBwYHDB3oAEBwIHFByyDBABg"))
|
||||
}
|
||||
}
|
||||
else if (img == "w1"){//cube dispenser
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOI/3/+fvBwYEBnwO/By3APgN/O6IeBh4OF8AOcwADCBwX8g4dM/8fBwt774OE+/9Bwt/BxodH3oOcFgyVG8BhCBwX8hRwCBwXA0C6BBwc/w4OE41MBwtEo6VF84sE/1/54OLDo4sHHYxKHLIxoGO44AD/kAABo"))
|
||||
}
|
||||
}
|
||||
else if (img == "w2"){//acid
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOF+IOGngOF8/D8YGD/wdBB4nv4fzAwf4BwOfGQd/4f7/+//+f74OB4PwHIJKDx8P/4BBBwP8BwIBBBwXvh+Hw5ZD+Pwnl/NAcegJOBBwfgj0fBwvhBxcPgYEBBwXw/F+FghIB84OC/BfBOYQOBk/w/0f4f4nkGgFgh0hwED4H4jOBuF8hk/v/Hzlnx/zFgQZBGYLCD4EHaIn8gAOF8EDBwn+dgQOK/8AN4IOD+EABww0BBwqGEBwIWBBwk8CwIODg/gv4OEv4OD+4OBBAIOBRYIFBh+PcAQdC+gOCDoN+h/vBwPP/wOB/wOBwJCBBwP2oa3BLALgBiA7BOwIvB/+DQoV/d4hPBBwQsB/wJB8ZoEAAZoDAAQOPRQIAM"))
|
||||
}
|
||||
}
|
||||
else if (img == "w3"){//turret
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOi+IOGh4OF8AOF/UNBwthx4OE+0YBwtBh4OE6mQBwn7rEfBwl22IOE99gBwn99UzBwUc/+90YsC8HH+++n98n/+g0++2Z+4OB4Fz73T74OCg877d8/YdC+d7u/v3gsBjEvt/+O4X+gvtIgI7CwG934OD8E326kD/0A+yzEwEO74OD/EArYOEgEDv4OD+PAl4OEnkBaInz0EPBwk3iAdE+XwSIYDBj2Oj4OD/fYvIOEvdHz4OD99unIOD/vt44OE3u4Dou3h4OE+3x/IOE70/Bwn78/9Bwl4LAQ7Dx75DBwP4Awb+EBwgAEBz0AABo="))
|
||||
}
|
||||
}
|
||||
else if (img == "w4"){//falling cube
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOC+YOF/0PBwvgv4OE/kFBwvAyIdFnYeBBwYeDDofng4OE8vYDonx7uPBwkf/+/Bwfh+czBwf+g/5z4OD+FevIdEhMDDon/0E3BwgeBJQgeB+5ZFvAEBBwfzgYOEw/XLInwn3BBwf8gH4LYIOCwUHDonwmE4HYkHwKkE8P4XYQOCv7dCYQkBWYsAWYvAiAsE/EDJQn/wF+CwJZDg/gBwgrBXYIOC8D+FNAL+F4eDBwn4nh2BBweHFYJ3EFYQOC/0P/AOECgIOE/E/BwsHBwvACAIODWAQOEJAIOFAgIOEQ4QsEAAOfBwoACBwgACBw8AABo"))
|
||||
}
|
||||
}
|
||||
else if (img == "w5"){//ball
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOiv4OF8YOFAgQOEyYdGBw3zBw0BBwv4j4OB+EAgOD84OE+/ev4dD/3+BwvcugsE/u7t0f4aRC7e2sF8Bwlxg4dEu8YBwYsB/HDHYsMngOB8EDweHDon//PADoYABz0PBwfwnJKE/0OjZZC/kB4Hxz4OCwEYh+wBwXwgeA/+HBwUP8EP/0/BwPj/0DCQIOB/l/4DQBw4OBDIMPUoJKB+H/wY+B44OBj/4CoJKC+P/g7+FBAL+Fj4OFbwIOEI4IOF8YO6JQwAEaIgORgAANA"))
|
||||
}
|
||||
}
|
||||
else if (img == "w6"){//ball recviver
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOR/YOG34Ob/e7Bwu7CwQOhGgQOD34OF/0LBwvfv4dMuPfBwn29oOFtwONDowsHHY3+h7CNj4OF+IOc4A7NDo7gGJQ4ACBwX+//vBwnvBAIOK8EH/kBBwd+v/PSwIOB/fnjiWBBwXesHPLQIOB/2AgEvBwfgh0AFgf8gAuBLKQObgAANA=="))
|
||||
}
|
||||
}
|
||||
else if (img == "w7"){//falling portals
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gOE/v7Bxn5Bw2fHYv7/oOF3/cB118JQQOC4ODJQn8jEfLInBjBoE/0jO4pjD953CwCVF/EH5//+ykCwA8Cp4OB/MDz4DBEQUYjPzaIfn5k/74xC/l44f+BwePz1595ADDYPvv7vDMAN3Bwf4CAIOE4//BYIOB/0On47E8AFCBwcPTwYOCAgPAgE8Bwf8gEDBwOAGIJZDBwX9DofhUYRKDKIIOEAAQOD8EABwgcB+IODnoKB84OD37tCBwUzZ4QODZ4QdDnIFB/YODZwP+v47DJIIBBJQcAAwZyBABoA=="))
|
||||
}
|
||||
}
|
||||
else if (img == "w8"){//flying portals
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YFE/H8BwtvBwvvvgOE/33Bwvf3gdF/YOF/4OF/IOGgA7F8ENBwn8gHcBw/5AoOAg4OCh4sD/vD+AFB45KBBwfwv//BwMJgEIFAXcnvggF4kEBBwPMSIIYBz/8nAEBw5ZD4IhBO48AhpoG953FSo/2Ugv/p4OF/LCGaIyIBB34OH4EAngODbAMDBwnfDoqeCBy7RBBwnh//xBwc9BQPnBwe/AYO/BwUzFYQODGgYOCnIFB/YOD57WBv47Dj//AIJKDgAGDOQIANA"))
|
||||
}
|
||||
}
|
||||
else if (img == "w9"){//cake
|
||||
return {
|
||||
width : 60, height : 60, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AB0//4AE4YGF/gOY/oOG94OF/1/Bwv3FgwKCBwfnFhn8HY0LAQPwvgOB8EP/5uBBwP2gF4j+PBwP+sEEj/x44OB90Ao/8Dodwg8/nkH4ZXBgHnx8ABwPv/k98+ABwZEB+EAJQPj/3+nkAv4OB5+fz0Aj4OB98Ag+Ah/nBwJXB4EDHYSTB/EA/wsCSoJfBwAODNIPgBwgcBHYQOCC4QODn8Ah4ODGgMH+47D8EB/A7KTYMf4A7Eg/wHYgcBHZx3DcAPggbRBFgQcBcAQOB/iUBBwgcBBwgcCd4V/HYL+D/YOBDgIOC8/+DgIOC/+HfwIOD/4cCBwYAEBwQADBz0AABoA="))
|
||||
}
|
||||
}
|
||||
else if (img == "butPress"){
|
||||
return {
|
||||
width : 176, height : 176, bpp : 4,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhPD5e7he7A4fBJn5XNKwJXCLAZX/ABUcKwhXDLAZN/VxhSCK4m8WH5XNVwZXEWARX/ABEcK4sAgBYDXYRP/K5RQC2ACE3e8K/5XPVgYDCK/4AKJIPLVYoEEBoPBIWPd6ICPK46uDAohXzjvd7oCMCAJX/K7cAAAZXFBQkBK/6v/ABPd6ICPK/4AaK4mwKwYEDV+4ARjhKBVQoDD3gMBK2MdAIRXXVYSuDK/5XN5ZRCgEAWQYLBK/4AJK4u7KwZXC4JXxiPd6JXV5hXH3hX1ACscWApXDMQRN/WBpYCK4QICV35XOLARXBA4ZX/ABccKAfMhgFEJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACIA=="))
|
||||
}
|
||||
}
|
||||
else if (img == "butUnpress"){
|
||||
return {
|
||||
width : 176, height : 176, bpp : 4,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("iIA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AFEc5kM5hD/ACXMAAJXB5nBI35WSK4ZY/AB8cK4/MJP5WRK4pY/ABhQEK43BJn5X/AEMcK5fMJv6uPK46w/K/4AgjhPFgEALAxP/K5vAAQhX/K6KsDWApP/AA6uHWA/BIWOIwICPK46qFAohXxjGIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIwICPK/4AaKInAAhCv2ACMcVRC0FK2MYAIRXXVYSuFK/5XO5kAgCuFK/4AJJwvMKw3BK+MRxGBK/4ArjhXMJv6wQK4qu/K/4AjjhXKJf5YRK4hJ/ABxXH4JI/LCRXCK34ASjhXCIf4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACI="))
|
||||
}
|
||||
}
|
||||
|
||||
else if (img == "apetureLaboratories"){
|
||||
return {
|
||||
width : 173, height : 43, bpp : 4,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AA+IAAeAIv5UTK34APhABBKwpeBJX5VLJgJWGAwKv/ABL8EKomABIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWAL4oA/KpALGXQhS/Ko4MIwAOMAHREOKv5ZVVgcAh///4LDAwQAB+AIHCQYHMDQQYBDwQEGDIoaGTx4MCwAiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiL+DzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPgGAEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwEIVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFhGAVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocP///dhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2AKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAv/EghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FgAGCKooA/AAZMBfopWDKv5WLVgxT/AB+AKopW/ACBU/ABwA=="))
|
||||
}
|
||||
}
|
||||
|
||||
else if (img == "apetureLaboratoriesLight"){
|
||||
return {
|
||||
width : 173, height : 43, bpp : 4,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("iIAGxAADwINHAH5ULK34APjABBKwpeBJX5VLJgJWGAwKv/ABL8EKomBBIYA/KpJWHAoRW/KpZQCfwJSCAgRW/KphWFBgZW/KphNCAgSxEKP4ADJAatFKwWBL4oA/KpALGXQhS/Ko4MIwIOMAHREOKv5ZVVgcRiEAgALDAwQABgIIHCQYHMDQQYBDwQEGDIoaGTx4MCwIiCJgYhFBIYIHLw4HFBARjGESJUDehZVFVhJNLRI5VGDIIdEAgwiLgLzDJAJVJB4IMBCoKsHAYpFCDgr2IApYEIBpJFCKpqsCHgQbFIhBVHBhJoGTwyBRKp5mBDoY6GKoYqEXYg7JApKeHQJysEKpRmBDoasHQBAACM4qMGEAr0JAgZjGFYhVPiOBEIpVEAAaAEA4oyEW4qyKFZBoFKoisDJIJWLfIp3IQBJeJfgysMERpVCwMYVpijFBA6ZJdA4JHJYgEKQIx1EVgRVBVhj5IEIyZHHYoUGNw4MCQIwIKAARVDxBVRQo6ZJHZZoGU4yBGBAiBGVgRZBVhYlIfJBoFHZZoHfJRwGBAQrEVISvCKpwrEUZAqFVhzYKB4yKGQIpVDAYJVKEoYFDBIpVIb4ysMIgoPHOgoRFjGBVwKsLAFzQHACBVCVhQA/KpawBCJa76IhRWDVxIODAwTaFAocQgEAdhALGBQYJBCIYQBDYgKEBBAEDVgeIAgKgFBYZVEEYY+CH4YDBBgYFBBYoFEOYhmEDYgFFLIwEDKw2BKwhTFBoSsHIgglFAQo/HKIoDECBBZGc46eEAYa2EKox2Ga5LjLDoq8FKAgVDBAsAEghWGVgRWJKoaOFD4IgCViAWFVjKTGJIZOFKpKOHAQj3JAogCFPApcGKQziGLopKCU4hWFKojsEHYrXFCAJFId45CEDYh4EB4Y1DCYSzFJQT+FiIGCKooA/AAZMBfopWDKv5WLVgxT/AB+BKopW/ACBU/ABsQA="))
|
||||
}
|
||||
}
|
||||
|
||||
else if (img == "potato"){
|
||||
return {
|
||||
width : 54, height : 55, bpp : 4,
|
||||
transparent : 6,
|
||||
buffer : require("heatshrink").decompress(atob("swAEsEGA4oASEQ4ARGgNgDa8QsA3BKStowxTDDalikxTCRKsSNwY2BDSYvDGoI2TsoTDgwEBGx5IBs1VHIgaBGx4qCDQZOBUiUGstQDQ4FBKYwHGDQNWDRA3BCYsABotgJ4dkogABowcGAAUGOwogDDAVCAYScKOooFBooWCAAjqNAoQYHKYQ8ELQZzFsAMCiIeJAAdFMwiCEoIaNoICBoAaMiMUDA0ikMiigaIAAgaGDIIaBkcyoCHEMxqIBmdEkMzmcxDSVBkYXBGoMzmUQDSMSDIURiIbBkDBCDRtBiYwBDIMREAMiDR6qBiI0BkQEBKQKkBUJQAEoCfBC4MVgMSDYMkGwQaBeBMUiC3BGYNN8kSHgM0DQrsGAAMQQAMyCwIaBgK/BmIaKM4IGCiMTDQXd9waCmlENZIaEgESKAMhpxQEDQSiEoJTGiEimaGCqIaBmKABUQwyBUIzRBkIXBDoI8BkAaDGwgaFEIMCeQUSYAKNBmLYDGwJ/DDYlFqAaBGwILBGgLyBUIRSFQghrCgEjDYMiiTdCggaFDYgaFKIKIBmcTkciiA1GNwpsFgI4BmZsBkVEDRAbJiBTCNQMABIIZHfBCPBDQKSCoFEGhA2IKIMQgJ1CoCFHGxVBeASQDgAZJDQ8RQIMyDQkGDZQ1GDILtBAwNGsAaLs1hokECYNCaQMxDIVmDRoOBDQifCBggaNKgMRqUhiovFGxoMEsCADAQQaMsUWJBi+LsMRqtWDSwUBqvFqpVFQ54UCstVqEGsDbBQx4lFgAABotRAgQABT54ADqtRM5jjQAAQ"))
|
||||
}
|
||||
}
|
||||
|
||||
else if (img == "apetureWatch"){
|
||||
return {
|
||||
width : 176, height : 176, bpp : 4,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+/4A/AH4A/AH4A/AD0RgAASgMQCqgrqiJX/K/5X/K/5XR7vQK/8IxBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xah////wK/5XTKwIABK4YAG//x7vRBhAA2jGIAYMfK4fxCRAOCK/5XFKwYABBgTBEh4LC7vQbabxLFYoVPFZMIxADBK4sAK4wLDK/5XSBYhX/K6MPBIQDBK/5XD+BXMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XF+CuEK4yuCK/5XHWAZOCK4cPKwhX/K45MFK4YAGK/5XGLApX/K6X/K5sPK/5XIWAZXJ/5X/K6sPK/5XPAoauDK/5XJ/5XDj5eEVwRX/K5y2FVwRX/K4/wK44GDVwRX/K50fWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLC+BXBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/Ch5X/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAnxFYRdBBohX/K5cAiIrJK/5XEfIhX/K/5Xr+BX/K/5Xu/5X/K/5WKFhRXWl5XB+BX/K6ywFK9XxK4SMFK7JWCK58PK7sP/5XDGoxXr/5Xc//4xBXEHIKyPK54fFK5CPBK7cPxAyBK4oHBK7wKFK5AQBK7UP/GPD4JWCiMfK4IJBK7jOGFgYwEK4XRBg4AQ/CtFAAZXCBQ4AQjBXCCRxXcUoJLDjnMhnMBgTqCK7RzJYYxXC6DbTAAf4UYInB5gABK4PM4KBDdYwrQhBXBBQ5XIAAJXYh6GDKwRXDLASwCK7BxIK8auDjhXH5iwPK5gKIK8iuBKwhXFLAJX/AA0PxCuBKAhXG4KwCK/6uEx/xK5qwNK/KuBjhXL5kRWAJXrbapXEJ4pXH4JXXABRXhh+I+JXPiP//5X/K4WP+McJ4oLBLAxX/K5vAAQhX/ABBDBiJXFVgawFiMf//wK/4gBAAKuHWA/BCYQhIK8uIwACOK5CqFAohXxhGIxACMCAJX/K7YaEK4pKFK/6v/ABOIwACOK/4AMFZRXI4AEIV+wrO///iMcVRC0FiMf//wQaZXZhABCFZ0P//xK4qrCVwpXB/+PbahX15gLBVwpX/K5ERJwvMKw3BiP4K98AxGAFaH//5XPj/4+BXvFaRXCjhXMiMfxBXhGoIAcK4nxWAxXF4MR/GPK4Q4e+UiADcvK4UPWARXMj/4NwayKACUPK8KwDjhXKcQOIKYZX/eIkRLAhXEiKuBx5XEY4QAYDgJXiIAXxiJXH4KuC/4VDK/6wGLAZXCKwKuGK/6wIiMcK4QFBVw5X/WA2IwJSCAAYJBVwpX/WA2IJwJVDj///GPVwpX/LA34K4PxVoYHBKwxX/AAynCK4ZWC+BX/K5ixB/6vEVo5X/IxAABK4asHK/5XLgJXCBxRX/K/5XgLAQNLK/5X/K6r7CACxX/K/5XVfJcBiINLK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K5o6bK/YaZK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K6o6bK/cAABUBiINLK/5X/K/5X/K/5X/K/AMLACBX/K7DMaAAcRADA4eA=="))
|
||||
}
|
||||
}
|
||||
|
||||
else if (img == "apetureWatchLight"){
|
||||
return {
|
||||
width : 176, height : 176, bpp : 4,
|
||||
transparent : 2,
|
||||
buffer : require("heatshrink").decompress(atob("kQA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A+gAA/AH4A/AH4A/AD0R/4AS+MfCqgrqiJX/K/5X/K/5XR7vfK//4xBX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5Xa+EAgEPK/5XTKwIABK4YAGgEB7vRBhAA2jGIAYMQK4cBCRAOCK/5XFKwYABBgTBE+ALC7vfbabxLFYoVPFZP4xADBK4v/K4wLDK/5XSBYhX/K6PwBIQDBK/5XDh5XMBAQQCK/5XDKAJXKVwRWBAoJX/K4j3CUohXDL4YACK/5XFh6uEK4yuCK/5XHWAZOCK4fwKwhX/K45MFK4YAGK/5XGLApX/K6UAK5vwK/5XIWAZXJgBX/K6vwK/5XPAoauDK/5XJgBXDiBeEVwRX/K5y2FVwRX/K48PK44GDVwRX/K50QWAi9DK/5XGUYRQCiKwCAwKuDK/5XGJgZXGMQJWDK/5XGAoKkCK4arEK/5XIVQRQDK/5XQAwZLCh5XBAwYACLwJX/K4auCWApXHCAJX/K5JYFAoi/C+BX/K4ZHCAAZXEAoZnCK/5XLVQRXFBgZX/K4igCLAkBFYRdBBohX/K5f/iIrJK/5XEfIhX/K/5Xrh5X/K/5XugBX/K/5WKFhRXWkBXBh5XvgJXCGgpXcWApXoF4KvD+COGK65WCK5/wK7gtCK4YmCLB5XfgBXbFgIzBK4mILCBXPDwpXIcIJXa+BPBKAJXFLARXdBQpXICAJXah/4x4qDAAMfK4IJBWBpXODgwsDAAcQK4XRBg4APgAwBVogADK4XwgInWjBXCCRxXbiD9BKwcc5kM5gNCRgXwK7JyJYYxXC77bTIwf4UYInB5gABK4PM4MRDQXwDpYrKawMABQ5XIAAJXYh6uDKwRXDLAQRDK64YIK8SuEjhXH5iwPK5gKIK8UP/APBKwhXFLAKwNK/ItBiJQEK43BDgRX/AAXw/GP+JXNGQXwK/5XDEgMcK5fMiIdBK9YAKK5cPK4RPFK4/BDoUPFagAJK8WI+JXPGYRX/IIWP+McJ4sAgBYGK/5XN4ACEK/4AH+AjCK4qsDWAsRDwIWCK/ogBAAKuHWA/BCYQhIK8uIx4COK5CqFAohXx/GIxACMCAJX/K7cAAAZXFBQkBK/6v/ABOIx4COK/4AMFZRXI4AEIV+wrN+AjCjiqIWgpUCCwRXr/ABCFZ0PBoJXFVYSuFK4P/x4VBK/5XI5kAgCuFK/5XIiJOF5hWG4MR/BXv/+Ix5XREgJXOj58BK94rR+AkCjhXMiMfxAUCK70AADpXE+KwGK4vBiP4x5XhgUiADcgEQTyCK5sf/ATDK/5DD+McK5UR/+IK/5XEeYcRLAhXEiKuBx4SDK/6wFiJXH4KuOK/SwELAZXCKwKuOK/ywBiMcK4QFBVwZX/K43/gACBxGBKQQADBIKuBh5X/K43/JAOIJwJVDIYP4x4NCK/5XHfAP4K4PxVoYHBBgRX/K5H/gCnCK4ZWDVxpX9LARABV4ZWQK/xYBgBXD+EAKx5X/AAMBK4RVQK/5ADK4RBSK/5YDIKZX/K/5XNfYQA1K/5X2eJkReKfxj4VTK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XvgAAYh5X8DTJX/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5XVgAAYK/orL+MRIKZX/K/5X/K/5X/K/5X/K/5XmgAAdiIA3"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawStart(){
|
||||
g.clear();
|
||||
g.reset();
|
||||
if (g.theme.dark){apSciLab = getImg("apetureLaboratories");}
|
||||
else {apSciLab = getImg("apetureLaboratoriesLight");}
|
||||
g.drawImage(apSciLab, xyCenter-apSciLab.width/2, xyCenter-apSciLab.height/2);
|
||||
}
|
||||
|
||||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
//warnings
|
||||
var maxWarning = 9;
|
||||
var curWarning = Math.floor(Math.random() * (maxWarning+1));
|
||||
|
||||
function unPause(delay, quote){
|
||||
if (pause){
|
||||
setTimeout(function() {
|
||||
if (quote == undefined || quoteNum == quote){
|
||||
pause = false;
|
||||
draw();
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
var quoteNum;
|
||||
|
||||
function quote(fontsize, width, height, specificQuote){
|
||||
pause = true;
|
||||
var finalString = "";
|
||||
var quotesFile;
|
||||
var finalFontSize;
|
||||
quotesFile = require("Storage").read("aptsciclkquotes.txt", 0, 0); //opens the quotes file
|
||||
//console.log(quotesFile);
|
||||
var quotes = quotesFile.split("^");
|
||||
var numQuotes = quotes.length;//number of quotes
|
||||
var curQuote;
|
||||
|
||||
if (specificQuote == undefined){
|
||||
quoteNum = Math.round(Math.random()*numQuotes)-1;
|
||||
curQuote = quotes[quoteNum]; //quote to be displayed
|
||||
}
|
||||
else{
|
||||
quoteNum = specificQuote;
|
||||
curQuote = quotes[quoteNum];
|
||||
}
|
||||
|
||||
unPause(10000, quoteNum);
|
||||
|
||||
var curWords = curQuote.split(" "); //individual words
|
||||
//console.log(numQuotes);
|
||||
|
||||
var maxChar = width/6/fontsize;
|
||||
var maxLines = height/10/fontsize;
|
||||
var curLines = 0;
|
||||
var curLength = 0;
|
||||
|
||||
|
||||
for (var i = 0; i < curWords.length; i++){
|
||||
//console.log(curLength+curWords[i].length);
|
||||
if (curLength + curWords[i].length <= maxChar){
|
||||
finalString += " "+curWords[i];
|
||||
curLength += curWords[i].length+1;
|
||||
//console.log("next");
|
||||
}
|
||||
else{
|
||||
//console.log("break");
|
||||
curLines++;
|
||||
if (curLines > maxLines){
|
||||
curLength = 0;
|
||||
finalString = "";
|
||||
i = -1;
|
||||
if (fontsize > 1){fontsize--;}
|
||||
maxChar = width/6/fontsize;
|
||||
maxLines = height/10/fontsize;
|
||||
console.log(maxLines);
|
||||
console.log(maxChar);
|
||||
|
||||
}
|
||||
else{
|
||||
curLength = 0;
|
||||
finalString += "\n";
|
||||
i--;
|
||||
}
|
||||
}
|
||||
finalFontSize = fontsize;
|
||||
}
|
||||
|
||||
|
||||
//drawing actual stuff
|
||||
g.setColor(g.getBgColor());
|
||||
g.fillRect(10, 10+28, g.getWidth()-10,g.getWidth()-10);
|
||||
g.reset();
|
||||
g.setFont(font, finalFontSize);
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(finalString, xyCenter, xyCenter+14);
|
||||
//quote length*pixels per character = pixel width
|
||||
//height ~120 width ~160
|
||||
}
|
||||
|
||||
function buttonPressed(){
|
||||
if (curWarning < maxWarning) curWarning += 1;
|
||||
else curWarning = 0;
|
||||
g.reset();
|
||||
buttonImg = getImg("butPress");
|
||||
g.drawImage(buttonImg, 0, 0);
|
||||
|
||||
warningImg = getImg("w"+String(curWarning));
|
||||
g.drawImage(warningImg, 1, g.getWidth()-61);
|
||||
|
||||
setTimeout(buttonUnpressed, 500);
|
||||
}
|
||||
function buttonUnpressed(){
|
||||
if (!pause){
|
||||
buttonImg = getImg("butUnpress");
|
||||
g.drawImage(buttonImg, 0, 0);
|
||||
}
|
||||
else{
|
||||
setTimeout(buttonUnpressed, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function draw() {
|
||||
if (pause){}
|
||||
else{
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
||||
g.reset(); // default draw styles
|
||||
//draw watchface
|
||||
if (g.theme.dark){apSciWatch = getImg("apetureWatch");}
|
||||
else {apSciWatch = getImg("apetureWatchLight");}
|
||||
g.drawImage(apSciWatch, xyCenter-apSciWatch.width/2, xyCenter-apSciWatch.height/2);
|
||||
|
||||
potato = getImg("potato");
|
||||
g.drawImage(potato, 118, 118);
|
||||
|
||||
g.drawImage(warningImg, 1, g.getWidth()-61);//update warning
|
||||
|
||||
// drawString centered
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].substr(0, 5).split(":");
|
||||
var hours = time[0],
|
||||
minutes = time[1];
|
||||
var meridian = "";
|
||||
if (is12Hour) {
|
||||
hours = parseInt(hours,10);
|
||||
meridian = "AM";
|
||||
if (hours == 0) {
|
||||
hours = 12;
|
||||
meridian = "AM";
|
||||
} else if (hours >= 12) {
|
||||
meridian = "PM";
|
||||
if (hours>12) hours -= 12;
|
||||
}
|
||||
hours = (" "+hours).substr(-2);
|
||||
}
|
||||
|
||||
g.setFont(font, timeFontSize);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter+2, yposTime, false);
|
||||
g.setFont(font, gmtFontSize);
|
||||
g.drawString(meridian, xyCenter + 102, yposTime + 10, true);
|
||||
|
||||
// draw Day, name of month, Date
|
||||
var date = [da[0], da[1], da[2]].join(" ");
|
||||
g.setFont(font, dateFontSize);
|
||||
g.drawString(String(date), xyCenter, yposDate, false);
|
||||
|
||||
|
||||
// draw year
|
||||
g.setFont(font, dateFontSize);
|
||||
g.drawString(d.getFullYear(), xyCenter+1, yposYear, true);
|
||||
}
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('touch',(n,e)=>{
|
||||
//button is 88 104
|
||||
if (!pause && buttonX-buttonTolerance < e.x && e.x < buttonX+buttonTolerance && buttonY-buttonTolerance < e.y && e.y < buttonY+buttonTolerance){
|
||||
buttonPressed();
|
||||
}
|
||||
//Potato GLaDOS
|
||||
else if (!pause && 117 < e.x && e.x < 172 && 117 < e.y && e.y < 172){
|
||||
quote(2, 150, 140);
|
||||
}
|
||||
else{
|
||||
unPause(0);
|
||||
}
|
||||
});
|
||||
|
||||
//show Apeture laboritories
|
||||
drawStart();
|
||||
|
||||
setTimeout(function() {
|
||||
// clean app screen
|
||||
g.clear();
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
//update warning image
|
||||
buttonPressed();
|
||||
// draw now
|
||||
draw();
|
||||
}, 500);
|
After Width: | Height: | Size: 714 B |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "aptsciclk",
|
||||
"name": "Apeture Science Clock",
|
||||
"shortName":"AptSci Clock",
|
||||
"version": "0.08",
|
||||
"description": "A clock based on the portal series",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": false,
|
||||
"readme":"README.md",
|
||||
"storage": [
|
||||
{"name":"aptsciclkquotes.txt","url":"quotes.txt"},
|
||||
{"name":"aptsciclk.app.js","url":"app.js"},
|
||||
{"name":"aptsciclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Well here we are again^You euthanized your faithful Companion Cube more quickly than any test subject on record. Congratulations.^So get comfortable while I warm up the neurotoxin emitters^This isn't brave. It's murder. What did I ever do to you?^The difference between us is that I can feel pain.^Who's gonna make the cake when I'm gone? You?^Oh... It's you.^I've been really busy being dead. You know, after you MURDERED ME.^So. How are you holding up? BECAUSE I'M A POTATO.^You really do have brain damage, don't you?^You like revenge, right? Everybody likes revenge. Well, let's go get some.^It's been fun. Don't come back.^And then you showed up. You dangerous, mute lunatic.^Unbelievable. You, [subject name here] must be the pride of [subject hometown here.]^You are not a good person. You know that, right? Good people don't get up here.^Cake, and grief counseling, will be available at the conclusion of the test.^This is your fault. I'm going to kill you. And all the cake is gone. You don't even care, do you?^Momentum, a function of mass and velocity, is conserved between portals. In layman's terms, speedy thing goes in, speedy thing comes out.
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Add "Calculating" placeholder, update JSON save format
|
||||
0.04: Fix tapping at very bottom of list, exit on inactivity
|
||||
0.05: Add support for bulk importing and exporting tokens
|
||||
0.06: Add spaces to codes for improved readability (thanks @BartS23)
|
||||
|
|
|
@ -33,7 +33,7 @@ Keep those copies safe and secure.
|
|||
* Swipe right to exit to the app launcher.
|
||||
* Swipe left on selected counter token to advance the counter to the next value.
|
||||
|
||||

|
||||
   
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const tokenextraheight = 16;
|
||||
var tokendigitsheight = 30;
|
||||
var tokenheight = tokendigitsheight + tokenextraheight;
|
||||
// Hash functions
|
||||
const crypto = require("crypto");
|
||||
const algos = {
|
||||
|
@ -93,6 +94,9 @@ function hotp(d, token, dohmac) {
|
|||
while (ret.length < token.digits) {
|
||||
ret = "0" + ret;
|
||||
}
|
||||
// add a space after every 3rd or 4th digit
|
||||
var re = (token.digits % 3 == 0 || (token.digits % 3 >= token.digits % 4 && token.digits % 4 != 0)) ? "" : ".";
|
||||
ret = ret.replace(new RegExp("(..." + re + ")", "g"), "$1 ").trim();
|
||||
} catch(err) {
|
||||
ret = notsupported;
|
||||
}
|
||||
|
@ -121,15 +125,15 @@ function drawToken(id, r) {
|
|||
lbl = tokens[id].label.substr(0, 10);
|
||||
if (id == state.curtoken) {
|
||||
// current token
|
||||
g.setColor(g.theme.fgH);
|
||||
g.setBgColor(g.theme.bgH);
|
||||
g.setFont("Vector", tokenextraheight);
|
||||
g.setColor(g.theme.fgH)
|
||||
.setBgColor(g.theme.bgH)
|
||||
.setFont("Vector", tokenextraheight)
|
||||
// center just below top line
|
||||
g.setFontAlign(0, -1, 0);
|
||||
.setFontAlign(0, -1, 0);
|
||||
adj = y1;
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setColor(g.theme.fg)
|
||||
.setBgColor(g.theme.bg);
|
||||
sz = tokendigitsheight;
|
||||
do {
|
||||
g.setFont("Vector", sz--);
|
||||
|
@ -138,8 +142,8 @@ function drawToken(id, r) {
|
|||
g.setFontAlign(0, 0, 0);
|
||||
adj = (y1 + y2) / 2;
|
||||
}
|
||||
g.clearRect(x1, y1, x2, y2);
|
||||
g.drawString(lbl, (x1 + x2) / 2, adj, false);
|
||||
g.clearRect(x1, y1, x2, y2)
|
||||
.drawString(lbl, (x1 + x2) / 2, adj, false);
|
||||
if (id == state.curtoken) {
|
||||
if (tokens[id].period > 0) {
|
||||
// timed - draw progress bar
|
||||
|
@ -160,10 +164,10 @@ function drawToken(id, r) {
|
|||
g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + tokenextraheight, false);
|
||||
}
|
||||
// shaded lines top and bottom
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
g.drawLine(x1, y1, x2, y1);
|
||||
g.drawLine(x1, y2, x2, y2);
|
||||
g.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
g.setColor(0.5, 0.5, 0.5)
|
||||
.drawLine(x1, y1, x2, y1)
|
||||
.drawLine(x1, y2, x2, y2)
|
||||
.setClipRect(0, 0, g.getWidth(), g.getHeight());
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
@ -198,15 +202,15 @@ function draw() {
|
|||
}
|
||||
if (tokens.length > 0) {
|
||||
var drewcur = false;
|
||||
var id = Math.floor(state.listy / (tokendigitsheight + tokenextraheight));
|
||||
var y = id * (tokendigitsheight + tokenextraheight) + Bangle.appRect.y - state.listy;
|
||||
var id = Math.floor(state.listy / tokenheight);
|
||||
var y = id * tokenheight + Bangle.appRect.y - state.listy;
|
||||
while (id < tokens.length && y < Bangle.appRect.y2) {
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:(tokendigitsheight + tokenextraheight)});
|
||||
drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenheight});
|
||||
if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) {
|
||||
drewcur = true;
|
||||
}
|
||||
id += 1;
|
||||
y += (tokendigitsheight + tokenextraheight);
|
||||
y += tokenheight;
|
||||
}
|
||||
if (drewcur) {
|
||||
// the current token has been drawn - schedule a redraw
|
||||
|
@ -228,9 +232,9 @@ function draw() {
|
|||
state.nexttime = 0;
|
||||
}
|
||||
} else {
|
||||
g.setFont("Vector", tokendigitsheight);
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
g.setFont("Vector", tokendigitsheight)
|
||||
.setFontAlign(0, 0, 0)
|
||||
.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
|
||||
}
|
||||
if (state.drawtimer) {
|
||||
clearTimeout(state.drawtimer);
|
||||
|
@ -240,18 +244,18 @@ function draw() {
|
|||
|
||||
function onTouch(zone, e) {
|
||||
if (e) {
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / (tokendigitsheight + tokenextraheight));
|
||||
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenheight);
|
||||
if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
|
||||
id = -1;
|
||||
}
|
||||
if (state.curtoken != id) {
|
||||
if (id != -1) {
|
||||
var y = id * (tokendigitsheight + tokenextraheight) - state.listy;
|
||||
var y = id * tokenheight - state.listy;
|
||||
if (y < 0) {
|
||||
state.listy += y;
|
||||
y = 0;
|
||||
}
|
||||
y += (tokendigitsheight + tokenextraheight);
|
||||
y += tokenheight;
|
||||
if (y > Bangle.appRect.h) {
|
||||
state.listy += (y - Bangle.appRect.h);
|
||||
}
|
||||
|
@ -268,7 +272,7 @@ function onTouch(zone, e) {
|
|||
function onDrag(e) {
|
||||
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
|
||||
if (e.dx == 0 && e.dy == 0) return;
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * (tokendigitsheight + tokenextraheight) - Bangle.appRect.h);
|
||||
var newy = Math.min(state.listy - e.dy, tokens.length * tokenheight - Bangle.appRect.h);
|
||||
state.listy = Math.max(0, newy);
|
||||
draw();
|
||||
}
|
||||
|
@ -300,8 +304,12 @@ function bangle1Btn(e) {
|
|||
}
|
||||
state.curtoken = Math.max(state.curtoken, 0);
|
||||
state.curtoken = Math.min(state.curtoken, tokens.length - 1);
|
||||
state.listy = state.curtoken * tokenheight;
|
||||
state.listy -= (Bangle.appRect.h - tokenheight) / 2;
|
||||
state.listy = Math.min(state.listy, tokens.length * tokenheight - Bangle.appRect.h);
|
||||
state.listy = Math.max(state.listy, 0);
|
||||
var fakee = {};
|
||||
fakee.y = state.curtoken * (tokendigitsheight + tokenextraheight) - state.listy + Bangle.appRect.y;
|
||||
fakee.y = state.curtoken * tokenheight - state.listy + Bangle.appRect.y;
|
||||
state.curtoken = -1;
|
||||
state.nextTime = 0;
|
||||
onTouch(0, fakee);
|
||||
|
@ -319,7 +327,7 @@ Bangle.on('drag' , onDrag );
|
|||
Bangle.on('swipe', onSwipe);
|
||||
if (typeof BTN2 == 'number') {
|
||||
setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising" , debounce:50, repeat:true});
|
||||
setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
setWatch(function(){exitApp(); }, BTN2, {edge:"falling", debounce:50});
|
||||
setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising" , debounce:50, repeat:true});
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -56,6 +56,7 @@ function base32clean(val, nows) {
|
|||
var ret = val.replaceAll(/\s+/g, ' ');
|
||||
ret = ret.replaceAll(/0/g, 'O');
|
||||
ret = ret.replaceAll(/1/g, 'I');
|
||||
ret = ret.replaceAll(/8/g, 'B');
|
||||
ret = ret.replaceAll(/[^A-Za-z2-7 ]/g, '');
|
||||
if (nows) {
|
||||
ret = ret.replaceAll(/\s+/g, '');
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"name": "2FA Authenticator",
|
||||
"shortName": "AuthWatch",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version": "0.05",
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
||||
"version": "0.06",
|
||||
"description": "Google Authenticator compatible tool.",
|
||||
"tags": "tool",
|
||||
"interface": "interface.html",
|
||||
|
|
Before Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -1 +0,0 @@
|
|||
node_modules/
|
|
@ -1,13 +0,0 @@
|
|||
0.01: First release
|
||||
0.02: Bugfix time: Reset minutes to 0 when hitting 60
|
||||
0.03: Fix distance >=10 km (fix #529)
|
||||
0.04: Use offscreen buffer for flickerless updates
|
||||
0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging
|
||||
0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above)
|
||||
0.07: Fixed GPS update, added guards against NaN values
|
||||
0.08: Fix issue with GPS coordinates being wrong after the first one
|
||||
0.09: Another GPS fix (log raw coordinates - not filtered ones)
|
||||
0.10: Removed kalman filtering to allow distance log to work
|
||||
Only log data every 5 seconds (not 1 sec)
|
||||
Don't create a file until the first log entry is ready
|
||||
Add labels for buttons
|
|
@ -1,25 +0,0 @@
|
|||
# BangleRun
|
||||
|
||||
An app for running sessions. Displays info and logs your run for later viewing.
|
||||
|
||||
## Compilation
|
||||
|
||||
The app is written in Typescript, and needs to be transpiled in order to be
|
||||
run on the BangleJS. The easiest way to perform this step is by using the
|
||||
ubiquitous [NPM package manager](https://www.npmjs.com/get-npm).
|
||||
|
||||
After having installed NPM for your platform, checkout the `BangleApps` repo,
|
||||
open a terminal, and navigate into the `apps/banglerun` folder. Then issue:
|
||||
|
||||
```
|
||||
npm i
|
||||
```
|
||||
|
||||
to install the project's build tools, and:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
To build the app. The last command will generate the `app.js` file, containing
|
||||
the transpiled code for the BangleJS.
|
|
@ -1 +0,0 @@
|
|||
require("heatshrink").decompress(atob("mEwwIHEuAEDgP8ApMDAqAXBjAGD/E8AgUcgF8CAX/BgIFBn//wAFCv//8PwAoP///5Aon/8AcB+IFB4AFB8P/34FBgfj/8fwAFB4f+g4cBg/H/w/Cg+HKQcPx4FEh4/CAoMfAocOj4/CKYRwELIIFDLII6BAoZSBLIYeCgP+v4FD/k/GAQFBHgcD/ABBIIX4gIFBSYPwAoUPAog/B8AFEwAFDDQQCBQoQFCZYYFigCKEgFwgAA=="))
|
|
@ -1 +0,0 @@
|
|||
!function(){"use strict";var t;!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(t||(t={}));const n={STOP:63488,PAUSE:65504,RUN:2016};function e(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function i(i){var s;g.setFontVector(30),g.setFontAlign(0,-1,0),e((i.distance/1e3).toFixed(2),60,55),e(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,s=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+s).substr(-2)}(i.duration),172,55),e(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(i.speed),60,115),e(i.hr.toFixed(0),172,115),e(i.steps.toFixed(0),60,175),e(i.cadence.toFixed(0),172,175),g.setFont("6x8",2),g.setColor(i.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(s=new Date).getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2),120,220),g.setColor(n[i.status]),g.fillRect(160,216,230,240),g.setColor(0),g.drawString(i.status,200,220),g.setFont("6x8").setFontAlign(0,0,1).setColor(-1),i.status===t.Paused?g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1):i.status===t.Running?g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1):g.drawString("START",236,60,1).drawString(" ",236,180,1)}function s(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),i(t),Bangle.drawWidgets()}function a(n){n.status===t.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Running,i(n)}const r={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:t.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var o;o=r,Bangle.on("GPS",n=>function(n,e){n.lat=e.lat,n.lon=e.lon,n.alt=e.alt,n.vel=e.speed/3.6,n.fix=e.fix,n.dop=e.hdop,n.gpsValid=n.fix>0,function(n){const e=Date.now();let i=(e-n.t)/1e3;if(isFinite(i)||(i=0),n.t=e,n.timeSinceLog+=i,n.status===t.Running&&(n.duration+=i),!n.gpsValid)return;const s=6371008.8+n.alt,a=n.lat*Math.PI/180,r=n.lon*Math.PI/180,o=s*Math.cos(a)*Math.cos(r),g=s*Math.cos(a)*Math.sin(r),d=s*Math.sin(a);if(!n.x)return n.x=o,n.y=g,void(n.z=d);const u=o-n.x,l=g-n.y,c=d-n.z,f=Math.sqrt(u*u+l*l+c*c);n.x=o,n.y=g,n.z=d,n.status===t.Running&&(n.distance+=f,n.speed=n.distance/n.duration||0,n.cadence=60*n.steps/n.duration||0)}(n),i(n),n.gpsValid&&n.status===t.Running&&n.timeSinceLog>5&&(n.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(n))}(o,n)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,s=t.hrError/(t.hrError+i)||0;t.hr+=e*s,t.hrError+=(i-t.hrError)*s}(t,n)),Bangle.setHRMPower(1)}(r),function(n){Bangle.on("step",()=>function(n){n.status===t.Running&&(n.steps+=1)}(n))}(r),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&s(t)}),s(t)}(r),setWatch(()=>a(r),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(n){n.status===t.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Stopped,i(n)}(r),BTN3,{repeat:!0,edge:"falling"})}();
|
Before Width: | Height: | Size: 10 KiB |
|
@ -1,217 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="tracks"></div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
/* TODO: Calculate cadence from step count */
|
||||
var domTracks = document.getElementById("tracks");
|
||||
|
||||
function saveKML(track,title) {
|
||||
var kml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
|
||||
<Document>
|
||||
<Schema id="schema">
|
||||
<gx:SimpleArrayField name="heartrate" type="int">
|
||||
<displayName>Heart Rate</displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="steps" type="int">
|
||||
<displayName>Step Count</displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="distance" type="float">
|
||||
<displayName>Distance</displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="cadence" type="int">
|
||||
<displayName>Cadence</displayName>
|
||||
</gx:SimpleArrayField>
|
||||
</Schema>
|
||||
<Folder>
|
||||
<name>Tracks</name>
|
||||
<Placemark>
|
||||
<name>${title}</name>
|
||||
<gx:Track>
|
||||
${track.map(pt=>` <when>${pt.date.toISOString()}</when>\n`).join("")}
|
||||
${track.map(pt=>` <gx:coord>${pt.lon} ${pt.lat} ${pt.alt}</gx:coord>\n`).join("")}
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
<gx:SimpleArrayData name="heartrate">
|
||||
${track.map(pt=>` <gx:value>${pt.heartrate}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="steps">
|
||||
${track.map(pt=>` <gx:value>${pt.steps}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="distance">
|
||||
${track.map(pt=>` <gx:value>${pt.distance}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
</Document>
|
||||
</kml>`;
|
||||
var a = document.createElement("a"),
|
||||
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
|
||||
var url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = title+".kml";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function saveGPX(track, title) {
|
||||
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<metadata>
|
||||
<time>${track[0].date.toISOString()}</time>
|
||||
</metadata>
|
||||
<trk>
|
||||
<name>${title}</name>
|
||||
<trkseg>`;
|
||||
track.forEach(pt=>{
|
||||
gpx += `
|
||||
<trkpt lat="${pt.lat}" lon="${pt.lon}">
|
||||
<ele>${pt.alt}</ele>
|
||||
<time>${pt.date.toISOString()}</time>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>${pt.heartrate}</gpxtpx:hr>
|
||||
<gpxtpx:distance>${pt.distance}</gpxtpx:distance>
|
||||
${/* <gpxtpx:cad>65</gpxtpx:cad> */""}
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>`;
|
||||
});
|
||||
gpx += `
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>`;
|
||||
var a = document.createElement("a"),
|
||||
file = new Blob([gpx], {type: "application/gpx+xml"});
|
||||
var url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = title+".gpx";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function trackLineToObject(l, hasFileName) {
|
||||
// "timestamp,latitude,longitude,altitude,duration,distance,heartrate,steps\n"
|
||||
var t = l.trim().split(",");
|
||||
var n = hasFileName ? 1 : 0;
|
||||
var o = {
|
||||
invalid : t.length < 8,
|
||||
date : new Date(parseInt(t[n+0])),
|
||||
lat : parseFloat(t[n+1]),
|
||||
lon : parseFloat(t[n+2]),
|
||||
alt : parseFloat(t[n+3]),
|
||||
duration : parseFloat(t[n+4]),
|
||||
distance : parseFloat(t[n+5]),
|
||||
heartrate : parseInt(t[n+6]),
|
||||
steps : parseInt(t[n+7]),
|
||||
};
|
||||
if (hasFileName)
|
||||
o.filename = t[0];
|
||||
return o;
|
||||
}
|
||||
|
||||
function downloadTrack(trackid, callback) {
|
||||
Util.showModal("Downloading Track...");
|
||||
Util.readStorageFile(trackid, data=>{
|
||||
Util.hideModal();
|
||||
var trackLines = data.trim().split("\n");
|
||||
trackLines.shift(); // remove first line, which is column header
|
||||
// should be:
|
||||
// "timestamp,latitude,longitude,altitude,duration,distance,heartrate,steps\n"
|
||||
var track = trackLines.map(l=>trackLineToObject(l,false));
|
||||
callback(track);
|
||||
});
|
||||
}
|
||||
function getTrackList() {
|
||||
Util.showModal("Loading Tracks...");
|
||||
domTracks.innerHTML = "";
|
||||
Puck.eval(`require("Storage").list(/banglerun_.*\\x01/).map(fn=>{fn=fn.slice(0,-1);var f=require("Storage").open(fn,"r");f.readLine();return fn+","+f.readLine()})`,trackLines=>{
|
||||
var html = `<div class="container">
|
||||
<div class="columns">\n`;
|
||||
trackLines.forEach(l => {
|
||||
var track = trackLineToObject(l, true /*has filename*/);
|
||||
html += `
|
||||
<div class="column col-12">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Track ${track.filename}</div>
|
||||
<div class="card-subtitle text-gray">${track.invalid ? "No Data":track.date.toString().substr(0,24)}</div>
|
||||
</div>
|
||||
${track.invalid?``:`<div class="card-image">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="250"
|
||||
frameborder="0" style="border:0"
|
||||
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyBxTcwrrVOh2piz7EmIs1Xn4FsRxJWeVH4&q=${track.lat},${track.lon}&zoom=10" allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="card-body"></div>`}
|
||||
<div class="card-footer">${track.invalid?``:`
|
||||
<button class="btn btn-primary" trackid="${track.filename}" task="downloadkml">Download KML</button>
|
||||
<button class="btn btn-primary" trackid="${track.filename}" task="downloadgpx">Download GPX</button>`}
|
||||
<button class="btn btn-default" trackid="${track.filename}" task="delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
if (trackLines.length==0) {
|
||||
html += `
|
||||
<div class="column col-12">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">No tracks</div>
|
||||
<div class="card-subtitle text-gray">No GPS tracks found</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
html += `
|
||||
</div>
|
||||
</div>`;
|
||||
domTracks.innerHTML = html;
|
||||
Util.hideModal();
|
||||
var buttons = domTracks.querySelectorAll("button");
|
||||
for (var i=0;i<buttons.length;i++) {
|
||||
buttons[i].addEventListener("click",event => {
|
||||
var button = event.currentTarget;
|
||||
var trackid = button.getAttribute("trackid");
|
||||
var task = button.getAttribute("task");
|
||||
if (task=="delete") {
|
||||
Util.showModal("Deleting Track...");
|
||||
Util.eraseStorageFile(trackid,()=>{
|
||||
Util.hideModal();
|
||||
getTrackList();
|
||||
});
|
||||
}
|
||||
if (task=="downloadkml") {
|
||||
downloadTrack(trackid, track => saveKML(track, `Bangle.js Track ${trackid}`));
|
||||
}
|
||||
if (task=="downloadgpx") {
|
||||
downloadTrack(trackid, track => saveGPX(track, `Bangle.js Track ${trackid}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
getTrackList();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"spec_dir": "test",
|
||||
"spec_files": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"id": "banglerun",
|
||||
"name": "BangleRun",
|
||||
"shortName": "BangleRun",
|
||||
"version": "0.10",
|
||||
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
|
||||
"icon": "banglerun.png",
|
||||
"tags": "run,running,fitness,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"interface": "interface.html",
|
||||
"allow_emulator": false,
|
||||
"storage": [
|
||||
{"name":"banglerun.app.js","url":"app.js"},
|
||||
{"name":"banglerun.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"name": "banglerun",
|
||||
"version": "0.5.0",
|
||||
"description": "Bangle.js app for running sessions",
|
||||
"main": "app.js",
|
||||
"types": "app.d.ts",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"test": "ts-node -P tsconfig.spec.json node_modules/jasmine/bin/jasmine --config=jasmine.json"
|
||||
},
|
||||
"author": {
|
||||
"name": "Stefano Baldan",
|
||||
"email": "singintime@gmail.com"
|
||||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^4.1.1",
|
||||
"@types/jasmine": "^3.5.10",
|
||||
"jasmine": "^3.5.0",
|
||||
"rollup": "^2.10.2",
|
||||
"rollup-plugin-terser": "^5.3.0",
|
||||
"terser": "^4.7.0",
|
||||
"ts-node": "^8.10.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.2"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import typescript from '@rollup/plugin-typescript';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
export default {
|
||||
input: './src/app.ts',
|
||||
output: {
|
||||
dir: '.',
|
||||
format: 'iife',
|
||||
name: 'banglerun'
|
||||
},
|
||||
plugins: [
|
||||
typescript(),
|
||||
terser(),
|
||||
]
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
import { draw } from './display';
|
||||
import { initLog } from './log';
|
||||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
function startActivity(state: AppState): void {
|
||||
if (state.status === ActivityStatus.Stopped) {
|
||||
initLog(state);
|
||||
}
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.status = ActivityStatus.Paused;
|
||||
} else {
|
||||
state.status = ActivityStatus.Running;
|
||||
}
|
||||
|
||||
draw(state);
|
||||
}
|
||||
|
||||
function stopActivity(state: AppState): void {
|
||||
if (state.status === ActivityStatus.Paused) {
|
||||
clearActivity(state);
|
||||
}
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.status = ActivityStatus.Paused;
|
||||
} else {
|
||||
state.status = ActivityStatus.Stopped;
|
||||
}
|
||||
|
||||
draw(state);
|
||||
}
|
||||
|
||||
function clearActivity(state: AppState): void {
|
||||
state.duration = 0;
|
||||
state.distance = 0;
|
||||
state.speed = 0;
|
||||
state.steps = 0;
|
||||
state.cadence = 0;
|
||||
}
|
||||
|
||||
export { clearActivity, startActivity, stopActivity };
|
|
@ -1,20 +0,0 @@
|
|||
import { startActivity, stopActivity } from './activity';
|
||||
import { initDisplay } from './display';
|
||||
import { initGps } from './gps';
|
||||
import { initHrm } from './hrm';
|
||||
import { initState } from './state';
|
||||
import { initStep } from './step';
|
||||
|
||||
declare var BTN1: any;
|
||||
declare var BTN3: any;
|
||||
declare var setWatch: any;
|
||||
|
||||
const appState = initState();
|
||||
|
||||
initGps(appState);
|
||||
initHrm(appState);
|
||||
initStep(appState);
|
||||
initDisplay(appState);
|
||||
|
||||
setWatch(() => startActivity(appState), BTN1, { repeat: true, edge: 'falling' });
|
||||
setWatch(() => stopActivity(appState), BTN3, { repeat: true, edge: 'falling' });
|
|
@ -1,123 +0,0 @@
|
|||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
declare var Bangle: any;
|
||||
declare var g: any;
|
||||
|
||||
const STATUS_COLORS = {
|
||||
'STOP': 0xF800,
|
||||
'PAUSE': 0xFFE0,
|
||||
'RUN': 0x07E0,
|
||||
}
|
||||
|
||||
function initDisplay(state: AppState): void {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.on('lcdPower', (on: boolean) => {
|
||||
state.drawing = on;
|
||||
if (on) {
|
||||
drawAll(state);
|
||||
}
|
||||
});
|
||||
drawAll(state);
|
||||
}
|
||||
|
||||
function drawBackground(): void {
|
||||
g.clear();
|
||||
g.setColor(0xC618);
|
||||
g.setFont('6x8', 2);
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString('DIST (KM)', 60, 32);
|
||||
g.drawString('TIME', 172, 32);
|
||||
g.drawString('PACE', 60, 92);
|
||||
g.drawString('HEART', 172, 92);
|
||||
g.drawString('STEPS', 60, 152);
|
||||
g.drawString('CADENCE', 172, 152);
|
||||
}
|
||||
|
||||
function drawValue(value: string, x: number, y: number) {
|
||||
g.setColor(0x0000);
|
||||
g.fillRect(x - 60, y, x + 60, y + 30);
|
||||
g.setColor(0xFFFF);
|
||||
g.drawString(value, x, y);
|
||||
}
|
||||
|
||||
function draw(state: AppState): void {
|
||||
g.setFontVector(30);
|
||||
g.setFontAlign(0, -1, 0);
|
||||
|
||||
drawValue(formatDistance(state.distance), 60, 55);
|
||||
drawValue(formatTime(state.duration), 172, 55);
|
||||
drawValue(formatPace(state.speed), 60, 115);
|
||||
drawValue(state.hr.toFixed(0), 172, 115);
|
||||
drawValue(state.steps.toFixed(0), 60, 175);
|
||||
drawValue(state.cadence.toFixed(0), 172, 175);
|
||||
|
||||
g.setFont('6x8', 2);
|
||||
|
||||
g.setColor(state.gpsValid ? 0x07E0 : 0xF800);
|
||||
g.fillRect(0, 216, 80, 240);
|
||||
g.setColor(0x0000);
|
||||
g.drawString('GPS', 40, 220);
|
||||
|
||||
g.setColor(0xFFFF);
|
||||
g.fillRect(80, 216, 160, 240);
|
||||
g.setColor(0x0000);
|
||||
g.drawString(formatClock(new Date()), 120, 220);
|
||||
|
||||
g.setColor(STATUS_COLORS[state.status]);
|
||||
g.fillRect(160, 216, 230, 240);
|
||||
g.setColor(0x0000);
|
||||
g.drawString(state.status, 200, 220);
|
||||
|
||||
g.setFont("6x8").setFontAlign(0,0,1).setColor(-1);
|
||||
if (state.status === ActivityStatus.Paused) {
|
||||
g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1);
|
||||
} else if (state.status === ActivityStatus.Running) {
|
||||
g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1);
|
||||
} else {
|
||||
g.drawString("START",236,60,1).drawString(" ",236,180,1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawAll(state: AppState) {
|
||||
drawBackground();
|
||||
draw(state);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
function formatClock(date: Date): string {
|
||||
return ('0' + date.getHours()).substr(-2) + ':' + ('0' + date.getMinutes()).substr(-2);
|
||||
}
|
||||
|
||||
function formatDistance(meters: number): string {
|
||||
return (meters / 1000).toFixed(2);
|
||||
}
|
||||
|
||||
function formatPace(speed: number): string {
|
||||
if (speed < 0.1667) {
|
||||
return `__'__"`;
|
||||
}
|
||||
const pace = Math.round(1000 / speed);
|
||||
const min = Math.floor(pace / 60);
|
||||
const sec = pace % 60;
|
||||
return ('0' + min).substr(-2) + `'` + ('0' + sec).substr(-2) + `"`;
|
||||
}
|
||||
|
||||
function formatTime(time: number): string {
|
||||
const seconds = Math.round(time);
|
||||
const hrs = Math.floor(seconds / 3600);
|
||||
const min = Math.floor(seconds / 60) % 60;
|
||||
const sec = seconds % 60;
|
||||
return (hrs ? hrs + ':' : '') + ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2);
|
||||
}
|
||||
|
||||
export {
|
||||
draw,
|
||||
drawAll,
|
||||
drawBackground,
|
||||
drawValue,
|
||||
formatClock,
|
||||
formatDistance,
|
||||
formatPace,
|
||||
formatTime,
|
||||
initDisplay,
|
||||
};
|
|
@ -1,90 +0,0 @@
|
|||
import { draw } from './display';
|
||||
import { updateLog } from './log';
|
||||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
declare var Bangle: any;
|
||||
|
||||
interface GpsEvent {
|
||||
lat: number;
|
||||
lon: number;
|
||||
alt: number;
|
||||
speed: number;
|
||||
hdop: number;
|
||||
fix: number;
|
||||
}
|
||||
|
||||
const EARTH_RADIUS = 6371008.8;
|
||||
|
||||
function initGps(state: AppState): void {
|
||||
Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps));
|
||||
Bangle.setGPSPower(1);
|
||||
}
|
||||
|
||||
function readGps(state: AppState, gps: GpsEvent): void {
|
||||
state.lat = gps.lat;
|
||||
state.lon = gps.lon;
|
||||
state.alt = gps.alt;
|
||||
state.vel = gps.speed / 3.6;
|
||||
state.fix = gps.fix;
|
||||
state.dop = gps.hdop;
|
||||
state.gpsValid = state.fix > 0;
|
||||
|
||||
updateGps(state);
|
||||
draw(state);
|
||||
|
||||
/* Only log GPS data every 5 secs if we
|
||||
have a fix and we're running. */
|
||||
if (state.gpsValid &&
|
||||
state.status === ActivityStatus.Running &&
|
||||
state.timeSinceLog > 5) {
|
||||
state.timeSinceLog = 0;
|
||||
updateLog(state);
|
||||
}
|
||||
}
|
||||
|
||||
function updateGps(state: AppState): void {
|
||||
const t = Date.now();
|
||||
let dt = (t - state.t) / 1000;
|
||||
if (!isFinite(dt)) dt=0;
|
||||
state.t = t;
|
||||
state.timeSinceLog += dt;
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.duration += dt;
|
||||
}
|
||||
|
||||
if (!state.gpsValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const r = EARTH_RADIUS + state.alt;
|
||||
const lat = state.lat * Math.PI / 180;
|
||||
const lon = state.lon * Math.PI / 180;
|
||||
const x = r * Math.cos(lat) * Math.cos(lon);
|
||||
const y = r * Math.cos(lat) * Math.sin(lon);
|
||||
const z = r * Math.sin(lat);
|
||||
|
||||
if (!state.x) {
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.z = z;
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = x - state.x;
|
||||
const dy = y - state.y;
|
||||
const dz = z - state.z;
|
||||
const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.z = z;
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.distance += dpMag;
|
||||
state.speed = (state.distance / state.duration) || 0;
|
||||
state.cadence = (60 * state.steps / state.duration) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
export { initGps, readGps, updateGps };
|
|
@ -1,29 +0,0 @@
|
|||
import { AppState } from './state';
|
||||
|
||||
interface HrmData {
|
||||
bpm: number;
|
||||
confidence: number;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
declare var Bangle: any;
|
||||
|
||||
function initHrm(state: AppState) {
|
||||
Bangle.on('HRM', (hrm: HrmData) => updateHrm(state, hrm));
|
||||
Bangle.setHRMPower(1);
|
||||
}
|
||||
|
||||
function updateHrm(state: AppState, hrm: HrmData) {
|
||||
if (hrm.confidence === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dHr = hrm.bpm - state.hr;
|
||||
const hrError = Math.abs(dHr) + 101 - hrm.confidence;
|
||||
const hrGain = (state.hrError / (state.hrError + hrError)) || 0;
|
||||
|
||||
state.hr += dHr * hrGain;
|
||||
state.hrError += (hrError - state.hrError) * hrGain;
|
||||
}
|
||||
|
||||
export { initHrm, updateHrm };
|
|
@ -1,40 +0,0 @@
|
|||
import { AppState } from './state';
|
||||
|
||||
declare var require: any;
|
||||
|
||||
function initLog(state: AppState): void {
|
||||
const datetime = new Date().toISOString().replace(/[-:]/g, '');
|
||||
const date = datetime.substr(2, 6);
|
||||
const time = datetime.substr(9, 6);
|
||||
const filename = `banglerun_${date}_${time}`;
|
||||
state.file = require('Storage').open(filename, 'w');
|
||||
state.fileWritten = false;
|
||||
}
|
||||
|
||||
function updateLog(state: AppState): void {
|
||||
if (!state.fileWritten) {
|
||||
state.file.write([
|
||||
'timestamp',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'altitude',
|
||||
'duration',
|
||||
'distance',
|
||||
'heartrate',
|
||||
'steps',
|
||||
].join(',') + '\n');
|
||||
state.fileWritten = true;
|
||||
}
|
||||
state.file.write([
|
||||
Date.now().toFixed(0),
|
||||
state.lat.toFixed(6),
|
||||
state.lon.toFixed(6),
|
||||
state.alt.toFixed(2),
|
||||
state.duration.toFixed(0),
|
||||
state.distance.toFixed(2),
|
||||
state.hr.toFixed(0),
|
||||
state.steps.toFixed(0),
|
||||
].join(',') + '\n');
|
||||
}
|
||||
|
||||
export { initLog, updateLog };
|
|
@ -1,85 +0,0 @@
|
|||
enum ActivityStatus {
|
||||
Stopped = 'STOP',
|
||||
Paused = 'PAUSE',
|
||||
Running = 'RUN',
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
// GPS NMEA data
|
||||
fix: number;
|
||||
lat: number;
|
||||
lon: number;
|
||||
alt: number;
|
||||
vel: number;
|
||||
dop: number;
|
||||
gpsValid: boolean;
|
||||
|
||||
// Absolute position data
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
// Last fix time
|
||||
t: number;
|
||||
// Last time we saved log info
|
||||
timeSinceLog : number;
|
||||
|
||||
// HRM data
|
||||
hr: number,
|
||||
hrError: number,
|
||||
|
||||
// Logger data
|
||||
file: File;
|
||||
fileWritten: boolean;
|
||||
|
||||
// Drawing data
|
||||
drawing: boolean;
|
||||
|
||||
// Activity data
|
||||
status: ActivityStatus;
|
||||
duration: number;
|
||||
distance: number;
|
||||
speed: number;
|
||||
steps: number;
|
||||
cadence: number;
|
||||
}
|
||||
|
||||
interface File {
|
||||
read: Function;
|
||||
write: Function;
|
||||
erase: Function;
|
||||
}
|
||||
|
||||
function initState(): AppState {
|
||||
return {
|
||||
fix: NaN,
|
||||
lat: NaN,
|
||||
lon: NaN,
|
||||
alt: NaN,
|
||||
vel: NaN,
|
||||
dop: NaN,
|
||||
gpsValid: false,
|
||||
|
||||
x: NaN,
|
||||
y: NaN,
|
||||
z: NaN,
|
||||
t: NaN,
|
||||
timeSinceLog : 0,
|
||||
|
||||
hr: 60,
|
||||
hrError: 100,
|
||||
|
||||
file: null,
|
||||
fileWritten: false,
|
||||
|
||||
drawing: false,
|
||||
|
||||
status: ActivityStatus.Stopped,
|
||||
duration: 0,
|
||||
distance: 0,
|
||||
speed: 0,
|
||||
steps: 0,
|
||||
cadence: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export { ActivityStatus, AppState, File, initState };
|
|
@ -1,15 +0,0 @@
|
|||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
declare var Bangle: any;
|
||||
|
||||
function initStep(state: AppState) {
|
||||
Bangle.on('step', () => updateStep(state));
|
||||
}
|
||||
|
||||
function updateStep(state: AppState) {
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.steps += 1;
|
||||
}
|
||||
}
|
||||
|
||||
export { initStep, updateStep };
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2015",
|
||||
"noImplicitAny": true,
|
||||
"target": "es2015"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": true,
|
||||
"target": "es2015"
|
||||
},
|
||||
"include": [
|
||||
"test"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"}],
|
||||
|
|
|
@ -46,3 +46,4 @@
|
|||
0.40: Bootloader now rebuilds for new firmware versions
|
||||
0.41: Add Keyboard and Mouse Bluetooth HID option
|
||||
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
|
||||
0.43: Fix Gadgetbridge handling with Programmable:off
|
||||
|
|
|
@ -38,7 +38,7 @@ LoopbackA.setConsole(true);\n`;
|
|||
boot += `
|
||||
Bluetooth.line="";
|
||||
Bluetooth.on('data',function(d) {
|
||||
var l = (Bluetooth.line + d).split("\n");
|
||||
var l = (Bluetooth.line + d).split(/[\\n\\r]/);
|
||||
Bluetooth.line = l.pop();
|
||||
l.forEach(n=>Bluetooth.emit("line",n));
|
||||
});
|
||||
|
@ -196,7 +196,7 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
|
|||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
require('Storage').list(/\.boot\.js/).sort((a,b)=>{
|
||||
require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
var aPriority = a.match(getPriority);
|
||||
var bPriority = b.match(getPriority);
|
||||
if (aPriority && bPriority){
|
||||
|
@ -206,7 +206,7 @@ require('Storage').list(/\.boot\.js/).sort((a,b)=>{
|
|||
} else if (!aPriority && bPriority){
|
||||
return 1;
|
||||
}
|
||||
return a > b;
|
||||
return a==b ? 0 : (a>b ? 1 : -1);
|
||||
}).forEach(bootFile=>{
|
||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.42",
|
||||
"version": "0.43",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -20,3 +20,4 @@
|
|||
0.07: Recorder icon only blue if values actually arive
|
||||
Adds some preset modes and a custom one
|
||||
Restructure the settings menu
|
||||
0.08: Allow scanning for devices in settings
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
}
|
||||
|
||||
function getCache(){
|
||||
return require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||
if (settings.btname && settings.btname == cache.name) return cache;
|
||||
clearCache();
|
||||
return {};
|
||||
}
|
||||
|
||||
function addNotificationHandler(characteristic){
|
||||
|
@ -361,7 +364,13 @@
|
|||
var promise;
|
||||
|
||||
if (!device){
|
||||
promise = NRF.requestDevice({ filters: serviceFilters });
|
||||
var filters = serviceFilters;
|
||||
if (settings.btname){
|
||||
log("Configured device name", settings.btname);
|
||||
filters = [{name: settings.btname}];
|
||||
}
|
||||
log("Requesting device with filters", filters);
|
||||
promise = NRF.requestDevice({ filters: filters });
|
||||
|
||||
if (settings.gracePeriodRequest){
|
||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||
|
@ -488,11 +497,15 @@
|
|||
if (gatt) {
|
||||
if (gatt.connected){
|
||||
log("Disconnect with gatt: ", gatt);
|
||||
try{
|
||||
gatt.disconnect().then(()=>{
|
||||
log("Successful disconnect");
|
||||
}).catch((e)=>{
|
||||
log("Error during disconnect", e);
|
||||
log("Error during disconnect promise", e);
|
||||
});
|
||||
} catch (e){
|
||||
log("Error during disconnect attempt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
var settings;
|
||||
readSettings();
|
||||
|
||||
function buildMainMenu(){
|
||||
var mainmenu = {
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
'< Back': back,
|
||||
|
@ -57,14 +58,32 @@
|
|||
}
|
||||
writeSettings("mode",v);
|
||||
}
|
||||
},
|
||||
'Custom Mode': function() { E.showMenu(submenu_custom); },
|
||||
'Debug': function() { E.showMenu(submenu_debug); }
|
||||
}
|
||||
};
|
||||
|
||||
if (settings.btname){
|
||||
var name = "Clear " + settings.btname;
|
||||
mainmenu[name] = function() {
|
||||
E.showPrompt("Clear current device name?").then((r)=>{
|
||||
if (r) {
|
||||
writeSettings("btname",undefined);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
mainmenu["BLE Scan"] = ()=> createMenuFromScan();
|
||||
mainmenu["Custom Mode"] = function() { E.showMenu(submenu_custom); };
|
||||
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var submenu_debug = {
|
||||
'' : { title: "Debug"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Alert on disconnect': {
|
||||
value: !!settings.warnDisconnect,
|
||||
format: v => settings.warnDisconnect ? "On" : "Off",
|
||||
|
@ -82,9 +101,40 @@
|
|||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||
};
|
||||
|
||||
function createMenuFromScan(){
|
||||
E.showMenu();
|
||||
E.showMessage("Scanning");
|
||||
|
||||
var submenu_scan = {
|
||||
'' : { title: "Scan"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); }
|
||||
};
|
||||
var packets=10;
|
||||
var scanStart=Date.now();
|
||||
NRF.setScan(function(d) {
|
||||
packets--;
|
||||
if (packets<=0 || Date.now() - scanStart > 5000){
|
||||
NRF.setScan();
|
||||
E.showMenu(submenu_scan);
|
||||
} else if (d.name){
|
||||
print("Found device", d);
|
||||
submenu_scan[d.name] = function(){
|
||||
E.showPrompt("Set "+d.name+"?").then((r)=>{
|
||||
if (r) {
|
||||
writeSettings("btname",d.name);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
};
|
||||
}
|
||||
}, { filters: [{services: [ "180d" ]}]});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var submenu_custom = {
|
||||
'' : { title: "Custom mode"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Replace HRM': {
|
||||
value: !!settings.custom_replace,
|
||||
format: v => settings.custom_replace ? "On" : "Off",
|
||||
|
@ -165,7 +215,7 @@
|
|||
|
||||
var submenu = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(mainmenu); },
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Request': {
|
||||
value: settings.gracePeriodRequest,
|
||||
min: 0,
|
||||
|
@ -208,5 +258,5 @@
|
|||
}
|
||||
};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
E.showMenu(buildMainMenu());
|
||||
})
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Write available data on reset or kill
|
||||
0.03: Buzz short on every finished measurement and longer if all are done
|
||||
|
|
|
@ -75,7 +75,6 @@ function write(){
|
|||
data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
||||
data += "\n";
|
||||
file.write(data);
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
|
@ -87,6 +86,11 @@ function onBtHrm(e) {
|
|||
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
|
||||
hrvValues[hrvSlots[currentSlot]] = hrv;
|
||||
currentSlot++;
|
||||
if (currentSlot == hrvSlots.length){
|
||||
Bangle.buzz(500)
|
||||
} else {
|
||||
Bangle.buzz(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrv",
|
||||
"name": "Bluetooth Heart Rate variance calculator",
|
||||
"shortName": "BT HRV",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Calculates HRV from a a BT HRM with interval data",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -20,3 +20,6 @@
|
|||
Color depending on value (green -> red, red -> green) option
|
||||
Good HRM value will not be overwritten so fast anymore
|
||||
0.10: Use roboto font for time, date and day of week and center align them
|
||||
0.11: New color option: foreground color
|
||||
Improve performance, reduce memory usage
|
||||
Small optical adjustments
|
||||
|
|
|
@ -3,23 +3,8 @@ const storage = require("Storage");
|
|||
const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
|
||||
const shoesIcon = atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA");
|
||||
const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
|
||||
const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
|
||||
const temperatureIcon = atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA");
|
||||
|
||||
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
|
||||
const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||
const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||
|
||||
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
||||
// Actual height 39 (40 - 2)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
|
||||
|
@ -67,7 +52,7 @@ const colorGreen = '#008000';
|
|||
const colorBlue = '#0000ff';
|
||||
const colorYellow = '#ffff00';
|
||||
const widgetOffset = showWidgets ? 24 : 0;
|
||||
const dowOffset = circleCount == 3 ? 22 : 24; // dow offset relative to date
|
||||
const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date
|
||||
const h = g.getHeight() - widgetOffset;
|
||||
const w = g.getWidth();
|
||||
const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset;
|
||||
|
@ -103,10 +88,7 @@ const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
|||
const iconOffset = circleCount == 3 ? 6 : 8;
|
||||
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
||||
|
||||
|
||||
function draw() {
|
||||
g.clear(true);
|
||||
if (!showWidgets) {
|
||||
function hideWidgets() {
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
|
@ -118,6 +100,12 @@ function draw() {
|
|||
wd.area = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clear(true);
|
||||
if (!showWidgets) {
|
||||
hideWidgets();
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
@ -129,7 +117,7 @@ function draw() {
|
|||
g.setFontRobotoRegular50NumericOnly();
|
||||
g.setFontAlign(0, -1);
|
||||
g.setColor(colorFg);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 8);
|
||||
g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6);
|
||||
now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
// date & dow
|
||||
|
@ -138,10 +126,19 @@ function draw() {
|
|||
g.drawString(locale.date(new Date()), w / 2, h2);
|
||||
g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset);
|
||||
|
||||
// draw the circles a little bit delayed so we decrease the blocking time
|
||||
setTimeout(function() {
|
||||
drawCircle(1);
|
||||
}, 1);
|
||||
setTimeout(function() {
|
||||
drawCircle(2);
|
||||
}, 1);
|
||||
setTimeout(function() {
|
||||
drawCircle(3);
|
||||
}, 1);
|
||||
setTimeout(function() {
|
||||
if (circleCount >= 4) drawCircle(4);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function drawCircle(index) {
|
||||
|
@ -248,6 +245,9 @@ function getGradientColor(color, percent) {
|
|||
const colorList = [
|
||||
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
|
||||
];
|
||||
if (color == "fg") {
|
||||
color = colorFg;
|
||||
}
|
||||
if (color == "green-red") {
|
||||
const colorIndex = Math.round(colorList.length * percent);
|
||||
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00";
|
||||
|
@ -325,6 +325,8 @@ function drawStepsDistance(w) {
|
|||
function drawHeartRate(w) {
|
||||
if (!w) w = getCircleXPosition("hr");
|
||||
|
||||
const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
const color = getCircleColor("hr");
|
||||
|
@ -349,6 +351,8 @@ function drawBattery(w) {
|
|||
if (!w) w = getCircleXPosition("battery");
|
||||
const battery = E.getBattery();
|
||||
|
||||
const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("battery");
|
||||
|
@ -426,6 +430,10 @@ function drawSunProgress(w) {
|
|||
if (!w) w = getCircleXPosition("sunprogress");
|
||||
const percent = getSunProgress();
|
||||
|
||||
// sunset icons:
|
||||
const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||
const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
const color = getCircleColor("sunprogress");
|
||||
|
@ -559,6 +567,18 @@ function windAsBeaufort(windInKmh) {
|
|||
*/
|
||||
function getWeatherIconByCode(code) {
|
||||
const codeGroup = Math.round(code / 100);
|
||||
|
||||
// weather icons:
|
||||
const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
|
||||
switch (codeGroup) {
|
||||
case 2:
|
||||
return weatherStormy;
|
||||
|
@ -823,7 +843,6 @@ if (isCircleEnabled("hr")) {
|
|||
enableHRMSensor();
|
||||
}
|
||||
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.10",
|
||||
"version":"0.11",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
|
||||
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
|
||||
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"];
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
|
||||
"cyan", "white", "black", "green->red", "red->green", "foreground"];
|
||||
|
||||
const weatherData = ["empty", "humidity", "wind"];
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial upload
|
||||
0.2: Added scrollable calendar and swipe gestures
|
|
@ -0,0 +1,33 @@
|
|||
# Clock & Calendar by Michael
|
||||
|
||||
This is my "Hello World". I first made this watchface almost 10 years ago for my original Pebble and Pebble Time and I missed this so much, that I had to write it for the BangleJS2.
|
||||
I know that it seems redundant because there already **is** a *time&cal*-app, but it didn't fit my style.
|
||||
|
||||
|Screenshot|description|
|
||||
|:--:|:-|
|
||||
||locked: triggers only one minimal update/min|
|
||||
||unlocked: smaller clock, but with seconds|
|
||||
||swipe up for big calendar, (up down to scroll, left/right to exit)|
|
||||
|
||||
|
||||
|
||||
|
||||
## Configurable Features
|
||||
- Number of calendar rows (weeks)
|
||||
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
|
||||
- Clock Mode (24h/12h). Doesn't have an am/pm indicator. It's only there because it was easy.
|
||||
- First day of the week
|
||||
- Red Saturday
|
||||
- Red Sunday
|
||||
- Swipes (to disable all gestures)
|
||||
- Swipes: music (swipe down)
|
||||
- Spipes: messages (swipe right)
|
||||
|
||||
## Auto detects your message/music apps:
|
||||
- swiping down will search your files for an app with the string "music" in its filename and launch it
|
||||
- swiping right will search your files for an app with the string "message" in its filename and launch it.
|
||||
- Configurable apps coming soon.
|
||||
|
||||
## Feedback
|
||||
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
|
||||
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkECqMCkQACiEDkIXQuUnkUBkESiYXPgN/u8jgEx/8vC6E3k9xiH//8/C6BHCPQMSL6EDO4cgaf4A/ACEC+YFDl4FEAAM/+ISHbIIECh4FB+QWEA4PwCQsfC4gVBkYGDgP/mQ4CCQk/iAXEAQTiCgMiDQQSFiATDBgQXCgILBEQkQBwYrEC4sPLQRpCBwoXECgUCC4oSBAggXHNQRfDV4X/JgQXJBIIXFgYuDC5QKBiE/C4f/bwgXJmanGJgoSDiTQBmQMBE4JYBfwJ5BBYMiYQISEB4IAB+KdCAgfwAwTrCn4SDiczAAMwGwMTmR0CmECBgRSBCQwA/AGsBgEQAgYABAwcHu93s4GBqAXEmLrCiYICmICBj4XEgvABIMMqECiIXCgQXCegLYBC4NwF4VcAQNV4EPkEhF4REBgYXCiQvCu4UCAQMFJYRfKgxGBuxfGLgkjFgMCkMBmEjgEigZaBI4XFMYcRC4kBmRhBkMQgI5DF4MFgAXCLARfCFoIvDkZmBhnF4sA5gvDYghfEHIQJDAAhQBIAPwVQMTgQvCNIMhAwJfBR4MMU4JRB+RJBiUQgUDVwMgYwMBgcwX4amBqBQBiTqBgUQh8RmJhCL4IvC4HMR4ZaEAgIBBL4LBDL5EBmI5BkQvBXwIGBmMPMwMvkEFR4VcR4UgU4MSC4UQmIJBn7dBiQNBqoXBPYNQh8Q+MB+MvgEvG4JyBj8A+RkBhlQd4ZHBiBYCL4bBELxEAA=="))
|
|
@ -0,0 +1,292 @@
|
|||
Bangle.loadWidgets();
|
||||
|
||||
var s = Object.assign({
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon
|
||||
REDSUN: true, // Use red color for sunday?
|
||||
REDSAT: true, // Use red color for saturday?
|
||||
DRAGENABLED: true,
|
||||
DRAGMUSIC: true,
|
||||
DRAGMESSAGES: true
|
||||
}, require('Storage').readJSON("clockcal.json", true) || {});
|
||||
|
||||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
const CELL_W = w / 7;
|
||||
const CELL2_W = w / 8;//full calendar
|
||||
const CELL_H = 15;
|
||||
const CAL_Y = h - s.CAL_ROWS * CELL_H;
|
||||
const DEBUG = false;
|
||||
var state = "watch";
|
||||
var monthOffset = 0;
|
||||
|
||||
/*
|
||||
* Calendar features
|
||||
*/
|
||||
function drawFullCalendar(monthOffset) {
|
||||
addMonths = function (_d, _am) {
|
||||
var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
|
||||
while ((m + _am) > 11) { ay++; _am -= 12; }
|
||||
while ((m + _am) < 0) { ay--; _am += 12; }
|
||||
n = new Date(_d.getTime());
|
||||
n.setMonth(m + _am);
|
||||
n.setFullYear(y + ay);
|
||||
return n;
|
||||
};
|
||||
monthOffset = (typeof monthOffset == "undefined") ? 0 : monthOffset;
|
||||
state = "calendar";
|
||||
var start = Date().getTime();
|
||||
const months = ['Jan.', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec.'];
|
||||
const monthclr = ['#0f0', '#f0f', '#00f', '#ff0', '#0ff', '#fff'];
|
||||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
|
||||
d = addMonths(Date(), monthOffset);
|
||||
tdy = Date().getDate() + "." + Date().getMonth();
|
||||
newmonth=false;
|
||||
c_y = 0;
|
||||
g.reset();
|
||||
g.setBgColor(0);
|
||||
g.clear();
|
||||
var prevmonth = addMonths(d, -1)
|
||||
const today = prevmonth.getDate();
|
||||
var rD = new Date(prevmonth.getTime());
|
||||
rD.setDate(rD.getDate() - (today - 1));
|
||||
const dow = (s.FIRSTDAYOFFSET + rD.getDay()) % 7;
|
||||
rD.setDate(rD.getDate() - dow);
|
||||
var rDate = rD.getDate();
|
||||
bottomrightY = c_y - 3;
|
||||
clrsun=s.REDSUN?'#f00':'#fff';
|
||||
clrsat=s.REDSUN?'#f00':'#fff';
|
||||
var fg=[clrsun,'#fff','#fff','#fff','#fff','#fff',clrsat];
|
||||
for (var y = 1; y <= 11; y++) {
|
||||
bottomrightY += CELL_H;
|
||||
bottomrightX = -2;
|
||||
for (var x = 1; x <= 7; x++) {
|
||||
bottomrightX += CELL2_W;
|
||||
rMonth = rD.getMonth();
|
||||
rDate = rD.getDate();
|
||||
if (tdy == rDate + "." + rMonth) {
|
||||
caldrawToday(rDate);
|
||||
} else if (rDate == 1) {
|
||||
caldrawFirst(rDate);
|
||||
} else {
|
||||
caldrawNormal(rDate,fg[rD.getDay()]);
|
||||
}
|
||||
if (newmonth && x == 7) {
|
||||
caldrawMonth(rDate,monthclr[rMonth % 6],months[rMonth],rD);
|
||||
}
|
||||
rD.setDate(rDate + 1);
|
||||
}
|
||||
}
|
||||
delete addMonths;
|
||||
if (DEBUG) console.log("Calendar performance (ms):" + (Date().getTime() - start));
|
||||
}
|
||||
function caldrawMonth(rDate,c,m,rD) {
|
||||
g.setColor(c);
|
||||
g.setFont("Vector", 18);
|
||||
g.setFontAlign(-1, 1, 1);
|
||||
drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
|
||||
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
|
||||
newmonth = false;
|
||||
}
|
||||
function caldrawToday(rDate) {
|
||||
g.setFont("Vector", 16);
|
||||
g.setFontAlign(1, 1);
|
||||
g.setColor('#0f0');
|
||||
g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
|
||||
g.setColor('#000');
|
||||
g.drawString(rDate, bottomrightX, bottomrightY);
|
||||
}
|
||||
function caldrawFirst(rDate) {
|
||||
g.flip();
|
||||
g.setFont("Vector", 16);
|
||||
g.setFontAlign(1, 1);
|
||||
bottomrightY += 3;
|
||||
newmonth = true;
|
||||
g.setColor('#0ff');
|
||||
g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
|
||||
g.setColor('#000');
|
||||
g.drawString(rDate, bottomrightX, bottomrightY);
|
||||
}
|
||||
function caldrawNormal(rDate,c) {
|
||||
g.setFont("Vector", 16);
|
||||
g.setFontAlign(1, 1);
|
||||
g.setColor(c);
|
||||
g.drawString(rDate, bottomrightX, bottomrightY);//100
|
||||
}
|
||||
function drawMinutes() {
|
||||
if (DEBUG) console.log("|-->minutes");
|
||||
var d = new Date();
|
||||
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
|
||||
var minutes = d.getMinutes().toString().padStart(2, '0');
|
||||
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
|
||||
var size = 50;
|
||||
var clock_x = (w - 20) / 2;
|
||||
if (dimSeconds) {
|
||||
size = 65;
|
||||
clock_x = 4 + (w / 2);
|
||||
}
|
||||
g.setBgColor(0);
|
||||
g.setColor(textColor);
|
||||
g.setFont("Vector", size);
|
||||
g.setFontAlign(0, 1);
|
||||
g.drawString(hours + ":" + minutes, clock_x, CAL_Y - 10, 1);
|
||||
var nextminute = (61 - d.getSeconds());
|
||||
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
|
||||
minuteInterval = setTimeout(drawMinutes, nextminute * 1000);
|
||||
}
|
||||
|
||||
function drawSeconds() {
|
||||
if (DEBUG) console.log("|--->seconds");
|
||||
var d = new Date();
|
||||
g.setColor();
|
||||
g.fillRect(w - 31, CAL_Y - 36, w - 3, CAL_Y - 19);
|
||||
g.setBgColor(0);
|
||||
g.setColor('#fff');
|
||||
g.setFont("Vector", 24);
|
||||
g.setFontAlign(1, 1);
|
||||
g.drawString(" " + d.getSeconds().toString().padStart(2, '0'), w, CAL_Y - 13);
|
||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||
if (!dimSeconds) secondInterval = setTimeout(drawSeconds, 1000);
|
||||
}
|
||||
|
||||
function drawWatch() {
|
||||
if (DEBUG) console.log("CALENDAR");
|
||||
monthOffset = 0;
|
||||
state = "watch";
|
||||
var d = new Date();
|
||||
g.reset();
|
||||
g.setBgColor(0);
|
||||
g.clear();
|
||||
drawMinutes();
|
||||
if (!dimSeconds) drawSeconds();
|
||||
const dow = (s.FIRSTDAYOFFSET + d.getDay()) % 7; //MO=0, SU=6
|
||||
const today = d.getDate();
|
||||
var rD = new Date(d.getTime());
|
||||
rD.setDate(rD.getDate() - dow);
|
||||
var rDate = rD.getDate();
|
||||
g.setFontAlign(1, 1);
|
||||
for (var y = 1; y <= s.CAL_ROWS; y++) {
|
||||
for (var x = 1; x <= 7; x++) {
|
||||
bottomrightX = x * CELL_W - 2;
|
||||
bottomrightY = y * CELL_H + CAL_Y;
|
||||
g.setFont("Vector", 16);
|
||||
var fg = ((s.REDSUN && rD.getDay() == 0) || (s.REDSAT && rD.getDay() == 6)) ? '#f00' : '#fff';
|
||||
if (y == 1 && today == rDate) {
|
||||
g.setColor('#0f0');
|
||||
g.fillRect(bottomrightX - CELL_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
|
||||
g.setColor('#000');
|
||||
g.drawString(rDate, bottomrightX, bottomrightY);
|
||||
}
|
||||
else {
|
||||
g.setColor(fg);
|
||||
g.drawString(rDate, bottomrightX, bottomrightY);
|
||||
}
|
||||
rD.setDate(rDate + 1);
|
||||
rDate = rD.getDate();
|
||||
}
|
||||
}
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var nextday = (3600 * 24) - (d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds() + 1);
|
||||
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
|
||||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||
dayInterval = setTimeout(drawWatch, nextday * 1000);
|
||||
}
|
||||
|
||||
function BTevent() {
|
||||
drawMinutes();
|
||||
if (s.BUZZ_ON_BT) {
|
||||
var interval = (NRF.getSecurityStatus().connected) ? 100 : 500;
|
||||
Bangle.buzz(interval);
|
||||
setTimeout(function () { Bangle.buzz(interval); }, interval * 3);
|
||||
}
|
||||
}
|
||||
|
||||
function input(dir) {
|
||||
if (s.DRAGENABLED) {
|
||||
Bangle.buzz(100,1);
|
||||
console.log("swipe:"+dir);
|
||||
switch (dir) {
|
||||
case "r":
|
||||
if (state == "calendar") {
|
||||
drawWatch();
|
||||
} else {
|
||||
if (s.DRAGMUSIC) {
|
||||
l=require("Storage").list(RegExp("music.*app"));
|
||||
if (l.length > 0) {
|
||||
load(l[0]);
|
||||
} else Bangle.buzz(3000,1);//not found
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "l":
|
||||
if (state == "calendar") {
|
||||
drawWatch();
|
||||
}
|
||||
break;
|
||||
case "d":
|
||||
if (state == "calendar") {
|
||||
monthOffset--;
|
||||
drawFullCalendar(monthOffset);
|
||||
} else {
|
||||
if (s.DRAGMESSAGES) {
|
||||
l=require("Storage").list(RegExp("message.*app"));
|
||||
if (l.length > 0) {
|
||||
load(l[0]);
|
||||
} else Bangle.buzz(3000,1);//not found
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "u":
|
||||
if (state == "watch") {
|
||||
state = "calendar";
|
||||
drawFullCalendar(0);
|
||||
} else if (state == "calendar") {
|
||||
monthOffset++;
|
||||
drawFullCalendar(monthOffset);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (state == "calendar") {
|
||||
drawWatch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let drag;
|
||||
Bangle.on("drag", e => {
|
||||
if (s.DRAGENABLED) {
|
||||
if (!drag) {
|
||||
drag = { x: e.x, y: e.y };
|
||||
} else if (!e.b) {
|
||||
const dx = e.x - drag.x, dy = e.y - drag.y;
|
||||
var dir = "t";
|
||||
if (Math.abs(dx) > Math.abs(dy) + 10) {
|
||||
dir = (dx > 0) ? "r" : "l";
|
||||
} else if (Math.abs(dy) > Math.abs(dx) + 10) {
|
||||
dir = (dy > 0) ? "d" : "u";
|
||||
}
|
||||
drag = null;
|
||||
input(dir);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//register events
|
||||
Bangle.on('lock', locked => {
|
||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||
dimSeconds = locked; //dim seconds if lock=on
|
||||
drawWatch();
|
||||
});
|
||||
NRF.on('connect', BTevent);
|
||||
NRF.on('disconnect', BTevent);
|
||||
|
||||
dimSeconds = Bangle.isLocked();
|
||||
drawWatch();
|
||||
Bangle.setUI("clock");
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "clockcal",
|
||||
"name": "Clock & Calendar",
|
||||
"version": "0.2",
|
||||
"description": "Clock with Calendar",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"clockcal.app.js","url":"app.js"},
|
||||
{"name":"clockcal.settings.js","url":"settings.js"},
|
||||
{"name":"clockcal.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"clockcal.json"}]
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 5.7 KiB |
|
@ -0,0 +1,122 @@
|
|||
(function (back) {
|
||||
var FILE = "clockcal.json";
|
||||
|
||||
settings = Object.assign({
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
|
||||
REDSUN: true, // Use red color for sunday?
|
||||
REDSAT: true, // Use red color for saturday?
|
||||
DRAGENABLED: true, //Enable drag gestures (bigger calendar etc)
|
||||
DRAGMUSIC: true, //Enable drag down for music (looks for "music*app")
|
||||
DRAGMESSAGES: true //Enable drag right for messages (looks for "message*app")
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
menu = {
|
||||
"": { "title": "Clock & Calendar" },
|
||||
"< Back": () => back(),
|
||||
'Buzz(dis)conn.?': {
|
||||
value: settings.BUZZ_ON_BT,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.BUZZ_ON_BT = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'#Calendar Rows': {
|
||||
value: settings.CAL_ROWS,
|
||||
min: 0, max: 6,
|
||||
onchange: v => {
|
||||
settings.CAL_ROWS = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Clock mode': {
|
||||
value: settings.MODE24,
|
||||
format: v => v ? "24h" : "12h",
|
||||
onchange: v => {
|
||||
settings.MODE24 = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'First Day': {
|
||||
value: settings.FIRSTDAY,
|
||||
min: 0, max: 6,
|
||||
format: v => ["Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon"][v],
|
||||
onchange: v => {
|
||||
settings.FIRSTDAY = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Red Saturday?': {
|
||||
value: settings.REDSAT,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.REDSAT = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Red Sunday?': {
|
||||
value: settings.REDSUN,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.REDSUN = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Swipes (big cal.)?': {
|
||||
value: settings.DRAGENABLED,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.DRAGENABLED = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Swipes (music)?': {
|
||||
value: settings.DRAGMUSIC,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.DRAGMUSIC = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Swipes (messg)?': {
|
||||
value: settings.DRAGMESSAGES,
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.DRAGMESSAGES = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Load deafauls?': {
|
||||
value: 0,
|
||||
min: 0, max: 1,
|
||||
format: v => ["No", "Yes"][v],
|
||||
onchange: v => {
|
||||
if (v == 1) {
|
||||
settings = {
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect.
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
|
||||
REDSUN: true, // Use red color for sunday?
|
||||
REDSAT: true, // Use red color for saturday?
|
||||
DRAGENABLED: true,
|
||||
DRAGMUSIC: true,
|
||||
DRAGMESSAGES: true
|
||||
};
|
||||
writeSettings();
|
||||
load();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
// Show the menu
|
||||
E.showMenu(menu);
|
||||
});
|
|
@ -4,3 +4,5 @@
|
|||
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.
|
||||
0.26: Time formatted to locale
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
var is12;
|
||||
function getHours(d) {
|
||||
var h = d.getHours();
|
||||
if (is12===undefined) is12 = (require('Storage').readJSON('setting.json',1)||{})["12hour"];
|
||||
if (!is12) return h;
|
||||
return (h%12==0) ? 12 : h%12;
|
||||
}
|
||||
|
||||
exports.drawClock = function(fontIndex) {
|
||||
var digits = [];
|
||||
fontFile=require("Storage").read("contourclock-"+Math.abs(parseInt(fontIndex+0.5))+".json");
|
||||
|
@ -15,8 +23,8 @@ exports.drawClock = function(fontIndex) {
|
|||
var y = g.getHeight()/2-digits[0].height/2;
|
||||
var date = new Date();
|
||||
g.clearRect(0,38,g.getWidth()-1,138);
|
||||
d1=parseInt(date.getHours()/10);
|
||||
d2=parseInt(date.getHours()%10);
|
||||
d1=parseInt(getHours(date)/10);
|
||||
d2=parseInt(getHours(date)%10);
|
||||
d3=10;
|
||||
d4=parseInt(date.getMinutes()/10);
|
||||
d5=parseInt(date.getMinutes()%10);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.24",
|
||||
"version":"0.26",
|
||||
"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"}],
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
|
||||
|
|
|
@ -133,15 +133,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();
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "crowclk",
|
||||
"name": "Crow Clock",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face",
|
||||
"icon": "crow_clock.png",
|
||||
"screenshots": [{"url":"screenshot_crow.png"}],
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Add cadence sensor support
|
||||
0.06: Now read wheel rev as well as cadence sensor
|
||||
Improve connection code
|
||||
0.07: Make Bangle.js 2 compatible
|
||||
|
|
|
@ -11,9 +11,9 @@ Currently the app displays the following data:
|
|||
- total distance traveled
|
||||
- an icon with the battery status of the remote sensor
|
||||
|
||||
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
|
||||
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor.
|
||||
Button 2 switches between the display for cycling speed and cadence.
|
||||
Button 1 (swipe up on Bangle.js 2) resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
|
||||
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 (swipe down on Bangle.js 2) will attempt to reconnect to the sensor.
|
||||
Button 2 (tap on Bangle.js 2) switches between the display for cycling speed and cadence.
|
||||
|
||||
Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app.
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "cscsensor",
|
||||
"name": "Cycling speed sensor",
|
||||
"shortName": "CSCSensor",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
|
||||
"icon": "icons8-cycling-48.png",
|
||||
"tags": "outdoors,exercise,ble,bluetooth",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"cscsensor.app.js","url":"cscsensor.app.js"},
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jshint/2.11.0/jshint.min.js"></script>
|
||||
<p>Type your javascript code here</p>
|
||||
<p><textarea id="custom-js"></textarea></p>
|
||||
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<p>Then click <button id="upload" class="btn btn-primary">Upload</button> <span id="btninfo" style="color:orange"></span></p>
|
||||
<script>
|
||||
const item = "custom.boot.js";
|
||||
const id = "custom-js";
|
||||
const sample = "//Bangle.setOptions({wakeOnBTN2:false});";
|
||||
var localeModule = null;
|
||||
var customBootCode = null;
|
||||
var editor = {};
|
||||
|
||||
if (localStorage.getItem(item) === null) {
|
||||
|
@ -48,13 +48,28 @@
|
|||
gutters: ["CodeMirror-linenumbers", "CodeMirror-lint-markers"],
|
||||
lineNumbers: true
|
||||
});
|
||||
function hasWarnings() {
|
||||
return editor.state.lint.marked.length!=0;
|
||||
}
|
||||
|
||||
editor.on("change", function() {
|
||||
setTimeout(function() {
|
||||
if (hasWarnings()) {
|
||||
document.getElementById("btninfo").innerHTML = "There are warnings in the code to be uploaded";
|
||||
document.getElementById("upload").classList.add("disabled");
|
||||
} else {
|
||||
document.getElementById("btninfo").innerHTML = "";
|
||||
document.getElementById("upload").classList.remove("disabled");
|
||||
}
|
||||
}, 500);
|
||||
})
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
if (!editor.state.lint.marked.length) {
|
||||
localeModule = editor.getValue();
|
||||
localStorage.setItem(item, localeModule);
|
||||
if (!hasWarnings()) {
|
||||
customBootCode = editor.getValue();
|
||||
localStorage.setItem(item, customBootCode);
|
||||
sendCustomizedApp({
|
||||
storage: [{ name: item, content: localeModule }]
|
||||
storage: [{ name: item, content: customBootCode }]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
0.01: first release
|
||||
0.02: added settings menu to change color
|
||||
0.03: fix metadata.json to allow setting as clock
|
||||
0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs
|
||||
0.05: changed text to uppercase, just looks better, removed colons on text
|
|
@ -0,0 +1,32 @@
|
|||
# Daisy 
|
||||
|
||||
*A beautiful digital clock with large ring guage, idle timer and a
|
||||
cyclic information line that includes, day, date, steps, battery,
|
||||
sunrise and sunset times*
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
||||
* Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
|
||||
* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer
|
||||
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate)
|
||||
* The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle
|
||||
* The heart value is displayed in RED if the confidence value is less than 50%
|
||||
* NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about.
|
||||
See [#1248](https://github.com/espruino/BangleApps/issues/1248)
|
||||
* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location
|
||||
* If your Sunrise, Sunset times look odd make sure you have setup your location using
|
||||
[MyLocation](https://banglejs.com/apps/?id=mylocation)
|
||||
* The screen is updated every minute to save battery power
|
||||
* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use
|
||||
|
||||
## Future Development
|
||||
* Use mini icons in the information line rather that text
|
||||
* Add weather icons as per Pastel clock
|
||||
* Add a lock icon to the screen
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
It is worth looking at the real thing though as the screenshot does not do it justice.
|