Merge branch 'master' into development
|
|
@ -4,4 +4,5 @@ apps/schoolCalendar/fullcalendar/main.js
|
|||
apps/authentiwatch/qr_packed.js
|
||||
apps/qrcode/qr-scanner.umd.min.js
|
||||
apps/gipy/pkg/gpconv.js
|
||||
apps/health/chart.min.js
|
||||
*.test.js
|
||||
|
|
|
|||
|
|
@ -58,3 +58,7 @@ body:
|
|||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: apps
|
||||
attributes:
|
||||
label: Installed apps
|
||||
|
|
@ -14,3 +14,4 @@ _site
|
|||
.owncloudsync.log
|
||||
Desktop.ini
|
||||
.sync_*.db*
|
||||
*.swp
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ This is the best way to test...
|
|||
|
||||
**Note:** It's a great idea to get a local copy of the repository on your PC,
|
||||
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
|
||||
that there might be.
|
||||
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
|
||||
|
||||
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
|
||||
|
||||
|
|
@ -282,8 +282,11 @@ and which gives information about the app for the Launcher.
|
|||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require'
|
||||
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' - see provides_modules
|
||||
"dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
|
||||
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||
"provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
|
||||
"default" : true, // set if an app is the default implementer of something (a widget/module/etc)
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
// A 'Read more...' link will be added under the app
|
||||
|
|
|
|||
|
|
@ -1,140 +1,142 @@
|
|||
class TwoK {
|
||||
constructor() {
|
||||
this.b = Array(4).fill().map(() => Array(4).fill(0));
|
||||
this.score = 0;
|
||||
this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"};
|
||||
}
|
||||
drawBRect(x1, y1, x2, y2, th, c, cf, fill) {
|
||||
g.setColor(c);
|
||||
for (i=0; i<th; ++i) g.drawRect(x1+i, y1+i, x2-i, y2-i);
|
||||
if (fill) g.setColor(cf).fillRect(x1+th, y1+th, x2-th, y2-th);
|
||||
}
|
||||
render() {
|
||||
const yo = 20;
|
||||
const xo = yo/2;
|
||||
h = g.getHeight()-yo;
|
||||
w = g.getWidth()-yo;
|
||||
bh = Math.floor(h/4);
|
||||
bw = Math.floor(w/4);
|
||||
g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0);
|
||||
g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
|
||||
this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false);
|
||||
for (y=0; y<4; ++y)
|
||||
for (x=0; x<4; ++x) {
|
||||
b = this.b[y][x];
|
||||
this.drawBRect(xo+x*bw, yo+y*bh-1, xo+(x+1)*bh-1, yo+(y+1)*bh-2, 4, "#a88", this.cmap[b], true);
|
||||
if (b > 4) g.setColor(1, 1, 1);
|
||||
else g.setColor(0, 0, 0);
|
||||
g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7));
|
||||
if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh);
|
||||
}
|
||||
}
|
||||
shift(d) { // +/-1: shift x, +/- 2: shift y
|
||||
var crc = E.CRC32(this.b.toString());
|
||||
if (d==-1) { // shift x left
|
||||
for (y=0; y<4; ++y) {
|
||||
for (x=2; x>=0; x--)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1];
|
||||
this.b[y][3] = 0;
|
||||
}
|
||||
for (x=0; x<3; ++x)
|
||||
if (this.b[y][x]==this.b[y][x+1]) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y][x+1];
|
||||
for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1];
|
||||
{ // wrap app in scope to prevent minifier from removing the class definition completely
|
||||
class TwoK {
|
||||
constructor() {
|
||||
this.b = Array(4).fill().map(() => Array(4).fill(0));
|
||||
this.score = 0;
|
||||
this.cmap = {0: "#caa", 2:"#ccc", 4: "#bcc", 8: "#ba6", 16: "#e61", 32: "#d20", 64: "#d00", 128: "#da0", 256: "#ec0", 512: "#dd0"};
|
||||
}
|
||||
drawBRect(x1, y1, x2, y2, th, c, cf, fill) {
|
||||
g.setColor(c);
|
||||
for (i=0; i<th; ++i) g.drawRect(x1+i, y1+i, x2-i, y2-i);
|
||||
if (fill) g.setColor(cf).fillRect(x1+th, y1+th, x2-th, y2-th);
|
||||
}
|
||||
render() {
|
||||
const yo = 20;
|
||||
const xo = yo/2;
|
||||
h = g.getHeight()-yo;
|
||||
w = g.getWidth()-yo;
|
||||
bh = Math.floor(h/4);
|
||||
bw = Math.floor(w/4);
|
||||
g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0);
|
||||
g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
|
||||
this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false);
|
||||
for (y=0; y<4; ++y)
|
||||
for (x=0; x<4; ++x) {
|
||||
b = this.b[y][x];
|
||||
this.drawBRect(xo+x*bw, yo+y*bh-1, xo+(x+1)*bh-1, yo+(y+1)*bh-2, 4, "#a88", this.cmap[b], true);
|
||||
if (b > 4) g.setColor(1, 1, 1);
|
||||
else g.setColor(0, 0, 0);
|
||||
g.setFont("Vector", bh*(b>8 ? (b>64 ? (b>512 ? 0.32 : 0.4) : 0.6) : 0.7));
|
||||
if (b>0) g.drawString(b.toString(), xo+(x+0.5)*bw+1, yo+(y+0.5)*bh);
|
||||
}
|
||||
}
|
||||
shift(d) { // +/-1: shift x, +/- 2: shift y
|
||||
var crc = E.CRC32(this.b.toString());
|
||||
if (d==-1) { // shift x left
|
||||
for (y=0; y<4; ++y) {
|
||||
for (x=2; x>=0; x--)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=x; i<3; i++) this.b[y][i] = this.b[y][i+1];
|
||||
this.b[y][3] = 0;
|
||||
}
|
||||
}
|
||||
for (x=0; x<3; ++x)
|
||||
if (this.b[y][x]==this.b[y][x+1]) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y][x+1];
|
||||
for (j=x+1; j<3; ++j) this.b[y][j] = this.b[y][j+1];
|
||||
this.b[y][3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (d==1) { // shift x right
|
||||
for (y=0; y<4; ++y) {
|
||||
for (x=1; x<4; x++)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1];
|
||||
this.b[y][0] = 0;
|
||||
}
|
||||
for (x=3; x>0; --x)
|
||||
if (this.b[y][x]==this.b[y][x-1]) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y][x-1] ;
|
||||
for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1];
|
||||
this.b[y][0] = 0;
|
||||
}
|
||||
else if (d==1) { // shift x right
|
||||
for (y=0; y<4; ++y) {
|
||||
for (x=1; x<4; x++)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=x; i>0; i--) this.b[y][i] = this.b[y][i-1];
|
||||
this.b[y][0] = 0;
|
||||
}
|
||||
for (x=3; x>0; --x)
|
||||
if (this.b[y][x]==this.b[y][x-1]) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y][x-1] ;
|
||||
for (j=x-1; j>0; j--) this.b[y][j] = this.b[y][j-1];
|
||||
this.b[y][0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (d==-2) { // shift y down
|
||||
for (x=0; x<4; ++x) {
|
||||
for (y=1; y<4; y++)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x];
|
||||
this.b[0][x] = 0;
|
||||
}
|
||||
for (y=3; y>0; y--)
|
||||
if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y-1][x];
|
||||
for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x];
|
||||
this.b[0][x] = 0;
|
||||
}
|
||||
else if (d==-2) { // shift y down
|
||||
for (x=0; x<4; ++x) {
|
||||
for (y=1; y<4; y++)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=y; i>0; i--) this.b[i][x] = this.b[i-1][x];
|
||||
this.b[0][x] = 0;
|
||||
}
|
||||
for (y=3; y>0; y--)
|
||||
if (this.b[y][x]==this.b[y-1][x] || this.b[y][x]==0) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y-1][x];
|
||||
for (j=y-1; j>0; j--) this.b[j][x] = this.b[j-1][x];
|
||||
this.b[0][x] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (d==2) { // shift y up
|
||||
for (x=0; x<4; ++x) {
|
||||
for (y=2; y>=0; y--)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x];
|
||||
this.b[3][x] = 0;
|
||||
}
|
||||
for (y=0; y<3; ++y)
|
||||
if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y+1][x];
|
||||
for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x];
|
||||
this.b[3][x] = 0;
|
||||
}
|
||||
else if (d==2) { // shift y up
|
||||
for (x=0; x<4; ++x) {
|
||||
for (y=2; y>=0; y--)
|
||||
if (this.b[y][x]==0) {
|
||||
for (i=y; i<3; i++) this.b[i][x] = this.b[i+1][x];
|
||||
this.b[3][x] = 0;
|
||||
}
|
||||
for (y=0; y<3; ++y)
|
||||
if (this.b[y][x]==this.b[y+1][x] || this.b[y][x]==0) {
|
||||
this.score += 2*this.b[y][x];
|
||||
this.b[y][x] += this.b[y+1][x];
|
||||
for (j=y+1; j<3; ++j) this.b[j][x] = this.b[j+1][x];
|
||||
this.b[3][x] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (E.CRC32(this.b.toString())!=crc);
|
||||
}
|
||||
addDigit() {
|
||||
var d = Math.random()>0.9 ? 4 : 2;
|
||||
var id = Math.floor(Math.random()*16);
|
||||
while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16);
|
||||
this.b[Math.floor(id/4)][id%4] = d;
|
||||
}
|
||||
return (E.CRC32(this.b.toString())!=crc);
|
||||
}
|
||||
addDigit() {
|
||||
var d = Math.random()>0.9 ? 4 : 2;
|
||||
var id = Math.floor(Math.random()*16);
|
||||
while (this.b[Math.floor(id/4)][id%4] > 0) id = Math.floor(Math.random()*16);
|
||||
this.b[Math.floor(id/4)][id%4] = d;
|
||||
}
|
||||
}
|
||||
|
||||
function dragHandler(e) {
|
||||
if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) {
|
||||
var res = false;
|
||||
if (Math.abs(e.dx)>Math.abs(e.dy)) {
|
||||
if (e.dx>0) res = twok.shift(1);
|
||||
if (e.dx<0) res = twok.shift(-1);
|
||||
function dragHandler(e) {
|
||||
if (e.b && (Math.abs(e.dx)>7 || Math.abs(e.dy)>7)) {
|
||||
var res = false;
|
||||
if (Math.abs(e.dx)>Math.abs(e.dy)) {
|
||||
if (e.dx>0) res = twok.shift(1);
|
||||
if (e.dx<0) res = twok.shift(-1);
|
||||
}
|
||||
else {
|
||||
if (e.dy>0) res = twok.shift(-2);
|
||||
if (e.dy<0) res = twok.shift(2);
|
||||
}
|
||||
if (res) twok.addDigit();
|
||||
twok.render();
|
||||
}
|
||||
else {
|
||||
if (e.dy>0) res = twok.shift(-2);
|
||||
if (e.dy<0) res = twok.shift(2);
|
||||
}
|
||||
if (res) twok.addDigit();
|
||||
twok.render();
|
||||
}
|
||||
}
|
||||
|
||||
function swipeHandler() {
|
||||
|
||||
}
|
||||
function swipeHandler() {
|
||||
|
||||
}
|
||||
|
||||
function buttonHandler() {
|
||||
|
||||
}
|
||||
function buttonHandler() {
|
||||
|
||||
}
|
||||
|
||||
var twok = new TwoK();
|
||||
twok.addDigit(); twok.addDigit();
|
||||
twok.render();
|
||||
if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler);
|
||||
if (process.env.HWVERSION==1) {
|
||||
Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); });
|
||||
setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true});
|
||||
setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true});
|
||||
}
|
||||
var twok = new TwoK();
|
||||
twok.addDigit(); twok.addDigit();
|
||||
twok.render();
|
||||
if (process.env.HWVERSION==2) Bangle.on("drag", dragHandler);
|
||||
if (process.env.HWVERSION==1) {
|
||||
Bangle.on("swipe", (e) => { res = twok.shift(e); if (res) twok.addDigit(); twok.render(); });
|
||||
setWatch(() => { res = twok.shift(2); if (res) twok.addDigit(); twok.render(); }, BTN1, {repeat: true});
|
||||
setWatch(() => { res = twok.shift(-2); if (res) twok.addDigit(); twok.render(); }, BTN3, {repeat: true});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New app!
|
||||
0.02: Better support for watch themes
|
||||
0.03: Workaround minifier bug
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "2047pp",
|
||||
"shortName":"2047pp",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Bangle version of a tile shifting game",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Fullscreen settings.
|
||||
0.03: Tell clock widgets to hide.
|
||||
0.04: Use widget_utils.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const SETTINGS_FILE = "90sclk.setting.json";
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage');
|
||||
const widget_utils = require('widget_utils');
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -109,7 +110,7 @@ function draw() {
|
|||
|
||||
// Draw widgets if not fullscreen
|
||||
if(settings.fullscreen){
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
widget_utils.hide();
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "90sclk",
|
||||
"name": "90s Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A 90s style watch-face",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
0.01: Beta version for Bangle 2 (2021/11/28)
|
||||
0.02: Shows night time on the map (2022/12/28)
|
||||
0.03: Add 1 minute timer with upper taps (2023/01/05)
|
||||
1.00: Page to set up custom time zones (2023/01/06)
|
||||
|
|
@ -2,14 +2,17 @@
|
|||
|
||||
* Works with Bangle 2
|
||||
* Timer
|
||||
* Right tap: start/increase by 10 minutes; Left tap: decrease by 5 minutes
|
||||
* Top Right tap: increase by 1 minute
|
||||
* Top Left tap: decrease by 1 minute
|
||||
* Bottom Right tap: increase by 10 minutes
|
||||
* Bottom Left tap: decrease by 5 minutes
|
||||
* Short buzz at T-30, T-20, T-10 ; Double buzz at T
|
||||
* Other time zones
|
||||
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
|
||||
* Showing Paris and Tokyo by default, but you can customize this using the dedicated configuration page on the app store
|
||||
* World Map
|
||||
* The yellow line shows the position of the sun
|
||||
* The map shows day and night on Earth and the position of the Sun (yellow line)
|
||||
|
||||

|
||||
 
|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
||||
|
|
|
|||
|
|
@ -18,19 +18,29 @@ var timervalue = 0;
|
|||
var istimeron = false;
|
||||
var timertick;
|
||||
|
||||
Bangle.on('touch',t=>{
|
||||
if (t == 1) {
|
||||
Bangle.on('touch',(touchside, touchdata)=>{
|
||||
if (touchside == 1) {
|
||||
Bangle.buzz(30);
|
||||
if (timervalue < 5*60) { timervalue = 1 ; }
|
||||
else { timervalue -= 5*60; }
|
||||
var changevalue = 0;
|
||||
if(touchdata.y > 88) {
|
||||
changevalue += 60*5;
|
||||
} else {
|
||||
changevalue += 60*1;
|
||||
}
|
||||
if (timervalue < changevalue) { timervalue = 1 ; }
|
||||
else { timervalue -= changevalue; }
|
||||
}
|
||||
else if (t == 2) {
|
||||
else if (touchside == 2) {
|
||||
Bangle.buzz(30);
|
||||
if (!istimeron) {
|
||||
istimeron = true;
|
||||
timertick = setInterval(countDown, 1000);
|
||||
}
|
||||
timervalue += 60*10;
|
||||
if(touchdata.y > 88) {
|
||||
timervalue += 60*10;
|
||||
} else {
|
||||
timervalue += 60*1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -73,12 +83,13 @@ function countDown() {
|
|||
function showWelcomeMessage() {
|
||||
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||
g.setFontAlign(0, 0).setFont("6x8");
|
||||
g.drawString("Touch right to", 44, 80);
|
||||
g.drawString("Tap right to", 44, 80);
|
||||
g.drawString("start timer", 44, 88);
|
||||
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
||||
}
|
||||
|
||||
// time
|
||||
var offsets = require("Storage").readJSON("a_clock_timer.settings.json") || [ ["PAR",1], ["TYO",9] ];
|
||||
var drawTimeout;
|
||||
|
||||
function getGmt() {
|
||||
|
|
@ -102,20 +113,34 @@ function queueNextDraw() {
|
|||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||
|
||||
var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
|
||||
|
||||
var gmtHours = getGmt().getHours();
|
||||
|
||||
var x_sun = 176 - (gmtHours / 24 * 176 + 4);
|
||||
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
|
||||
g.reset();
|
||||
|
||||
var x_night_start = (176 - (((gmtHours-6)%24) / 24 * 176 + 4)) % 176;
|
||||
var x_night_end = 176 - (((gmtHours+6)%24) / 24 * 176 + 4);
|
||||
g.setColor('#000');
|
||||
for (let x = x_night_start; x < (x_night_end < x_night_start ? 176 : x_night_end); x+=2) {
|
||||
g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||
}
|
||||
if (x_night_end < x_night_start) {
|
||||
for (let x = 0; x < x_night_end; x+=2) {
|
||||
g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
var locale = require("locale");
|
||||
|
||||
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
|
||||
g.setFont("6x8");
|
||||
g.drawString(locale.date(new Date(),1), 125, 68);
|
||||
g.drawString("PAR "+locale.time(getTimeFromTimezone(1),1), 125, 80);
|
||||
g.drawString("TYO "+locale.time(getTimeFromTimezone(9),1), 125, 88);
|
||||
g.drawString(offsets[0][0]+" "+locale.time(getTimeFromTimezone(offsets[0][1]),1), 125, 80);
|
||||
g.drawString(offsets[1][0]+" "+locale.time(getTimeFromTimezone(offsets[1][1]),1), 125, 88);
|
||||
|
||||
queueNextDraw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p>You can set the 2 additional timezones displayed by the clock.</p>
|
||||
<table id="a_clock_timer-offsets">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>UTC Offset (Hours)</th>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script>
|
||||
var offsets=[];
|
||||
try{
|
||||
var stored = localStorage.getItem('a_clock_timer-offset-list')
|
||||
if(stored) offsets = JSON.parse(stored);
|
||||
if (!offsets || offsets.length!=2) {
|
||||
throw "Offsets invalid";
|
||||
}
|
||||
} catch(e){
|
||||
offsets=[
|
||||
["PAR",1],
|
||||
["TYO",9],
|
||||
];
|
||||
}
|
||||
console.log(offsets);
|
||||
var tbl=document.getElementById("a_clock_timer-offsets");
|
||||
for (var i=0; i<2; i++) {
|
||||
var $offset = document.createElement('tr')
|
||||
$offset.innerHTML = `
|
||||
<td><input type="text" size="4" maxlength="3" id="name_${i}" value="${offsets[i][0]}"></td>
|
||||
<td><input type="number" id="offset_${i}" value="${offsets[i][1]}"></td>`
|
||||
tbl.append($offset);
|
||||
}
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var storage_offsets=[];
|
||||
var app_offsets=[];
|
||||
for (var i=0; i<2; i++) {
|
||||
var name=document.getElementById("name_"+i).value;
|
||||
var offset=document.getElementById("offset_"+i).value;
|
||||
app_offsets.push([name,offset]);
|
||||
storage_offsets.push([name,offset]);
|
||||
}
|
||||
console.log(storage_offsets);
|
||||
console.log(app_offsets);
|
||||
localStorage.setItem('a_clock_timer-offset-list',JSON.stringify(storage_offsets));
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"a_clock_timer.settings.json", content:JSON.stringify(app_offsets)},
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
{
|
||||
"id": "a_clock_timer",
|
||||
"name": "A Clock with Timer",
|
||||
"version": "0.01",
|
||||
"version": "1.00",
|
||||
"description": "A Clock with Timer, Map and Time Zones",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"a_clock_timer.app.js","url":"app.js"},
|
||||
{"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"a_clock_timer.settings.json"}]
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.9 KiB |
|
|
@ -1,2 +1,3 @@
|
|||
1.00: Release (2021/12/01)
|
||||
1.01: Grey font when timer is frozen (2021/12/04)
|
||||
1.02: Force light theme, since the app is not designed for dark theme (2022/12/28)
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ function draw() {
|
|||
g.drawRect(88+8,138-24, 176-10, 138+22);
|
||||
}
|
||||
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||
require("FontHaxorNarrow7x17").add(Graphics);
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id":"a_speech_timer",
|
||||
"name":"Speech Timer",
|
||||
"icon": "app.png",
|
||||
"version":"1.01",
|
||||
"version":"1.02",
|
||||
"description": "A timer designed to help keeping your speeches and presentations to time.",
|
||||
"tags": "tool,timer",
|
||||
"readme":"README.md",
|
||||
|
|
|
|||
|
|
@ -7,3 +7,7 @@
|
|||
0.07: Clkinfo improvements.
|
||||
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||
Fix clkinfo icon
|
||||
0.09: Ensure Agenda supplies an image for clkinfo items
|
||||
0.10: Update clock_info to avoid a redraw
|
||||
0.11: Setting to use "Today" and "Yesterday" instead of dates
|
||||
Added dynamic, short and range fields to clkinfo
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
(function() {
|
||||
function getPassedSec(date) {
|
||||
var now = new Date();
|
||||
var passed = (now-date)/1000;
|
||||
if(passed<0) return 0;
|
||||
return passed;
|
||||
}
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||
dynamic: true,
|
||||
items: []
|
||||
};
|
||||
var locale = require("locale");
|
||||
|
|
@ -15,12 +22,16 @@
|
|||
var title = entry.title.slice(0,12);
|
||||
var date = new Date(entry.timestamp*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
|
||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||
|
||||
agendaItems.items.push({
|
||||
name: "Agenda "+i,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||
hasRange: true,
|
||||
get: () => ({ text: title + "\n" + dateStr,
|
||||
img: agendaItems.img, short: shortStr.trim(),
|
||||
v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
|
||||
show: function() {},
|
||||
hide: function () {}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,16 +33,32 @@ CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
|
|||
function getDate(timestamp) {
|
||||
return new Date(timestamp*1000);
|
||||
}
|
||||
function formatDay(date) {
|
||||
if (!settings.useToday) {
|
||||
return Locale.date(date);
|
||||
}
|
||||
const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
if (dateformatted == today) {
|
||||
return /*LANG*/"Today ";
|
||||
} else {
|
||||
const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
|
||||
if (dateformatted == tomorrow) {
|
||||
return /*LANG*/"Tomorrow ";
|
||||
}
|
||||
return Locale.date(date);
|
||||
}
|
||||
}
|
||||
function formatDateLong(date, includeDay, allDay) {
|
||||
let shortTime = Locale.time(date,1)+Locale.meridian(date);
|
||||
if(allDay) shortTime = "";
|
||||
if(includeDay || allDay)
|
||||
return Locale.date(date)+" "+shortTime;
|
||||
if(includeDay || allDay) {
|
||||
return formatDay(date)+" "+shortTime;
|
||||
}
|
||||
return shortTime;
|
||||
}
|
||||
function formatDateShort(date, allDay) {
|
||||
return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
|
||||
"" : Locale.time(date,1)+Locale.meridian(date));
|
||||
return formatDay(date).replace(/\d\d\d\d/,"")+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
|
||||
}
|
||||
|
||||
var lines = [];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.08",
|
||||
"version": "0.11",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Use 'Today',..." : {
|
||||
value : !!settings.useToday,
|
||||
onchange: v => {
|
||||
settings.useToday = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,3 +3,6 @@
|
|||
0.03: Do not load AGPS data on boot
|
||||
Increase minimum interval to 6 hours
|
||||
0.04: Write AGPS data chunks with delay to improve reliability
|
||||
0.05: Show last success date
|
||||
Do not start A-GPS update automatically
|
||||
0.06: Switch off gps after updating
|
||||
|
|
@ -23,12 +23,26 @@ Bangle.drawWidgets();
|
|||
|
||||
let waiting = false;
|
||||
|
||||
function start() {
|
||||
function start(restart) {
|
||||
g.reset();
|
||||
g.clear();
|
||||
waiting = false;
|
||||
display("Retry?", "touch to retry");
|
||||
if (!restart) {
|
||||
display("Start?", "touch to start");
|
||||
}
|
||||
else {
|
||||
display("Retry?", "touch to retry");
|
||||
}
|
||||
Bangle.on("touch", () => { updateAgps(); });
|
||||
|
||||
const file = "agpsdata.json";
|
||||
let data = require("Storage").readJSON(file, 1) || {};
|
||||
if (data.lastUpdate) {
|
||||
g.setFont("Vector", 11);
|
||||
g.drawString("last success:", 5, g.getHeight() - 22);
|
||||
g.drawString(new Date(data.lastUpdate).toISOString(), 5, g.getHeight() - 11);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateAgps() {
|
||||
|
|
@ -36,7 +50,7 @@ function updateAgps() {
|
|||
g.clear();
|
||||
if (!waiting) {
|
||||
waiting = true;
|
||||
display("Updating A-GPS...", "takes ~ 10 seconds");
|
||||
display("Updating A-GPS...", "takes ~10 seconds");
|
||||
require("agpsdata").pull(function() {
|
||||
waiting = false;
|
||||
display("A-GPS updated.", "touch to close");
|
||||
|
|
@ -45,10 +59,10 @@ function updateAgps() {
|
|||
function(error) {
|
||||
waiting = false;
|
||||
E.showAlert(error, "Error")
|
||||
.then(() => { start(); });
|
||||
.then(() => { start(true); });
|
||||
});
|
||||
} else {
|
||||
display("Waiting...");
|
||||
}
|
||||
}
|
||||
updateAgps();
|
||||
start(false);
|
||||
|
|
|
|||
|
|
@ -10,20 +10,24 @@ readSettings();
|
|||
|
||||
function setAGPS(b64) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
const gnsstype = settings.gnsstype || 1; // default GPS
|
||||
initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
|
||||
eval(initCommands);
|
||||
Bangle.setGPSPower(1,"agpsdata"); // turn GPS on
|
||||
Serial1.println(CASIC_CHECKSUM("$PCAS04," + gnsstype)); // set GNSS mode
|
||||
|
||||
try {
|
||||
writeChunks(atob(b64), resolve);
|
||||
writeChunks(atob(b64), ()=>{
|
||||
setTimeout(()=>{
|
||||
Bangle.setGPSPower(0,"agpsdata");
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("error:", e);
|
||||
Bangle.setGPSPower(0,"agpsdata");
|
||||
reject();
|
||||
}
|
||||
});
|
||||
|
|
@ -36,9 +40,8 @@ function writeChunks(bin, resolve) {
|
|||
setTimeout(function() {
|
||||
if (chunkI < bin.length) {
|
||||
var chunk = bin.substr(chunkI, chunkSize);
|
||||
js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
eval(js);
|
||||
|
||||
Serial1.write(atob(btoa(chunk)));
|
||||
|
||||
chunkI += chunkSize;
|
||||
writeChunks(bin, resolve);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "A-GPS Data Downloader App",
|
||||
"shortName":"A-GPS Data",
|
||||
"icon": "agpsdata.png",
|
||||
"version":"0.04",
|
||||
"version":"0.06",
|
||||
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||
"tags": "boot,tool,assisted,gps,agps,http",
|
||||
"allow_emulator":true,
|
||||
|
|
|
|||
|
|
@ -2,3 +2,6 @@
|
|||
0.02: Design improvements and fixes.
|
||||
0.03: Indicate battery level through line occurrence.
|
||||
0.04: Use widget_utils module.
|
||||
0.05: Support for clkinfo.
|
||||
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||
0.07: Use clock_info.addInteractive instead of a custom implementation
|
||||
|
|
@ -10,7 +10,8 @@ The original output of stable diffusion is shown here:
|
|||
|
||||
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||
probability is correlated with the battery level. So if your screen contains only
|
||||
a few lines its time to charge your bangle again ;)
|
||||
a few lines its time to charge your bangle again ;) Also note that the upper text
|
||||
implements the clkinfo module and can be configured via touch and swipe left/right and up/down.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
/**
|
||||
/************************************************
|
||||
* AI Clock
|
||||
*/
|
||||
const clock_info = require("clock_info");
|
||||
|
||||
|
||||
|
||||
/************************************************
|
||||
* Assets
|
||||
*/
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
Graphics.prototype.setFontGochiHand = function(scale) {
|
||||
// Actual height 27 (29 - 3)
|
||||
|
|
@ -13,33 +20,14 @@ Graphics.prototype.setFontGochiHand = function(scale) {
|
|||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set some important constants such as width, height and center
|
||||
*/
|
||||
var W = g.getWidth(),R=W/2;
|
||||
var H = g.getHeight();
|
||||
var cx = W/2;
|
||||
var cy = H/2;
|
||||
var drawTimeout;
|
||||
|
||||
/*
|
||||
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
||||
*/
|
||||
Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
|
||||
angle = angle % 360;
|
||||
var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
|
||||
return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
|
||||
{x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
|
||||
};
|
||||
|
||||
|
||||
function drawBackground() {
|
||||
function drawBackground(start, end) {
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setColor("#000");
|
||||
|
||||
var bat = E.getBattery() / 100.0;
|
||||
var y = 0;
|
||||
while(y < H){
|
||||
var y = start;
|
||||
while(y < end){
|
||||
// Show less lines in case of small battery level.
|
||||
if(Math.random() > bat){
|
||||
y += 5;
|
||||
|
|
@ -55,6 +43,30 @@ function drawBackground() {
|
|||
}
|
||||
|
||||
|
||||
/************************************************
|
||||
* Set some important constants such as width, height and center
|
||||
*/
|
||||
var W = g.getWidth(),R=W/2;
|
||||
var H = g.getHeight();
|
||||
var cx = W/2;
|
||||
var cy = H/2;
|
||||
var drawTimeout;
|
||||
|
||||
var clkInfoY = 60;
|
||||
|
||||
|
||||
/*
|
||||
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
||||
*/
|
||||
Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
|
||||
angle = angle % 360;
|
||||
var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
|
||||
return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
|
||||
{x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
function drawCircle(isLocked){
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(cx, cy, 12);
|
||||
|
|
@ -64,19 +76,12 @@ function drawCircle(isLocked){
|
|||
g.fillCircle(cx, cy, 6);
|
||||
}
|
||||
|
||||
function toAngle(a){
|
||||
if (a < 0){
|
||||
return 360 + a;
|
||||
}
|
||||
|
||||
if(a > 360) {
|
||||
return 360 - a;
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
function drawTime(){
|
||||
// Draw digital time first
|
||||
drawDigits();
|
||||
|
||||
// And now the analog time
|
||||
var drawHourHand = g.drawRotRect.bind(g,8,12,R-38);
|
||||
var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 );
|
||||
|
||||
|
|
@ -90,13 +95,6 @@ function drawTime(){
|
|||
h += date.getMinutes()/60.0;
|
||||
h = parseInt(h*360/12);
|
||||
|
||||
// Draw minute and hour bg
|
||||
g.setColor(g.theme.bg);
|
||||
drawHourHand(toAngle(h-3));
|
||||
drawHourHand(toAngle(h+3));
|
||||
drawMinuteHand(toAngle(m-2));
|
||||
drawMinuteHand(toAngle(m+3));
|
||||
|
||||
// Draw minute and hour fg
|
||||
g.setColor(g.theme.fg);
|
||||
drawHourHand(h);
|
||||
|
|
@ -104,28 +102,6 @@ function drawTime(){
|
|||
}
|
||||
|
||||
|
||||
|
||||
function drawDate(){
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFontGochiHand();
|
||||
|
||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
|
||||
var w = g.stringWidth(text);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
// Draw right line as designed by stable diffusion
|
||||
g.drawLine(cx+w/2+5, 20, cx+w/2+5, 40+12);
|
||||
g.drawLine(cx+w/2+6, 20, cx+w/2+6, 40+12);
|
||||
g.drawLine(cx+w/2+7, 20, cx+w/2+7, 40+12);
|
||||
|
||||
// And finally the text
|
||||
g.drawString(text, cx, 40);
|
||||
}
|
||||
|
||||
|
||||
function drawDigits(){
|
||||
var date = new Date();
|
||||
|
||||
|
|
@ -156,19 +132,22 @@ function drawDigits(){
|
|||
}
|
||||
|
||||
|
||||
|
||||
function draw(){
|
||||
// Note that we force a redraw also of the clock info as
|
||||
// we want to ensure (for design purpose) that the hands
|
||||
// are above the clkinfo section.
|
||||
clockInfoMenu.redraw();
|
||||
}
|
||||
|
||||
|
||||
function drawMainClock(){
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
|
||||
g.setColor("#fff");
|
||||
g.reset().clearRect(0, clkInfoY, g.getWidth(), g.getHeight());
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
g.setColor(1,1,1);
|
||||
drawBackground();
|
||||
drawDate();
|
||||
drawDigits();
|
||||
drawBackground(clkInfoY, H);
|
||||
drawTime();
|
||||
drawCircle(Bangle.isLocked());
|
||||
}
|
||||
|
|
@ -179,7 +158,7 @@ function draw(){
|
|||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(true);
|
||||
draw();
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
|
@ -191,6 +170,12 @@ Bangle.on('lock', function(isLocked) {
|
|||
});
|
||||
|
||||
|
||||
E.on("kill", function(){
|
||||
clockInfoMenu.remove();
|
||||
delete clockInfoMenu;
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Some helpers
|
||||
*/
|
||||
|
|
@ -203,6 +188,54 @@ function queueDraw() {
|
|||
}
|
||||
|
||||
|
||||
/************************************************
|
||||
* Clock Info
|
||||
*/
|
||||
let clockInfoItems = clock_info.load();
|
||||
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||
x : 0,
|
||||
y: 0,
|
||||
w: W,
|
||||
h: clkInfoY,
|
||||
draw : (itm, info, options) => {
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector", 20);
|
||||
|
||||
g.setColor("#fff");
|
||||
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||
drawBackground(0, clkInfoY+2);
|
||||
|
||||
// Set text and font
|
||||
var image = info.img;
|
||||
var text = String(info.text);
|
||||
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var strWidth = g.stringWidth(text);
|
||||
var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
|
||||
var w = imgWidth + strWidth;
|
||||
|
||||
// Draw right line as designed by stable diffusion
|
||||
g.setColor(options.focus ? "#0f0" : "#fff");
|
||||
g.fillRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
|
||||
|
||||
g.setColor("#000");
|
||||
g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
|
||||
g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
|
||||
g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
|
||||
|
||||
// Draw text and image
|
||||
g.drawString(text, cx+imgWidth/2, 42);
|
||||
g.drawString(text, cx+1+imgWidth/2, 41);
|
||||
|
||||
if(image != null) {
|
||||
var scale = image.width ? imgWidth / image.width : 1;
|
||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
|
||||
}
|
||||
|
||||
drawMainClock();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Lets start widgets, listen for btn etc.
|
||||
|
|
@ -216,8 +249,9 @@ Bangle.loadWidgets();
|
|||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
require('widget_utils').hide();
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false});
|
||||
draw();
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
"name": "AI Clock",
|
||||
"shortName":"AI Clock",
|
||||
"icon": "aiclock.png",
|
||||
"version":"0.04",
|
||||
"version":"0.07",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||
|
|
@ -11,7 +11,9 @@
|
|||
"tags": "clock",
|
||||
"screenshots": [
|
||||
{"url":"orig.png"},
|
||||
{"url":"impl.png"}
|
||||
{"url":"impl.png"},
|
||||
{"url":"impl_2.png"},
|
||||
{"url":"impl_3.png"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"aiclock.app.js","url":"aiclock.app.js"},
|
||||
|
|
|
|||
|
|
@ -36,4 +36,5 @@
|
|||
0.33: Allow hiding timers&alarms
|
||||
0.34: Add "Confirm" option to alarm/timer edit menus
|
||||
0.35: Add automatic translation of more strings
|
||||
|
||||
0.36: alarm widget moved out of app
|
||||
0.37: add message input and dated Events
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
# Alarms & Timers
|
||||
|
||||
This app allows you to add/modify any alarms and timers.
|
||||
This app allows you to add/modify any alarms, timers and events.
|
||||
|
||||
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
|
||||
|
||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||
|
||||
## Menu overview
|
||||
|
||||
- `New...`
|
||||
- `New Alarm` → Configure a new alarm
|
||||
- `New Alarm` → Configure a new alarm (triggered based on time and day of week)
|
||||
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
|
||||
- `New Timer` → Configure a new timer
|
||||
- `New Timer` → Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
|
||||
- `New Event` → Configure a new event (triggered based on time and date)
|
||||
- `Advanced`
|
||||
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
|
||||
- `Enable All` → Enable _all_ disabled alarms & timers
|
||||
|
|
|
|||
|
|
@ -48,9 +48,10 @@ function showMainMenu() {
|
|||
};
|
||||
|
||||
alarms.forEach((e, index) => {
|
||||
var label = e.timer
|
||||
var label = (e.timer
|
||||
? require("time_utils").formatDuration(e.timer)
|
||||
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
|
||||
: (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
|
||||
) + (e.msg ? " " + e.msg : "");
|
||||
menu[label] = {
|
||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||
|
|
@ -67,11 +68,12 @@ function showNewMenu() {
|
|||
"": { "title": /*LANG*/"New..." },
|
||||
"< Back": () => showMainMenu(),
|
||||
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
||||
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
|
||||
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
|
||||
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
|
||||
});
|
||||
}
|
||||
|
||||
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
||||
function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
||||
var isNew = alarmIndex === undefined;
|
||||
|
||||
var alarm = require("sched").newDefaultAlarm();
|
||||
|
|
@ -82,11 +84,16 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
}
|
||||
|
||||
var time = require("time_utils").decodeTime(alarm.t);
|
||||
if (withDate && !alarm.date) alarm.date = new Date().toLocalISOString().slice(0,10);
|
||||
var date = alarm.date ? new Date(alarm.date) : undefined;
|
||||
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
||||
var keyboard = "textinput";
|
||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||
|
||||
const menu = {
|
||||
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
|
||||
"": { "title": title },
|
||||
"< Back": () => {
|
||||
prepareAlarmForSave(alarm, alarmIndex, time);
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
},
|
||||
|
|
@ -106,6 +113,36 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
wrap: true,
|
||||
onchange: v => time.m = v
|
||||
},
|
||||
/*LANG*/"Day": {
|
||||
value: date ? date.getDate() : null,
|
||||
min: 1,
|
||||
max: 31,
|
||||
wrap: true,
|
||||
onchange: v => date.setDate(v)
|
||||
},
|
||||
/*LANG*/"Month": {
|
||||
value: date ? date.getMonth() + 1 : null,
|
||||
format: v => require("date_utils").month(v),
|
||||
onchange: v => date.setMonth((v+11)%12)
|
||||
},
|
||||
/*LANG*/"Year": {
|
||||
value: date ? date.getFullYear() : null,
|
||||
min: new Date().getFullYear(),
|
||||
max: 2100,
|
||||
onchange: v => date.setFullYear(v)
|
||||
},
|
||||
/*LANG*/"Message": {
|
||||
value: alarm.msg,
|
||||
onchange: () => {
|
||||
setTimeout(() => {
|
||||
keyboard.input({text:alarm.msg}).then(result => {
|
||||
alarm.msg = result;
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
/*LANG*/"Enabled": {
|
||||
value: alarm.on,
|
||||
onchange: v => alarm.on = v
|
||||
|
|
@ -115,8 +152,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
|
||||
alarm.rp = repeat;
|
||||
alarm.dow = dow;
|
||||
alarm.t = require("time_utils").encodeTime(time);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
})
|
||||
},
|
||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
||||
|
|
@ -136,6 +173,15 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
}
|
||||
};
|
||||
|
||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||
if (alarm.date || withDate) {
|
||||
delete menu[/*LANG*/"Repeat"];
|
||||
} else {
|
||||
delete menu[/*LANG*/"Day"];
|
||||
delete menu[/*LANG*/"Month"];
|
||||
delete menu[/*LANG*/"Year"];
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
menu[/*LANG*/"Delete"] = () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
|
||||
|
|
@ -145,7 +191,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
showMainMenu();
|
||||
} else {
|
||||
alarm.t = require("time_utils").encodeTime(time);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -154,14 +200,17 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function prepareAlarmForSave(alarm, alarmIndex, time) {
|
||||
function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
|
||||
alarm.t = require("time_utils").encodeTime(time);
|
||||
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
|
||||
if(date) alarm.date = date.toLocalISOString().slice(0,10);
|
||||
|
||||
if (alarmIndex === undefined) {
|
||||
alarms.push(alarm);
|
||||
} else {
|
||||
alarms[alarmIndex] = alarm;
|
||||
if(!temp) {
|
||||
if (alarmIndex === undefined) {
|
||||
alarms.push(alarm);
|
||||
} else {
|
||||
alarms[alarmIndex] = alarm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,6 +304,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
}
|
||||
|
||||
var time = require("time_utils").decodeTime(timer.timer);
|
||||
var keyboard = "textinput";
|
||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||
|
||||
const menu = {
|
||||
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
|
||||
|
|
@ -285,6 +336,18 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
wrap: true,
|
||||
onchange: v => time.s = v
|
||||
},
|
||||
/*LANG*/"Message": {
|
||||
value: timer.msg,
|
||||
onchange: () => {
|
||||
setTimeout(() => {
|
||||
keyboard.input({text:timer.msg}).then(result => {
|
||||
timer.msg = result;
|
||||
prepareTimerForSave(timer, timerIndex, time, true);
|
||||
setTimeout(showEditTimerMenu, 10, timer, timerIndex);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
/*LANG*/"Enabled": {
|
||||
value: timer.on,
|
||||
onchange: v => timer.on = v
|
||||
|
|
@ -306,6 +369,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
}
|
||||
};
|
||||
|
||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||
if (!isNew) {
|
||||
menu[/*LANG*/"Delete"] = () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
|
||||
|
|
@ -324,15 +388,17 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function prepareTimerForSave(timer, timerIndex, time) {
|
||||
function prepareTimerForSave(timer, timerIndex, time, temp) {
|
||||
timer.timer = require("time_utils").encodeTime(time);
|
||||
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
|
||||
timer.last = 0;
|
||||
|
||||
if (timerIndex === undefined) {
|
||||
alarms.push(timer);
|
||||
} else {
|
||||
alarms[timerIndex] = timer;
|
||||
if (!temp) {
|
||||
if (timerIndex === undefined) {
|
||||
alarms.push(timer);
|
||||
} else {
|
||||
alarms[timerIndex] = timer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,16 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.35",
|
||||
"version": "0.37",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,widget",
|
||||
"tags": "tool,alarm",
|
||||
"supports": [ "BANGLEJS", "BANGLEJS2" ],
|
||||
"readme": "README.md",
|
||||
"dependencies": { "scheduler":"type" },
|
||||
"dependencies": { "scheduler":"type", "alarm":"widget" },
|
||||
"storage": [
|
||||
{ "name": "alarm.app.js", "url": "app.js" },
|
||||
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
||||
{ "name": "alarm.wid.js", "url": "widget.js" }
|
||||
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true }
|
||||
],
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-1.png" },
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@
|
|||
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
|
||||
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
|
||||
0.18: Use new message library
|
||||
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
||||
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
||||
0.19: Add automatic translation for a couple of strings.
|
||||
0.20: Fix wrong event used for forwarded GPS data from Gadgetbridge and add mapper to map longitude value correctly.
|
||||
0.21: Fix broken 'Messages' button in menu
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||"Call", body:"Incoming call\n"+event.number});
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"alarm" : function() {
|
||||
|
|
@ -134,7 +134,11 @@
|
|||
event.satellites = NaN;
|
||||
event.course = NaN;
|
||||
event.fix = 1;
|
||||
Bangle.emit('gps', event);
|
||||
if (event.long!==undefined) {
|
||||
event.lon = event.long;
|
||||
delete event.long;
|
||||
}
|
||||
Bangle.emit('GPS', event);
|
||||
},
|
||||
"is_gps_active": function() {
|
||||
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
|
||||
|
|
@ -148,7 +152,7 @@
|
|||
Bangle.http = (url,options)=>{
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject("Not connected to Bluetooth");
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
|
|
@ -208,7 +212,7 @@
|
|||
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = (isOn, appID) => {
|
||||
// if not connected, use old logic
|
||||
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
||||
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
||||
// Emulate old GPS power logic
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.18",
|
||||
"version": "0.21",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
(function(back) {
|
||||
|
||||
|
||||
|
||||
|
||||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>require("message").openGUI(),
|
||||
/*LANG*/"Messages" : ()=>require("messages").openGUI(),
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Update to work with Bangle.js 2
|
||||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
0.04: Now turns GPS off after upload
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
function jsFromBase64(b64) {
|
||||
var bin = atob(b64);
|
||||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
var js = "\x10Bangle.setGPSPower(1,'agps');\n"; // turn GPS on
|
||||
if (isB1) { // UBLOX
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
|
|
@ -158,6 +158,7 @@
|
|||
var chunk = bin.substr(i,chunkSize);
|
||||
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||
}
|
||||
js = "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
|
||||
return js;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"sortorder": -1,
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
0.01: Create astrocalc app
|
||||
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
|
||||
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
0.04: Compatibility with Bangle.js 2, get location from My Location
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@
|
|||
* Calculate the Sun and Moon positions based on watch GPS and display graphically
|
||||
*/
|
||||
|
||||
const SunCalc = require("suncalc.js");
|
||||
const SunCalc = require("suncalc"); // from modules folder
|
||||
const storage = require("Storage");
|
||||
const LAST_GPS_FILE = "astrocalc.gps.json";
|
||||
let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null);
|
||||
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
||||
|
||||
function drawMoon(phase, x, y) {
|
||||
const moonImgFiles = [
|
||||
|
|
@ -73,7 +72,7 @@ function drawTitle(key) {
|
|||
*/
|
||||
function drawPoint(angle, radius, color) {
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 80; // watch face radius
|
||||
const faceWidth = g.getWidth()/3; // watch face radius
|
||||
const centerPx = g.getWidth() / 2;
|
||||
|
||||
const a = angle * pRad;
|
||||
|
|
@ -141,6 +140,7 @@ function drawData(title, obj, startX, startY) {
|
|||
|
||||
function drawMoonPositionPage(gps, title) {
|
||||
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
|
||||
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||
|
||||
const pageData = {
|
||||
Azimuth: pos.azimuth.toFixed(2),
|
||||
|
|
@ -150,59 +150,61 @@ function drawMoonPositionPage(gps, title) {
|
|||
};
|
||||
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
||||
|
||||
drawData(title, pageData, null, 80);
|
||||
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20);
|
||||
drawPoints();
|
||||
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(azimuthDegrees, 8, moonColor);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawMoonIlluminationPage(gps, title) {
|
||||
const phaseNames = [
|
||||
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
||||
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
|
||||
/*LANG*/"New Moon", /*LANG*/"Waxing Crescent", /*LANG*/"First Quarter", /*LANG*/"Waxing Gibbous",
|
||||
/*LANG*/"Full Moon", /*LANG*/"Waning Gibbous", /*LANG*/"Last Quater", /*LANG*/"Waning Crescent",
|
||||
];
|
||||
|
||||
const phase = SunCalc.getMoonIllumination(new Date());
|
||||
const phaseIdx = Math.round(phase.phase*8);
|
||||
const pageData = {
|
||||
Phase: phaseNames[phase.phase],
|
||||
Phase: phaseNames[phaseIdx],
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 35);
|
||||
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
|
||||
drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
|
||||
function drawMoonTimesPage(gps, title) {
|
||||
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
|
||||
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||
|
||||
const pageData = {
|
||||
Rise: dateToTimeString(times.rise),
|
||||
Set: dateToTimeString(times.set),
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 105);
|
||||
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||
drawPoints();
|
||||
|
||||
// Draw the moon rise position
|
||||
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
|
||||
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
|
||||
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(riseAzimuthDegrees, 8, moonColor);
|
||||
|
||||
// Draw the moon set position
|
||||
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
|
||||
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
|
||||
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(setAzimuthDegrees, 8, moonColor);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawSunShowPage(gps, key, date) {
|
||||
|
|
@ -224,7 +226,7 @@ function drawSunShowPage(gps, key, date) {
|
|||
Degrees: azimuthDegrees
|
||||
};
|
||||
|
||||
drawData(key, pageData, null, 85);
|
||||
drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||
|
||||
drawPoints();
|
||||
|
||||
|
|
@ -233,7 +235,7 @@ function drawSunShowPage(gps, key, date) {
|
|||
|
||||
m = setWatch(() => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -273,15 +275,15 @@ function moonIndexPageMenu(gps) {
|
|||
},
|
||||
"Times": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonTimesPage(gps, "Times");
|
||||
drawMoonTimesPage(gps, /*LANG*/"Times");
|
||||
},
|
||||
"Position": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonPositionPage(gps, "Position");
|
||||
drawMoonPositionPage(gps, /*LANG*/"Position");
|
||||
},
|
||||
"Illumination": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonIlluminationPage(gps, "Illumination");
|
||||
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
|
||||
},
|
||||
"< Back": () => m = indexPageMenu(gps),
|
||||
};
|
||||
|
|
@ -292,15 +294,15 @@ function moonIndexPageMenu(gps) {
|
|||
function indexPageMenu(gps) {
|
||||
const menu = {
|
||||
"": {
|
||||
"title": "Select",
|
||||
"title": /*LANG*/"Select",
|
||||
},
|
||||
"Sun": () => {
|
||||
/*LANG*/"Sun": () => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
},
|
||||
"Moon": () => {
|
||||
/*LANG*/"Moon": () => {
|
||||
m = moonIndexPageMenu(gps);
|
||||
},
|
||||
"< Exit": () => { load(); }
|
||||
"< Back": () => { load(); }
|
||||
};
|
||||
|
||||
return E.showMenu(menu);
|
||||
|
|
@ -310,79 +312,10 @@ function getCenterStringX(str) {
|
|||
return (g.getWidth() - g.stringWidth(str)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
|
||||
*/
|
||||
function drawGPSWaitPage() {
|
||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="));
|
||||
const str1 = "Astrocalc v0.02";
|
||||
const str2 = "Locating GPS";
|
||||
const str3 = "Please wait...";
|
||||
|
||||
g.clear();
|
||||
g.drawImage(img, 100, 50);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(str1, getCenterStringX(str1), 105);
|
||||
g.drawString(str2, getCenterStringX(str2), 140);
|
||||
g.drawString(str3, getCenterStringX(str3), 155);
|
||||
|
||||
if (lastGPS) {
|
||||
lastGPS = JSON.parse(lastGPS);
|
||||
lastGPS.time = new Date();
|
||||
|
||||
const str4 = "Press Button 3 to use last GPS";
|
||||
g.setColor("#d32e29");
|
||||
g.fillRect(0, 190, g.getWidth(), 215);
|
||||
g.setColor("#ffffff");
|
||||
g.drawString(str4, getCenterStringX(str4), 200);
|
||||
|
||||
setWatch(() => {
|
||||
clearWatch();
|
||||
Bangle.setGPSPower(0);
|
||||
m = indexPageMenu(lastGPS);
|
||||
}, BTN3, {repeat: false});
|
||||
}
|
||||
|
||||
g.flip();
|
||||
|
||||
const DEBUG = false;
|
||||
if (DEBUG) {
|
||||
clearWatch();
|
||||
|
||||
const gps = {
|
||||
"lat": 56.45783133333,
|
||||
"lon": -3.02188583333,
|
||||
"alt": 75.3,
|
||||
"speed": 0.070376,
|
||||
"course": NaN,
|
||||
"time":new Date(),
|
||||
"satellites": 4,
|
||||
"fix": 1
|
||||
};
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.on('GPS', (gps) => {
|
||||
if (gps.fix === 0) return;
|
||||
clearWatch();
|
||||
|
||||
if (isNaN(gps.course)) gps.course = 0;
|
||||
require("Storage").writeJSON(LAST_GPS_FILE, JSON.stringify(gps));
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.buzz();
|
||||
Bangle.setLCDPower(true);
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
Bangle.setGPSPower(1);
|
||||
drawGPSWaitPage();
|
||||
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||
indexPageMenu(location);
|
||||
}
|
||||
|
||||
let m;
|
||||
init();
|
||||
init();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"id": "astrocalc",
|
||||
"name": "Astrocalc",
|
||||
"version": "0.02",
|
||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
||||
"version": "0.04",
|
||||
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
|
||||
"icon": "astrocalc.png",
|
||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"tags": "app,sun,moon,cycles,tool",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"storage": [
|
||||
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
|
||||
{"name":"suncalc.js","url":"suncalc.js"},
|
||||
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
|
||||
{"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true},
|
||||
{"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true},
|
||||
|
|
|
|||
|
|
@ -1,328 +0,0 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
SunCalc.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: altitude(H, phi, c.dec)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
// adds a custom time to the times config
|
||||
|
||||
SunCalc.addTime = function (angle, riseName, setName) {
|
||||
times.push([angle, riseName, setName]);
|
||||
};
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: new Date(fromJulian(Jnoon)),
|
||||
nadir: new Date(fromJulian(Jnoon - 0.5))
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2));
|
||||
result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
// Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c
|
||||
|
||||
SunCalc.getMoonIllumination = function (date) {
|
||||
let month = date.getMonth();
|
||||
let year = date.getFullYear();
|
||||
let day = date.getDate();
|
||||
|
||||
let c = 0;
|
||||
let e = 0;
|
||||
let jd = 0;
|
||||
let b = 0;
|
||||
|
||||
if (month < 3) {
|
||||
year--;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
++month;
|
||||
c = 365.25 * year;
|
||||
e = 30.6 * month;
|
||||
jd = c + e + day - 694039.09; // jd is total days elapsed
|
||||
jd /= 29.5305882; // divide by the moon cycle
|
||||
b = parseInt(jd); // int(jd) -> b, take integer part of jd
|
||||
jd -= b; // subtract integer part to leave fractional part of original jd
|
||||
b = Math.round(jd * 8); // scale fraction from 0-8 and round
|
||||
|
||||
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
|
||||
|
||||
return {phase: b};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = date;
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else global.SunCalc = SunCalc;
|
||||
|
||||
}());
|
||||
|
|
@ -14,3 +14,4 @@
|
|||
0.14: Use ClockFace_menu.addItems
|
||||
0.15: Add Power saving option
|
||||
0.16: Support Fast Loading
|
||||
0.17: Hide widgets instead of not loading them at all
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "barclock",
|
||||
"name": "Bar Clock",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "A simple digital clock showing seconds as a bar",
|
||||
"icon": "clock-bar.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
(function(back) {
|
||||
let s = require("Storage").readJSON("barclock.settings.json", true) || {};
|
||||
// migrate "don't load widgets" to "hide widgets"
|
||||
if (!("hideWidgets" in s) && ("loadWidgets" in s) && !s.loadWidgets) {
|
||||
s.hideWidgets = 1;
|
||||
}
|
||||
delete s.loadWidgets;
|
||||
|
||||
function save(key, value) {
|
||||
s[key] = value;
|
||||
|
|
@ -19,7 +24,7 @@
|
|||
};
|
||||
let items = {
|
||||
showDate: s.showDate,
|
||||
loadWidgets: s.loadWidgets,
|
||||
hideWidgets: s.hideWidgets,
|
||||
};
|
||||
// Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
|
||||
if (process.env.HWVERSION>1) {
|
||||
|
|
|
|||
|
|
@ -4,3 +4,5 @@
|
|||
0.05: Update *on* the minute rather than every 15 secs
|
||||
Now show widgets
|
||||
Make compatible with themes, and Bangle.js 2
|
||||
0.06: Enable fastloading
|
||||
0.07: Adds fullscreen mode setting
|
||||
|
|
@ -1,32 +1,41 @@
|
|||
{
|
||||
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
|
||||
// https://github.com/eska-muc/BangleApps
|
||||
|
||||
var settings = require('Storage').readJSON("berlinc.json", true) || {};
|
||||
const fields = [4, 4, 11, 4];
|
||||
const offset = 24;
|
||||
const width = g.getWidth() - 2 * offset;
|
||||
const height = g.getHeight() - 2 * offset;
|
||||
const rowHeight = height / 4;
|
||||
|
||||
var show_date = false;
|
||||
var show_time = false;
|
||||
var yy = 0;
|
||||
let fullscreen = !!settings.fullscreen;
|
||||
|
||||
var rowlights = [];
|
||||
var time_digit = [];
|
||||
let show_date = false;
|
||||
let show_time = false;
|
||||
let yy = 0;
|
||||
|
||||
let rowlights = [];
|
||||
let time_digit = [];
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
let drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
let queueDraw = () => {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
};
|
||||
|
||||
function draw() {
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
|
||||
let draw = () => {
|
||||
let width = Math.min(Bangle.appRect.w,Bangle.appRect.h);
|
||||
let height = width;
|
||||
let offset = g.getHeight() - height;
|
||||
let x = Math.floor((g.getWidth() - width)/2);
|
||||
|
||||
if (show_date) height -= 8;
|
||||
let rowHeight = (height - 1) / 4;
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
var now = new Date();
|
||||
|
||||
// show date below the clock
|
||||
|
|
@ -37,7 +46,7 @@ function draw() {
|
|||
var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
|
||||
var strWidth = g.stringWidth(dateString);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1,-1);
|
||||
g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4);
|
||||
g.drawString(dateString, ( Bangle.appRect.x + Bangle.appRect.w - strWidth ) / 2, Bangle.appRect.y2 - 5);
|
||||
}
|
||||
|
||||
rowlights[0] = Math.floor(now.getHours() / 5);
|
||||
|
|
@ -50,15 +59,16 @@ function draw() {
|
|||
time_digit[2] = Math.floor(now.getMinutes() / 10);
|
||||
time_digit[3] = now.getMinutes() % 10;
|
||||
|
||||
g.drawRect(offset, offset, width + offset, height + offset);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawRect(x, offset, x + width - 1, height + offset - 1);
|
||||
for (row = 0; row < 4; row++) {
|
||||
nfields = fields[row];
|
||||
boxWidth = width / nfields;
|
||||
boxWidth = (width - 1) / nfields;
|
||||
|
||||
for (col = 0; col < nfields; col++) {
|
||||
x1 = col * boxWidth + offset;
|
||||
x1 = col * boxWidth + x;
|
||||
y1 = row * rowHeight + offset;
|
||||
x2 = (col + 1) * boxWidth + offset;
|
||||
x2 = (col + 1) * boxWidth + x;
|
||||
y2 = (row + 1) * rowHeight + offset;
|
||||
|
||||
g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2);
|
||||
|
|
@ -84,33 +94,53 @@ function draw() {
|
|||
queueDraw();
|
||||
}
|
||||
|
||||
function toggleDate() {
|
||||
let toggleDate = () => {
|
||||
show_date = ! show_date;
|
||||
draw();
|
||||
}
|
||||
|
||||
function toggleTime() {
|
||||
let toggleTime = () => {
|
||||
show_time = ! show_time;
|
||||
draw();
|
||||
}
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
let clear = () => {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
let onLcdPower = on => {
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let cleanup = () => {
|
||||
clear();
|
||||
Bangle.removeListener("lcdPower", onLcdPower);
|
||||
require("widget_utils").show();
|
||||
}
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',onLcdPower);
|
||||
|
||||
// Show launcher when button pressed, handle up/down
|
||||
Bangle.setUI("clockupdown", dir=> {
|
||||
Bangle.setUI({mode: "clockupdown", remove: cleanup}, dir=> {
|
||||
if (dir<0) toggleTime();
|
||||
if (dir>0) toggleDate();
|
||||
});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
|
||||
if (fullscreen){
|
||||
if (process.env.HWVERSION == 2) require("widget_utils").swipeOn();
|
||||
else require("widget_utils").hide();
|
||||
}
|
||||
|
||||
Bangle.drawWidgets();
|
||||
|
||||
draw();
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "berlinc",
|
||||
"name": "Berlin Clock",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
|
||||
"icon": "berlin-clock.png",
|
||||
"type": "clock",
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
"screenshots": [{"url":"berlin-clock-screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"berlinc.app.js","url":"berlin-clock.js"},
|
||||
{"name":"berlinc.settings.js","url":"settings.js"},
|
||||
{"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"berlinc.json"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
(function(back) {
|
||||
var FILE = "berlinc.json";
|
||||
var settings = Object.assign({
|
||||
fullscreem: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
var mainmenu = {
|
||||
"": {
|
||||
"title": "Berlin clock"
|
||||
},
|
||||
"< Back": () => back(),
|
||||
"Fullscreen": {
|
||||
value: !!settings.fullscreen,
|
||||
onchange: v => {
|
||||
settings.fullscreen = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
|
|
@ -61,3 +61,6 @@
|
|||
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
|
||||
0.54: Fix for invalid version comparison in polyfill
|
||||
0.55: Add toLocalISOString polyfill for pre-2v15 firmwares
|
||||
Only add boot info comments if settings.bootDebug was set
|
||||
If settings.bootDebug is set, output timing for each section of .boot0
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
/* This rewrites boot0.js based on current settings. If settings changed then it
|
||||
recalculates, but this avoids us doing a whole bunch of reconfiguration most
|
||||
of the time. */
|
||||
{ // execute in our own scope so we don't have to free variables...
|
||||
E.showMessage(/*LANG*/"Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||
var boot = "", bootPost = "";
|
||||
let s = require('Storage').readJSON('setting.json',1)||{};
|
||||
const BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
const FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||
const DEBUG = s.bootDebug; // we can set this to enable debugging output in boot0
|
||||
let boot = "", bootPost = "";
|
||||
if (DEBUG) {
|
||||
boot += "var _tm=Date.now()\n";
|
||||
bootPost += "delete _tm;";
|
||||
}
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
} else {
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
}
|
||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||
|
|
@ -88,14 +94,25 @@ delete Bangle.showClock;
|
|||
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||
delete Bangle.load;
|
||||
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||
let date = new Date();
|
||||
delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15
|
||||
if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() {
|
||||
var o = this.getTimezoneOffset();
|
||||
var d = new Date(this.getTime() - o*60000);
|
||||
var sign = o>0?"-":"+";
|
||||
o = Math.abs(o);
|
||||
return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0);
|
||||
};\n`;
|
||||
|
||||
// show timings
|
||||
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
// ================================================== BOOT.JS
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
var aPriority = a.match(getPriority);
|
||||
var bPriority = b.match(getPriority);
|
||||
let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
let getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
let aPriority = a.match(getPriority);
|
||||
let bPriority = b.match(getPriority);
|
||||
if (aPriority && bPriority){
|
||||
return parseInt(aPriority[1]) - parseInt(bPriority[1]);
|
||||
} else if (aPriority && !bPriority){
|
||||
|
|
@ -106,14 +123,16 @@ var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
|||
return a==b ? 0 : (a>b ? 1 : -1);
|
||||
});
|
||||
// precalculate file size
|
||||
var fileSize = boot.length + bootPost.length;
|
||||
let fileSize = boot.length + bootPost.length;
|
||||
bootFiles.forEach(bootFile=>{
|
||||
// match the size of data we're adding below in bootFiles.forEach
|
||||
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2;
|
||||
if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment
|
||||
fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n"
|
||||
if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
});
|
||||
// write file in chunks (so as not to use up all RAM)
|
||||
require('Storage').write('.boot0',boot,0,fileSize);
|
||||
var fileOffset = boot.length;
|
||||
let fileOffset = boot.length;
|
||||
bootFiles.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(){ ... }()
|
||||
|
|
@ -122,16 +141,18 @@ bootFiles.forEach(bootFile=>{
|
|||
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
||||
// but we need to do this without ever loading everything into RAM as some
|
||||
// boot files seem to be getting pretty big now.
|
||||
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
|
||||
fileOffset+=2+bootFile.length+1;
|
||||
var bf = require('Storage').read(bootFile);
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset);
|
||||
fileOffset+=2+bootFile.length+1;
|
||||
}
|
||||
let bf = require('Storage').read(bootFile);
|
||||
// we can't just write 'bf' in one go because at least in 2v13 and earlier
|
||||
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1
|
||||
// it can be too big (especially BTHRM).
|
||||
var bflen = bf.length;
|
||||
var bfoffset = 0;
|
||||
let bflen = bf.length;
|
||||
let bfoffset = 0;
|
||||
while (bflen) {
|
||||
var bfchunk = Math.min(bflen, 2048);
|
||||
let bfchunk = Math.min(bflen, 2048);
|
||||
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
|
||||
fileOffset+=bfchunk;
|
||||
bfoffset+=bfchunk;
|
||||
|
|
@ -139,15 +160,14 @@ bootFiles.forEach(bootFile=>{
|
|||
}
|
||||
require('Storage').write('.boot0',";\n",fileOffset);
|
||||
fileOffset+=2;
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
|
||||
fileOffset += 48+E.toJS(bootFile).length
|
||||
}
|
||||
});
|
||||
require('Storage').write('.boot0',bootPost,fileOffset);
|
||||
|
||||
delete boot;
|
||||
delete bootPost;
|
||||
delete bootFiles;
|
||||
delete fileSize;
|
||||
delete fileOffset;
|
||||
E.showMessage(/*LANG*/"Reloading...");
|
||||
eval(require('Storage').read('.boot0'));
|
||||
}
|
||||
// .bootcde should be run automatically after if required, since
|
||||
// we normally get called automatically from '.boot0'
|
||||
eval(require('Storage').read('.boot0'));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.54",
|
||||
"version": "0.55",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# BTHome Temperature and Pressure
|
||||
|
||||
This app displays temperature and pressure and advertises them over bluetooth using BTHome.io standard (along with battery level)
|
||||
|
||||
This can be used to integrate with [Home Assistant](https://www.home-assistant.io/), so you can use your Bangle as a wireless temperature/pressure sensor.
|
||||
|
||||
More info on the standard at https://bthome.io
|
||||
|
||||
And the data format used is https://bthome.io/format/
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4kA///1N6BIPf//1gMIwdE8sG2me+9Y/8C/2snXsoUNpdnzdt/xj/AH4AYgMRAAUQCyoYSCQNXs1muoFBFyHm1X//+qtwwPiMX1+YmczxP6uIwNFwN6yeDnGDmc504wNFwOpnGYC4OJweaGBsR9WTmYtBmc4GAOuC5ZGBt4SBAAQEBwf2JBcBiupnIuCmedxGTzVRC5cX1AuDnPZF4OKuIXLi3zIoedMgMzn9hC5uICQON5IDBxAXSznYC6RdDPQYXNO4JcB7pdCO56nBnGZ7p6DU5zXBXgSqDa5sAiPqIgOZd4c510RCxQXBi+pRQIXBxODzVxC5hIBvR1DnE505GMGAevzAvC/QuNGAfm1X//+qtwuOGAURq9ms11AoIWOGAQAEFw1EDBwWFggBCkUgAQMigUAAIIAJoABDCgIXQFwYXBCYYBDHAMCEAIkCFgcEAIIKCCoQFCkAhBAQIlCkAsBOoIXCBoIvEAwQTCAYI2BIwgXIF4YXDQwIVCC4YIBMIwfCAQRfGYBSPNC6TBFACgwBACouWAH4AiA="))
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// history of temperature/pressure readings
|
||||
var history = [];
|
||||
|
||||
// When we get temperature...
|
||||
function onTemperature(p) {
|
||||
// Average the last 5 temperature readings
|
||||
while (history.length>4) history.shift();
|
||||
history.push(p);
|
||||
var avrTemp = history.reduce((i,h)=>h.temperature+i,0) / history.length;
|
||||
var avrPressure = history.reduce((i,h)=>h.pressure+i,0) / history.length;
|
||||
var t = require('locale').temp(avrTemp).replace("'","°");
|
||||
// Draw
|
||||
var rect = Bangle.appRect;
|
||||
g.reset(1).clearRect(rect.x, rect.y, rect.x2, rect.y2);
|
||||
var x = (rect.x+rect.x2)/2;
|
||||
var y = (rect.y+rect.y2)/2 + 10;
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString("Temperature:", x, y - 65);
|
||||
g.setFontVector(50).setFontAlign(0,0).drawString(t, x, y-25);
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString("Pressure:", x, y+15 );
|
||||
g.setFont("12x20").setFontAlign(0,0).drawString(Math.round(avrPressure)+" hPa", x, y+40);
|
||||
// Set Bluetooth Advertising
|
||||
// https://bthome.io/format/
|
||||
var temp100 = Math.round(avrTemp*100);
|
||||
var pressure100 = Math.round(avrPressure*100);
|
||||
|
||||
Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information
|
||||
bit 0: "Encryption flag"
|
||||
bit 1-4: "Reserved for future use"
|
||||
bit 5-7: "BTHome Version" */
|
||||
|
||||
0x01, // Battery, 8 bit
|
||||
E.getBattery(),
|
||||
|
||||
0x02, // Temperature, 16 bit
|
||||
temp100&255,temp100>>8,
|
||||
|
||||
0x04, // Pressure, 16 bit
|
||||
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||
];
|
||||
NRF.setAdvertising(Bangle.bleAdvert);
|
||||
}
|
||||
|
||||
// Gets the temperature in the most accurate way with pressure sensor
|
||||
function drawTemperature() {
|
||||
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||
}
|
||||
|
||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
||||
setInterval(function() {
|
||||
drawTemperature();
|
||||
}, 10000); // update every 10s
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
back : function() {load();}
|
||||
});
|
||||
E.showMessage("Reading temperature...");
|
||||
drawTemperature();
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "bthometemp",
|
||||
"name": "BTHome Temperature and Pressure",
|
||||
"shortName":"BTHome T",
|
||||
"version":"0.01",
|
||||
"description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard",
|
||||
"icon": "app.png",
|
||||
"tags": "bthome,bluetooth,temperature",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthometemp.app.js","url":"app.js"},
|
||||
{"name":"bthometemp.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -40,3 +40,4 @@
|
|||
0.16: Set powerdownRequested correctly on BTHRM power on
|
||||
Additional logging on errors
|
||||
Add debug option for disabling active scanning
|
||||
0.17: New GUI based on layout library
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
var intervalInt;
|
||||
var intervalBt;
|
||||
const BPM_FONT_SIZE="19%";
|
||||
const VALUE_TIMEOUT=3000;
|
||||
|
||||
var BODY_LOCS = {
|
||||
0: 'Other',
|
||||
|
|
@ -7,46 +7,119 @@ var BODY_LOCS = {
|
|||
2: 'Wrist',
|
||||
3: 'Finger',
|
||||
4: 'Hand',
|
||||
5: 'Ear Lobe',
|
||||
5: 'Earlobe',
|
||||
6: 'Foot',
|
||||
};
|
||||
|
||||
var Layout = require("Layout");
|
||||
|
||||
function border(l,c) {
|
||||
g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4);
|
||||
}
|
||||
|
||||
function clear(y){
|
||||
g.reset();
|
||||
g.clearRect(0,y,g.getWidth(),y+75);
|
||||
}
|
||||
|
||||
function draw(y, type, event) {
|
||||
clear(y);
|
||||
var px = g.getWidth()/2;
|
||||
var str = event.bpm + "";
|
||||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
str = "Event: " + type;
|
||||
if (type === "HRM") {
|
||||
str += " Confidence: " + event.confidence;
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str = " Source: " + (event.src ? event.src : "internal");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
function getRow(id, text, additionalInfo){
|
||||
let additional = [];
|
||||
let l = {
|
||||
type:"h", c: [
|
||||
{
|
||||
type:"v",
|
||||
width: g.getWidth()*0.4,
|
||||
c: [
|
||||
{type:"txt", halign:1, font:"8%", label:text, id:id+"text" },
|
||||
{type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg }
|
||||
]
|
||||
},{
|
||||
type:undefined, fillx:1
|
||||
},{
|
||||
type:"v",
|
||||
valign: -1,
|
||||
width: g.getWidth()*0.45,
|
||||
c: additional
|
||||
},{
|
||||
type:undefined, width:g.getWidth()*0.05
|
||||
}
|
||||
]
|
||||
};
|
||||
for (let i of additionalInfo){
|
||||
let label = {type:"txt", font:"6x8", label:i + ":" };
|
||||
let value = {type:"txt", font:"6x8", label:"--", id:id + i };
|
||||
additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]});
|
||||
}
|
||||
if (type === "BTHRM"){
|
||||
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str= "";
|
||||
if (event.location) str += "Loc: " + BODY_LOCS[event.location];
|
||||
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
str= "";
|
||||
if (event.contact) str += " Contact: " + event.contact;
|
||||
if (event.energy) str += " kJoule: " + event.energy.toFixed(0);
|
||||
g.setFontVector(12).drawString(str,px,y+60);
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
var layout = new Layout( {
|
||||
type:"v", c: [
|
||||
getRow("int", "INT", ["Confidence"]),
|
||||
getRow("agg", "HRM", ["Confidence", "Source"]),
|
||||
getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]),
|
||||
{ type:undefined, height:8 } //dummy to protect debug output
|
||||
]
|
||||
}, {
|
||||
lazy:true
|
||||
});
|
||||
|
||||
var int,agg,bt;
|
||||
var firstEvent = true;
|
||||
|
||||
function draw(){
|
||||
if (!(int || agg || bt)) return;
|
||||
|
||||
if (firstEvent) {
|
||||
g.clearRect(Bangle.appRect);
|
||||
firstEvent = false;
|
||||
}
|
||||
|
||||
let now = Date.now();
|
||||
|
||||
if (int && int.time > (now - VALUE_TIMEOUT)){
|
||||
layout.int.label = int.bpm;
|
||||
if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence;
|
||||
} else {
|
||||
layout.int.label = "--";
|
||||
layout.intConfidence.label = "--";
|
||||
}
|
||||
|
||||
if (agg && agg.time > (now - VALUE_TIMEOUT)){
|
||||
layout.agg.label = agg.bpm;
|
||||
if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence;
|
||||
if (agg.src) layout.aggSource.label = agg.src;
|
||||
} else {
|
||||
layout.agg.label = "--";
|
||||
layout.aggConfidence.label = "--";
|
||||
layout.aggSource.label = "--";
|
||||
}
|
||||
|
||||
if (bt && bt.time > (now - VALUE_TIMEOUT)) {
|
||||
layout.bt.label = bt.bpm;
|
||||
if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%";
|
||||
if (bt.rr) layout.btRR.label = bt.rr.join(",");
|
||||
if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location];
|
||||
if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No";
|
||||
if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ";
|
||||
} else {
|
||||
layout.bt.label = "--";
|
||||
layout.btBattery.label = "--";
|
||||
layout.btRR.label = "--";
|
||||
layout.btLocation.label = "--";
|
||||
layout.btContact.label = "--";
|
||||
layout.btEnergy.label = "--";
|
||||
}
|
||||
|
||||
layout.update();
|
||||
layout.render();
|
||||
let first = true;
|
||||
for (let c of layout.l.c){
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (c.type && c.type == "h")
|
||||
border(c,g.theme.fg);
|
||||
}
|
||||
}
|
||||
|
||||
var firstEventBt = true;
|
||||
var firstEventInt = true;
|
||||
|
||||
|
||||
// This can get called for the boot code to show what's happening
|
||||
function showStatusInfo(txt) {
|
||||
|
|
@ -57,41 +130,26 @@ function showStatusInfo(txt) {
|
|||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
if (firstEventBt){
|
||||
clear(24);
|
||||
firstEventBt = false;
|
||||
}
|
||||
draw(100, "BTHRM", e);
|
||||
if (e.bpm === 0){
|
||||
Bangle.buzz(100,0.2);
|
||||
}
|
||||
if (intervalBt){
|
||||
clearInterval(intervalBt);
|
||||
}
|
||||
intervalBt = setInterval(()=>{
|
||||
clear(100);
|
||||
}, 2000);
|
||||
bt = e;
|
||||
bt.time = Date.now();
|
||||
}
|
||||
|
||||
function onHrm(e) {
|
||||
if (firstEventInt){
|
||||
clear(24);
|
||||
firstEventInt = false;
|
||||
}
|
||||
draw(24, "HRM", e);
|
||||
if (intervalInt){
|
||||
clearInterval(intervalInt);
|
||||
}
|
||||
intervalInt = setInterval(()=>{
|
||||
clear(24);
|
||||
}, 2000);
|
||||
function onInt(e) {
|
||||
int = e;
|
||||
int.time = Date.now();
|
||||
}
|
||||
|
||||
function onAgg(e) {
|
||||
agg = e;
|
||||
agg.time = Date.now();
|
||||
}
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.on('HRM', onHrm);
|
||||
Bangle.on('HRM_int', onInt);
|
||||
Bangle.on('HRM', onAgg);
|
||||
|
||||
|
||||
Bangle.setHRMPower(1,'bthrm');
|
||||
if (!(settings.startWithHrm)){
|
||||
|
|
@ -103,10 +161,11 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
if (Bangle.setBTHRMPower){
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
|
||||
setInterval(draw, 1000);
|
||||
} else {
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32);
|
||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
|
||||
}
|
||||
|
||||
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm'));
|
||||
|
|
|
|||
|
|
@ -553,14 +553,15 @@ exports.enable = () => {
|
|||
|
||||
if (settings.replace){
|
||||
// register a listener for original HRM events and emit as HRM_int
|
||||
Bangle.on("HRM", (e) => {
|
||||
e.modified = true;
|
||||
Bangle.on("HRM", (o) => {
|
||||
let e = Object.assign({},o);
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
if (fallbackActive){
|
||||
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||
log("Emitting HRM_R(int)", e);
|
||||
Bangle.emit("HRM_R", e);
|
||||
o.src = "int";
|
||||
log("Emitting HRM_R(int)", o);
|
||||
Bangle.emit("HRM_R", o);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -576,6 +577,13 @@ exports.enable = () => {
|
|||
if (name == "HRM") o("HRM_R", cb);
|
||||
else o(name, cb);
|
||||
})(Bangle.removeListener);
|
||||
} else {
|
||||
Bangle.on("HRM", (o)=>{
|
||||
o.src = "int";
|
||||
let e = Object.assign({},o);
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screen.png"}],
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth,hrm,bthrm",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -20,4 +20,13 @@
|
|||
0.20: Better handling of async data such as getPressure.
|
||||
0.21: On the default menu the week of year can be shown.
|
||||
0.22: Use the new clkinfo module for the menu.
|
||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
||||
0.24: Update clock_info to avoid a redraw
|
||||
0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16.
|
||||
ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||
0.26: Use clkinfo.addInteractive instead of a custom implementation
|
||||
0.27: Clean out some leftovers in the remove function after switching to
|
||||
clkinfo.addInteractive that would cause ReferenceError.
|
||||
0.28: Option to show (1) time only and (2) week of year.
|
||||
0.29: use setItem of clockInfoMenu to change the active item
|
||||
0.30: Use widget_utils.
|
||||
|
|
|
|||
|
|
@ -5,16 +5,12 @@ A very minimalistic clock.
|
|||
|
||||
## Features
|
||||
The BW clock implements features that are exposed by other apps through the `clkinfo` module.
|
||||
For example, if you install the HomeAssistant app, this menu item will be shown if you click right
|
||||
and additionally allows you to send triggers directly from the clock (select triggers via up/down and
|
||||
send via click center). Here are examples of other apps that are integrated:
|
||||
For example, if you install the HomeAssistant app, this menu item will be shown if you first
|
||||
touch the bottom of the screen and then swipe left/right to the home assistant menu. To select
|
||||
sub-items simply swipe up/down. To run an action (e.g. trigger home assistant), simply select the clkinfo (border) and touch on the item again. See also the screenshot below:
|
||||
|
||||
- Bangle data such as steps, heart rate, battery or charging state.
|
||||
- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
|
||||
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
|
||||
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
|
||||

|
||||
|
||||
Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
|
||||
|
||||
## Settings
|
||||
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
||||
|
|
@ -22,25 +18,6 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is
|
|||
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
|
||||
- Your bangle uses the sys color settings so you can change the color too.
|
||||
|
||||
## Menu structure
|
||||
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant.
|
||||
|
||||
Simply click left / right to go through the menu entries such as Bangle, Weather etc.
|
||||
and click up/down to move into this sub-menu. You can then click in the middle of the screen
|
||||
to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend
|
||||
on the app that provide this sub-menu through the `clkinfo` module.
|
||||
|
||||
```
|
||||
Bangle -- Agenda -- Weather -- HomeAssistant
|
||||
| | | |
|
||||
Battery Entry 1 Temperature Trigger1
|
||||
| | | |
|
||||
Steps ... ... ...
|
||||
|
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Thanks to
|
||||
- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
|
||||
- <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
|
||||
/************************************************
|
||||
* Includes
|
||||
*/
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
|
||||
const widget_utils = require("widget_utils");
|
||||
|
||||
/************************************************
|
||||
* Globals
|
||||
|
|
@ -12,8 +14,6 @@ const clock_info = require("clock_info");
|
|||
const SETTINGS_FILE = "bwclk.setting.json";
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
var lock_input = false;
|
||||
|
||||
|
||||
/************************************************
|
||||
* Settings
|
||||
|
|
@ -28,7 +28,20 @@ let settings = {
|
|||
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
settings[key] = saved_settings[key];
|
||||
}
|
||||
|
||||
let isFullscreen = function() {
|
||||
var s = settings.screen.toLowerCase();
|
||||
if(s == "dynamic"){
|
||||
return Bangle.isLocked();
|
||||
} else {
|
||||
return s == "full";
|
||||
}
|
||||
};
|
||||
|
||||
let getLineY = function(){
|
||||
return H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
}
|
||||
|
||||
/************************************************
|
||||
|
|
@ -74,32 +87,22 @@ Graphics.prototype.setMiniFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
function imgLock(){
|
||||
let imgLock = function() {
|
||||
return {
|
||||
width : 16, height : 16, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/************************************************
|
||||
* Menu
|
||||
* Clock Info
|
||||
*/
|
||||
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
|
||||
var bwItems = {
|
||||
name: null,
|
||||
img: null,
|
||||
items: [
|
||||
{ name: "WeekOfYear",
|
||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||
show: function() { bwItems.items[0].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
};
|
||||
let clockInfoItems = clock_info.load();
|
||||
|
||||
function weekOfYear() {
|
||||
// Add some custom clock-infos
|
||||
let weekOfYear = function() {
|
||||
var date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
|
|
@ -111,87 +114,98 @@ function weekOfYear() {
|
|||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
}
|
||||
|
||||
clockInfoItems[0].items.unshift({ name : "weekofyear",
|
||||
get : function() { return { text : "Week " + weekOfYear(),
|
||||
img : null}},
|
||||
show : function() {},
|
||||
hide : function() {},
|
||||
})
|
||||
|
||||
// Load menu
|
||||
var menu = clock_info.load();
|
||||
menu = menu.concat(bwItems);
|
||||
// Empty for large time
|
||||
clockInfoItems[0].items.unshift({ name : "nop",
|
||||
get : function() { return { text : null,
|
||||
img : null}},
|
||||
show : function() {},
|
||||
hide : function() {},
|
||||
})
|
||||
|
||||
|
||||
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
|
||||
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 0;
|
||||
}
|
||||
|
||||
// Set draw functions for each item
|
||||
menu.forEach((menuItm, x) => {
|
||||
menuItm.items.forEach((item, y) => {
|
||||
function drawItem() {
|
||||
// For the clock, we have a special case, as we don't wanna redraw
|
||||
// immediately when something changes. Instead, we update data each minute
|
||||
// to save some battery etc. Therefore, we hide (and disable the listener)
|
||||
// immedeately after redraw...
|
||||
item.hide();
|
||||
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||
x : 0,
|
||||
y: 135,
|
||||
w: W,
|
||||
h: H-135,
|
||||
draw : (itm, info, options) => {
|
||||
var hideClkInfo = info.text == null;
|
||||
|
||||
// After drawing the item, we enable inputs again...
|
||||
lock_input = false;
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||
|
||||
var info = item.get();
|
||||
drawMenuItem(info.text, info.img);
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.bg);
|
||||
|
||||
if (options.focus){
|
||||
var y = hideClkInfo ? options.y+20 : options.y+2;
|
||||
var h = hideClkInfo ? options.h-20 : options.h-2;
|
||||
g.drawRect(options.x, y, options.x+options.w-2, y+h-1); // show if focused
|
||||
g.drawRect(options.x+1, y+1, options.x+options.w-3, y+h-2); // show if focused
|
||||
}
|
||||
|
||||
item.on('redraw', drawItem);
|
||||
})
|
||||
// In case we hide the clkinfo, we show the time again as the time should
|
||||
// be drawn larger.
|
||||
if(hideClkInfo){
|
||||
drawTime();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set text and font
|
||||
var image = info.img;
|
||||
var text = String(info.text);
|
||||
if(text.split('\n').length > 1){
|
||||
g.setMiniFont();
|
||||
} else {
|
||||
g.setSmallFont();
|
||||
}
|
||||
|
||||
// Compute sizes
|
||||
var strWidth = g.stringWidth(text);
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var midx = options.x+options.w/2;
|
||||
|
||||
// Draw
|
||||
if (image) {
|
||||
var scale = imgWidth / image.width;
|
||||
g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale});
|
||||
}
|
||||
g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20);
|
||||
|
||||
// In case we are in focus and the focus box changes (fullscreen yes/no)
|
||||
// we draw the time again. Otherwise it could happen that a while line is
|
||||
// not cleared correctly.
|
||||
if(options.focus) drawTime();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function canRunMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
return item.run !== undefined;
|
||||
}
|
||||
|
||||
|
||||
function runMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
try{
|
||||
var ret = item.run();
|
||||
if(ret){
|
||||
Bangle.buzz(300, 0.6);
|
||||
}
|
||||
} catch (ex) {
|
||||
// Simply ignore it...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************************************
|
||||
* Draw
|
||||
*/
|
||||
function draw() {
|
||||
let draw = function() {
|
||||
// Queue draw again
|
||||
queueDraw();
|
||||
|
||||
// Draw clock
|
||||
drawDate();
|
||||
drawMenuAndTime();
|
||||
drawTime();
|
||||
drawLock();
|
||||
drawWidgets();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function drawDate(){
|
||||
let drawDate = function() {
|
||||
// Draw background
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
var y = getLineY()
|
||||
g.reset().clearRect(0,0,W,y);
|
||||
|
||||
// Draw date
|
||||
|
|
@ -216,17 +230,17 @@ function drawDate(){
|
|||
g.setMediumFont();
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function drawTime(y, smallText){
|
||||
let drawTime = function() {
|
||||
var hideClkInfo = clockInfoMenu.menuA == 0 && clockInfoMenu.menuB == 0;
|
||||
|
||||
// Draw background
|
||||
var y1 = getLineY();
|
||||
var y = y1;
|
||||
var date = new Date();
|
||||
|
||||
// Draw time
|
||||
g.setColor(g.theme.bg);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
var hours = String(date.getHours());
|
||||
var minutes = date.getMinutes();
|
||||
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
||||
|
|
@ -236,212 +250,93 @@ function drawTime(y, smallText){
|
|||
// Set y coordinates correctly
|
||||
y += parseInt((H - y)/2) + 5;
|
||||
|
||||
// Show large or small time depending on info entry
|
||||
if(smallText){
|
||||
if (hideClkInfo){
|
||||
g.setLargeFont();
|
||||
} else {
|
||||
y -= 15;
|
||||
g.setMediumFont();
|
||||
} else {
|
||||
g.setLargeFont();
|
||||
}
|
||||
g.drawString(timeStr, W/2, y);
|
||||
}
|
||||
|
||||
function drawMenuItem(text, image){
|
||||
// First clear the time region
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
|
||||
// Clear region and draw time
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(0,y,W,H);
|
||||
g.fillRect(0,y1,W,y+20 + (hideClkInfo ? 1 : 0) + (isFullscreen() ? 3 : 0));
|
||||
|
||||
// Draw menu text
|
||||
var hasText = (text != null && text != "");
|
||||
if(hasText){
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
// For multiline text we show an even smaller font...
|
||||
text = String(text);
|
||||
if(text.split('\n').length > 1){
|
||||
g.setMiniFont();
|
||||
} else {
|
||||
g.setSmallFont();
|
||||
}
|
||||
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var strWidth = g.stringWidth(text);
|
||||
g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
|
||||
g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
|
||||
|
||||
if(image != null){
|
||||
var scale = imgWidth / image.width;
|
||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
|
||||
}
|
||||
}
|
||||
|
||||
// Draw time
|
||||
drawTime(y, hasText);
|
||||
}
|
||||
g.setColor(g.theme.bg);
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString(timeStr, W/2, y);
|
||||
};
|
||||
|
||||
|
||||
function drawMenuAndTime(){
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
|
||||
// The first entry is the overview...
|
||||
if(settings.menuPosY == 0){
|
||||
drawMenuItem(menuEntry.name, menuEntry.img);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw item if needed
|
||||
lock_input = true;
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
item.show();
|
||||
}
|
||||
|
||||
|
||||
function drawLock(){
|
||||
let drawLock = function() {
|
||||
if(settings.showLock && Bangle.isLocked()){
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawImage(imgLock(), W-16, 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function drawWidgets(){
|
||||
let drawWidgets = function() {
|
||||
if(isFullscreen()){
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
widget_utils.hide();
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isFullscreen(){
|
||||
var s = settings.screen.toLowerCase();
|
||||
if(s == "dynamic"){
|
||||
return Bangle.isLocked()
|
||||
} else {
|
||||
return s == "full"
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/************************************************
|
||||
* Listener
|
||||
*/
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
let drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
let queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
let lcdListenerBw = function(on) {
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
};
|
||||
Bangle.on('lcdPower', lcdListenerBw);
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
let lockListenerBw = function(isLocked) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
|
||||
// If we have to show the widgets again, we load it from our
|
||||
// cache and not through Bangle.loadWidgets as its much faster!
|
||||
for (let wd of WIDGETS) {wd.draw=wd._draw;wd.area=wd._area;}
|
||||
widget_utils.show();
|
||||
}
|
||||
|
||||
draw();
|
||||
});
|
||||
|
||||
Bangle.on('charging',function(charging) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
};
|
||||
Bangle.on('lock', lockListenerBw);
|
||||
|
||||
let charging = function(charging){
|
||||
// Jump to battery
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 1;
|
||||
draw();
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(btn, e){
|
||||
var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
|
||||
var left = parseInt(g.getWidth() * 0.22);
|
||||
var right = g.getWidth() - left;
|
||||
var upper = parseInt(g.getHeight() * 0.22) + widget_size;
|
||||
var lower = g.getHeight() - upper;
|
||||
|
||||
var is_upper = e.y < upper;
|
||||
var is_lower = e.y > lower;
|
||||
var is_left = e.x < left && !is_upper && !is_lower;
|
||||
var is_right = e.x > right && !is_upper && !is_lower;
|
||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
||||
|
||||
if(lock_input){
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_lower){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
||||
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_upper){
|
||||
if(e.y < widget_size){
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = settings.menuPosY-1;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
||||
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_right){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
||||
settings.menuPosY = 0;
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_left){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = 0;
|
||||
settings.menuPosX = settings.menuPosX-1;
|
||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_center){
|
||||
if(canRunMenuItem()){
|
||||
runMenuItem();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
E.on("kill", function(){
|
||||
try{
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
} catch(ex){
|
||||
// If this fails, we still kill the app...
|
||||
}
|
||||
});
|
||||
clockInfoMenu.setItem(0, 2);
|
||||
drawTime();
|
||||
}
|
||||
Bangle.on('charging', charging);
|
||||
|
||||
let kill = function(){
|
||||
clockInfoMenu.remove();
|
||||
delete clockInfoMenu;
|
||||
};
|
||||
E.on("kill", kill);
|
||||
|
||||
/************************************************
|
||||
* Startup Clock
|
||||
|
|
@ -450,17 +345,31 @@ E.on("kill", function(){
|
|||
// The upper part is inverse i.e. light if dark and dark if light theme
|
||||
// is enabled. In order to draw the widgets correctly, we invert the
|
||||
// dark/light theme as well as the colors.
|
||||
let themeBackup = g.theme;
|
||||
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
Bangle.removeListener('lcdPower', lcdListenerBw);
|
||||
Bangle.removeListener('lock', lockListenerBw);
|
||||
Bangle.removeListener('charging', charging);
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
// save settings
|
||||
kill();
|
||||
E.removeListener("kill", kill);
|
||||
g.setTheme(themeBackup);
|
||||
widget_utils.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Load widgets and draw clock the first time
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Cache draw function for dynamic screen to hide / show widgets
|
||||
// Bangle.loadWidgets() could also be called later on but its much slower!
|
||||
for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
|
||||
|
||||
// Draw first time
|
||||
draw();
|
||||
|
||||
} // End of app scope
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.23",
|
||||
"description": "A very minimalistic clock to mainly show date and time.",
|
||||
"version": "0.30",
|
||||
"description": "A very minimalistic clock.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Tell clock widgets to hide.
|
||||
0.04: Improve current time readability in light theme.
|
||||
0.05: Show calendar colors & improved all day events.
|
||||
0.06: Improved multi-line locations & titles
|
||||
|
|
|
|||
|
|
@ -54,13 +54,15 @@ function drawEventBody(event, y) {
|
|||
var yStart = y;
|
||||
if (lines.length > 2) {
|
||||
lines = lines.slice(0,2);
|
||||
lines[1] = lines[1].slice(0,-3)+"...";
|
||||
lines[1] += "...";
|
||||
}
|
||||
g.drawString(lines.join('\n'),10,y);
|
||||
y+=20 * lines.length;
|
||||
if(event.location) {
|
||||
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y);
|
||||
g.drawString(event.location,25,y);
|
||||
var loclines = g.wrapString(event.location, g.getWidth()-30);
|
||||
if(loclines.length>1) loclines[0] += "...";
|
||||
g.drawString(loclines[0],25,y);
|
||||
y+=20;
|
||||
}
|
||||
if (event.color) {
|
||||
|
|
@ -131,4 +133,3 @@ var minuteInterval = setInterval(redraw, 60 * 1000);
|
|||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "calclock",
|
||||
"name": "Calendar Clock",
|
||||
"shortName": "CalClock",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
||||
"icon": "calclock.png",
|
||||
"type": "clock",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
0.01: New App!
|
||||
0.02: Support Bangle.js 2
|
||||
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
||||
0.04: Combine code for both apps
|
||||
Better colors for Bangle.js 2
|
||||
Fix selection animation for Bangle.js 2
|
||||
New icon
|
||||
Slightly wider arc segments for better visibility
|
||||
Extract arc drawing code in library
|
||||
|
|
|
|||
|
|
@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
|
|||
of players, ensure that each person knows which colour represents them, and then
|
||||
choose a segment. After a short animation, the chosen segment will fill the screen.
|
||||
|
||||
You can use Choozi to randomly select an element from any set with 2 to 13 members,
|
||||
You can use Choozi to randomly select an element from any set with 2 to 15 members,
|
||||
as long as you can define a bijection between members of the set and coloured
|
||||
segments on the Bangle.js display.
|
||||
|
||||
## Controls
|
||||
## Controls Bangle 1
|
||||
|
||||
BTN1: increase the number of segments
|
||||
BTN2: choose a segment at random
|
||||
BTN3: decrease the number of segments
|
||||
|
||||
## Controls Bangle 2
|
||||
|
||||
Swipe up/down: increase/decrease the number of segments
|
||||
BTN1 or tap: choose a segment at random
|
||||
|
||||
## Creator
|
||||
|
||||
James Stanley
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
||||
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
|
||||
|
|
@ -4,15 +4,16 @@
|
|||
*
|
||||
* James Stanley 2021
|
||||
*/
|
||||
|
||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||
const GU = require("graphics_utils");
|
||||
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
|
||||
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
|
||||
|
||||
var stepAngle = 0.18; // radians - resolution of polygon
|
||||
var gapAngle = 0.035; // radians - gap between segments
|
||||
var perimMin = 110; // px - min. radius of perimeter
|
||||
var perimMax = 120; // px - max. radius of perimeter
|
||||
var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
|
||||
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 106; // px - max radius of filled-in segment
|
||||
var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
|
||||
var segmentStep = 5; // px - step size of segment fill animation
|
||||
var circleStep = 4; // px - step size of circle fill animation
|
||||
|
||||
|
|
@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
|
|||
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||
var ballSize = 3; // px - ball radius
|
||||
var ballTrack = 100; // px - radius of ball path
|
||||
var ballTrack = perimMin - ballSize*2; // px - radius of ball path
|
||||
|
||||
var centreX = 120; // px - centre of screen
|
||||
var centreY = 120; // px - centre of screen
|
||||
var centreX = g.getWidth()*0.5; // px - centre of screen
|
||||
var centreY = g.getWidth()*0.5; // px - centre of screen
|
||||
|
||||
var fontSize = 50; // px
|
||||
|
||||
|
|
@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
|
|||
|
||||
var defaultN = 3; // default value for N
|
||||
var minN = 2;
|
||||
var maxN = colours.length;
|
||||
var N;
|
||||
var arclen;
|
||||
|
||||
|
|
@ -51,42 +51,14 @@ function shuffle (array) {
|
|||
}
|
||||
}
|
||||
|
||||
// draw an arc between radii minR and maxR, and between
|
||||
// angles minAngle and maxAngle
|
||||
function arc(minR, maxR, minAngle, maxAngle) {
|
||||
var step = stepAngle;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
var c, s;
|
||||
while (angle < maxAngle) {
|
||||
c = Math.cos(angle);
|
||||
s = Math.sin(angle);
|
||||
inside.push(centreX+c*minR); // x
|
||||
inside.push(centreY+s*minR); // y
|
||||
// outside coordinates are built up in reverse order
|
||||
outside.unshift(centreY+s*maxR); // y
|
||||
outside.unshift(centreX+c*maxR); // x
|
||||
angle += step;
|
||||
}
|
||||
c = Math.cos(maxAngle);
|
||||
s = Math.sin(maxAngle);
|
||||
inside.push(centreX+c*minR);
|
||||
inside.push(centreY+s*minR);
|
||||
outside.unshift(centreY+s*maxR);
|
||||
outside.unshift(centreX+c*maxR);
|
||||
|
||||
var vertices = inside.concat(outside);
|
||||
g.fillPoly(vertices, true);
|
||||
}
|
||||
|
||||
// draw the arc segments around the perimeter
|
||||
function drawPerimeter() {
|
||||
g.setBgColor('#000000');
|
||||
g.clear();
|
||||
for (var i = 0; i < N; i++) {
|
||||
g.setColor(colours[i%colours.length]);
|
||||
var minAngle = (i/N)*radians;
|
||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
||||
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +103,7 @@ function animateChoice(target) {
|
|||
g.fillCircle(x, y, ballSize);
|
||||
oldx=x;
|
||||
oldy=y;
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,11 +114,15 @@ function choose() {
|
|||
var maxAngle = minAngle + arclen;
|
||||
animateChoice((minAngle+maxAngle)/2);
|
||||
g.setColor(colours[chosen%colours.length]);
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
||||
arc(i, perimMax, minAngle, maxAngle);
|
||||
arc(0, perimMax, minAngle, maxAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep)
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
|
||||
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep){
|
||||
g.fillCircle(centreX,centreY,r);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
g.fillCircle(centreX,centreY,segmentMax);
|
||||
}
|
||||
|
||||
|
|
@ -171,38 +148,47 @@ function setN(n) {
|
|||
drawPerimeter();
|
||||
}
|
||||
|
||||
// save N to choozi.txt
|
||||
// save N to choozi.save
|
||||
function writeN() {
|
||||
var file = require("Storage").open("choozi.txt","w");
|
||||
file.write(N);
|
||||
var savedN = read();
|
||||
if (savedN != N) require("Storage").write("choozi.save","" + N);
|
||||
}
|
||||
|
||||
// load N from choozi.txt
|
||||
function read(){
|
||||
var n = require("Storage").read("choozi.save");
|
||||
if (n !== undefined) return parseInt(n);
|
||||
return defaultN;
|
||||
}
|
||||
|
||||
// load N from choozi.save
|
||||
function readN() {
|
||||
var file = require("Storage").open("choozi.txt","r");
|
||||
var n = file.readLine();
|
||||
if (n !== undefined) setN(parseInt(n));
|
||||
else setN(defaultN);
|
||||
setN(read());
|
||||
}
|
||||
|
||||
shuffle(colours); // is this really best?
|
||||
Bangle.setLCDMode("direct");
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
if (process.env.HWVERSION == 1){
|
||||
colours=colours.concat(colours2);
|
||||
shuffle(colours);
|
||||
} else {
|
||||
shuffle(colours);
|
||||
shuffle(colours2);
|
||||
colours=colours.concat(colours2);
|
||||
}
|
||||
|
||||
var maxN = colours.length;
|
||||
if (process.env.HWVERSION == 1){
|
||||
Bangle.setLCDMode("direct");
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
}
|
||||
readN();
|
||||
drawN();
|
||||
|
||||
setWatch(() => {
|
||||
setN(N+1);
|
||||
drawN();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
}, BTN2, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
setN(N-1);
|
||||
drawN();
|
||||
}, BTN3, {repeat:true});
|
||||
Bangle.setUI("updown", (v)=>{
|
||||
if (!v){
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
} else {
|
||||
setN(N-v);
|
||||
drawN();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 551 B |
|
|
@ -1,207 +0,0 @@
|
|||
/* Choozi - Choose people or things at random using Bangle.js.
|
||||
* Inspired by the "Chwazi" Android app
|
||||
*
|
||||
* James Stanley 2021
|
||||
*/
|
||||
|
||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||
|
||||
var stepAngle = 0.18; // radians - resolution of polygon
|
||||
var gapAngle = 0.035; // radians - gap between segments
|
||||
var perimMin = 80; // px - min. radius of perimeter
|
||||
var perimMax = 87; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 70; // px - max radius of filled-in segment
|
||||
var segmentStep = 5; // px - step size of segment fill animation
|
||||
var circleStep = 4; // px - step size of circle fill animation
|
||||
|
||||
// rolling ball animation:
|
||||
var maxSpeed = 0.08; // rad/sec
|
||||
var minSpeed = 0.001; // rad/sec
|
||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||
var ballSize = 3; // px - ball radius
|
||||
var ballTrack = 75; // px - radius of ball path
|
||||
|
||||
var centreX = 88; // px - centre of screen
|
||||
var centreY = 88; // px - centre of screen
|
||||
|
||||
var fontSize = 50; // px
|
||||
|
||||
var radians = 2*Math.PI; // radians per circle
|
||||
|
||||
var defaultN = 3; // default value for N
|
||||
var minN = 2;
|
||||
var maxN = colours.length;
|
||||
var N;
|
||||
var arclen;
|
||||
|
||||
// https://www.frankmitchell.org/2015/01/fisher-yates/
|
||||
function shuffle (array) {
|
||||
var i = 0
|
||||
, j = 0
|
||||
, temp = null;
|
||||
|
||||
for (i = array.length - 1; i > 0; i -= 1) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
// draw an arc between radii minR and maxR, and between
|
||||
// angles minAngle and maxAngle
|
||||
function arc(minR, maxR, minAngle, maxAngle) {
|
||||
var step = stepAngle;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
var c, s;
|
||||
while (angle < maxAngle) {
|
||||
c = Math.cos(angle);
|
||||
s = Math.sin(angle);
|
||||
inside.push(centreX+c*minR); // x
|
||||
inside.push(centreY+s*minR); // y
|
||||
// outside coordinates are built up in reverse order
|
||||
outside.unshift(centreY+s*maxR); // y
|
||||
outside.unshift(centreX+c*maxR); // x
|
||||
angle += step;
|
||||
}
|
||||
c = Math.cos(maxAngle);
|
||||
s = Math.sin(maxAngle);
|
||||
inside.push(centreX+c*minR);
|
||||
inside.push(centreY+s*minR);
|
||||
outside.unshift(centreY+s*maxR);
|
||||
outside.unshift(centreX+c*maxR);
|
||||
|
||||
var vertices = inside.concat(outside);
|
||||
g.fillPoly(vertices, true);
|
||||
}
|
||||
|
||||
// draw the arc segments around the perimeter
|
||||
function drawPerimeter() {
|
||||
g.clear();
|
||||
for (var i = 0; i < N; i++) {
|
||||
g.setColor(colours[i%colours.length]);
|
||||
var minAngle = (i/N)*radians;
|
||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
||||
}
|
||||
}
|
||||
|
||||
// animate a ball rolling around and settling at "target" radians
|
||||
function animateChoice(target) {
|
||||
var angle = 0;
|
||||
var speed = 0;
|
||||
var oldx = -10;
|
||||
var oldy = -10;
|
||||
var decelFromAngle = -1;
|
||||
var allowDecel = false;
|
||||
for (var i = 0; true; i++) {
|
||||
angle = angle + speed;
|
||||
if (angle > radians) angle -= radians;
|
||||
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
|
||||
speed = speed + accel;
|
||||
if (speed > maxSpeed) {
|
||||
speed = maxSpeed;
|
||||
/* when we reach max speed, we know how long it takes
|
||||
* to accelerate, and therefore how long to decelerate, so
|
||||
* we can work out what angle to start decelerating from */
|
||||
if (decelFromAngle < 0) {
|
||||
decelFromAngle = target-angle;
|
||||
while (decelFromAngle < 0) decelFromAngle += radians;
|
||||
while (decelFromAngle > radians) decelFromAngle -= radians;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
|
||||
if (allowDecel) speed = speed - accel;
|
||||
if (speed < minSpeed) speed = minSpeed;
|
||||
if (speed == minSpeed && angle < target && angle+speed >= target) return;
|
||||
}
|
||||
|
||||
var r = i/2;
|
||||
if (r > ballTrack) r = ballTrack;
|
||||
var x = centreX+Math.cos(angle)*r;
|
||||
var y = centreY+Math.sin(angle)*r;
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(oldx,oldy,ballSize+1);
|
||||
g.setColor('#ffffff');
|
||||
g.fillCircle(x, y, ballSize);
|
||||
oldx=x;
|
||||
oldy=y;
|
||||
g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
// choose a winning segment and animate its selection
|
||||
function choose() {
|
||||
var chosen = Math.floor(Math.random()*N);
|
||||
var minAngle = (chosen/N)*radians;
|
||||
var maxAngle = minAngle + arclen;
|
||||
animateChoice((minAngle+maxAngle)/2);
|
||||
g.setColor(colours[chosen%colours.length]);
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
||||
arc(i, perimMax, minAngle, maxAngle);
|
||||
arc(0, perimMax, minAngle, maxAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep)
|
||||
g.fillCircle(centreX,centreY,r);
|
||||
g.fillCircle(centreX,centreY,segmentMax);
|
||||
}
|
||||
|
||||
// draw the current value of N in the middle of the screen, with
|
||||
// up/down arrows
|
||||
function drawN() {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector",fontSize);
|
||||
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
|
||||
if (N < maxN)
|
||||
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
|
||||
if (N > minN)
|
||||
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
|
||||
}
|
||||
|
||||
// update number of segments, with min/max limit, "arclen" update,
|
||||
// and screen reset
|
||||
function setN(n) {
|
||||
N = n;
|
||||
if (N < minN) N = minN;
|
||||
if (N > maxN) N = maxN;
|
||||
arclen = radians/N - gapAngle;
|
||||
drawPerimeter();
|
||||
}
|
||||
|
||||
// save N to choozi.txt
|
||||
function writeN() {
|
||||
var file = require("Storage").open("choozi.txt","w");
|
||||
file.write(N);
|
||||
}
|
||||
|
||||
// load N from choozi.txt
|
||||
function readN() {
|
||||
var file = require("Storage").open("choozi.txt","r");
|
||||
var n = file.readLine();
|
||||
if (n !== undefined) setN(parseInt(n));
|
||||
else setN(defaultN);
|
||||
}
|
||||
|
||||
shuffle(colours); // is this really best?
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
readN();
|
||||
drawN();
|
||||
|
||||
setWatch(() => {
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
Bangle.on('touch', function(zone,e) {
|
||||
if(e.x>+88){
|
||||
setN(N-1);
|
||||
drawN();
|
||||
}else{
|
||||
setN(N+1);
|
||||
drawN();
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "choozi",
|
||||
"name": "Choozi",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Choose people or things at random using Bangle.js.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
|
|
@ -10,8 +10,10 @@
|
|||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||
"storage": [
|
||||
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
|
||||
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"choozi.app.js","url":"app.js"},
|
||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"choozi.save"}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,3 +32,12 @@
|
|||
Use widget_utils if available
|
||||
0.17: Load circles from clkinfo
|
||||
0.18: Improved clkinfo handling and using it for the weather circle
|
||||
0.19: Remove old code and fixing clkinfo handling (fix HRM and other items that change)
|
||||
Remove settings for what is displayed and instead allow circles to be changed by swiping
|
||||
0.20: Add much faster circle rendering (250ms -> 40ms)
|
||||
Add fast load capability
|
||||
0.21: Remade all icons without a palette for dark theme
|
||||
Now re-adds widgets if they were hidden when fast-loading
|
||||
0.22: Fixed crash if item has no image and cutting long overflowing text
|
||||
0.23: Setting circles colours per clkinfo and not position
|
||||
0.24: Using suggested color from clock_info if set as default and available
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ A clock with three or four circles for different data at the bottom in a probabl
|
|||
By default the time, date and day of week is shown.
|
||||
|
||||
It can show the following information (this can be configured):
|
||||
|
||||
* Steps
|
||||
* Steps distance
|
||||
* Heart rate (automatically updates when screen is on and unlocked)
|
||||
|
|
@ -14,15 +15,24 @@ It can show the following information (this can be configured):
|
|||
* Temperature inside circle
|
||||
* Condition as icon below circle
|
||||
* Big weather icon next to clock
|
||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||
* Temperature, air pressure or altitude from internal pressure sensor
|
||||
* Altitude from internal pressure sensor
|
||||
* Active alarms (if `Alarm` app installed)
|
||||
* Sunrise or sunset (if `Sunrise Clockinfo` app installed)
|
||||
|
||||
To change what is shown:
|
||||
|
||||
The color of each circle can be configured. The following colors are available:
|
||||
* Unlock the watch
|
||||
* Tap on the circle to change (a border is drawn around it)
|
||||
* Swipe up/down to change the guage within the given group
|
||||
* Swipe left/right to change the group (eg. between standard Bangle.js and Alarms/etc)
|
||||
|
||||
Data is provided by ['Clock Info'](http://www.espruino.com/Bangle.js+Clock+Info)
|
||||
so any apps that implement this feature can add extra information to be displayed.
|
||||
|
||||
The color of each circle can be configured from `Settings -> Apps -> Circles Clock`. The following colors are available:
|
||||
* Basic colors (red, green, blue, yellow, magenta, cyan, black, white)
|
||||
* Color depending on value (green -> red, red -> green)
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
|
@ -38,5 +48,5 @@ The color of each circle can be configured. The following colors are available:
|
|||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from
|
||||
Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from
|
||||
[icons8](https://icons8.com/icon/set/weather/small--static--black)
|
||||
|
|
|
|||
|
|
@ -3,27 +3,18 @@
|
|||
"showWidgets": false,
|
||||
"weatherCircleData": "humidity",
|
||||
"circleCount": 3,
|
||||
"circle1": "Bangle/HRM",
|
||||
"circle2": "Bangle/Steps",
|
||||
"circle3": "Bangle/Battery",
|
||||
"circle4": "weather",
|
||||
"circle1color": "green-red",
|
||||
"circle2color": "#0000ff",
|
||||
"circle3color": "red-green",
|
||||
"circle4color": "#ffff00",
|
||||
"Bangle/Battery_color":"red-green",
|
||||
"Bangle/Steps_color":"#0000ff",
|
||||
"Bangle/HRM_color":"green-red",
|
||||
"Bangle/Altitude_color":"#00ff00",
|
||||
"Weather/humidity_color":"#00ffff",
|
||||
"Weather/wind_color":"fg",
|
||||
"Weather/temperature_color":"blue-red",
|
||||
"Alarms_color":"#00ff00",
|
||||
"circle1colorizeIcon": true,
|
||||
"circle2colorizeIcon": true,
|
||||
"circle3colorizeIcon": true,
|
||||
"circle4colorizeIcon": false,
|
||||
"updateInterval": 60,
|
||||
"showBigWeather": false,
|
||||
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"hrmValidity": 60
|
||||
|
||||
"showBigWeather": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.18",
|
||||
"version":"0.24",
|
||||
"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"}],
|
||||
|
|
|
|||
|
|
@ -12,29 +12,10 @@
|
|||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
//const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"];
|
||||
//const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"];
|
||||
var valuesCircleTypes = ["empty","weather", "sunprogress"];
|
||||
var namesCircleTypes = ["empty","weather", "sun"];
|
||||
clock_info.load().forEach(e=>{
|
||||
if(e.dynamic) {
|
||||
valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]);
|
||||
namesCircleTypes = namesCircleTypes.concat([e.name]);
|
||||
} else {
|
||||
let values = e.items.map(i=>e.name+"/"+i.name);
|
||||
let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values;
|
||||
valuesCircleTypes = valuesCircleTypes.concat(values);
|
||||
namesCircleTypes = namesCircleTypes.concat(names);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
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"];
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "blue-red", "red-blue", "fg"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
|
||||
"cyan", "white", "black", "green->red", "red->green", "blue->red", "red->blue", "foreground"];
|
||||
|
||||
function showMainMenu() {
|
||||
let menu ={
|
||||
|
|
@ -47,31 +28,11 @@
|
|||
step: 1,
|
||||
onchange: x => save('circleCount', x),
|
||||
},
|
||||
/*LANG*/'circle 1': ()=>showCircleMenu(1),
|
||||
/*LANG*/'circle 2': ()=>showCircleMenu(2),
|
||||
/*LANG*/'circle 3': ()=>showCircleMenu(3),
|
||||
/*LANG*/'circle 4': ()=>showCircleMenu(4),
|
||||
/*LANG*/'battery warn': {
|
||||
value: settings.batteryWarn,
|
||||
min: 10,
|
||||
max : 100,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x + '%';
|
||||
},
|
||||
onchange: x => save('batteryWarn', x),
|
||||
},
|
||||
/*LANG*/'show widgets': {
|
||||
value: !!settings.showWidgets,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: x => save('showWidgets', x),
|
||||
},
|
||||
/*LANG*/'weather data': {
|
||||
value: weatherData.indexOf(settings.weatherCircleData),
|
||||
min: 0, max: 2,
|
||||
format: v => weatherData[v],
|
||||
onchange: x => save('weatherCircleData', weatherData[x]),
|
||||
},
|
||||
/*LANG*/'update interval': {
|
||||
value: settings.updateInterval,
|
||||
min: 0,
|
||||
|
|
@ -82,47 +43,54 @@
|
|||
},
|
||||
onchange: x => save('updateInterval', x),
|
||||
},
|
||||
//TODO deprecated local icons, may disappear in future
|
||||
/*LANG*/'legacy weather icons': {
|
||||
value: !!settings.legacyWeatherIcons,
|
||||
format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
|
||||
onchange: x => save('legacyWeatherIcons', x),
|
||||
},
|
||||
/*LANG*/'show big weather': {
|
||||
value: !!settings.showBigWeather,
|
||||
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||
onchange: x => save('showBigWeather', x),
|
||||
}
|
||||
},
|
||||
/*LANG*/'colorize icons': ()=>showCircleMenus()
|
||||
};
|
||||
clock_info.load().forEach(e=>{
|
||||
if(e.dynamic) {
|
||||
const colorKey = e.name + "_color";
|
||||
menu[e.name+/*LANG*/' color'] = {
|
||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||
min: 0, max: valuesColors.length - 1,
|
||||
format: v => namesColors[v],
|
||||
onchange: x => save(colorKey, valuesColors[x]),
|
||||
};
|
||||
} else {
|
||||
let values = e.items.map(i=>e.name+"/"+i.name);
|
||||
let names = e.name=="Bangle" ? e.items.map(i=>i.name) : values;
|
||||
values.forEach((v,i)=>{
|
||||
const colorKey = v + "_color";
|
||||
menu[names[i]+/*LANG*/' color'] = {
|
||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||
min: 0, max: valuesColors.length - 1,
|
||||
format: v => namesColors[v],
|
||||
onchange: x => save(colorKey, valuesColors[x]),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showCircleMenu(circleId) {
|
||||
const circleName = "circle" + circleId;
|
||||
const colorKey = circleName + "color";
|
||||
const colorizeIconKey = circleName + "colorizeIcon";
|
||||
|
||||
const menu = {
|
||||
'': { 'title': /*LANG*/'Circle ' + circleId },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'data': {
|
||||
value: valuesCircleTypes.indexOf(settings[circleName]),
|
||||
min: 0, max: valuesCircleTypes.length - 1,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save(circleName, valuesCircleTypes[x]),
|
||||
},
|
||||
/*LANG*/'color': {
|
||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||
min: 0, max: valuesColors.length - 1,
|
||||
format: v => namesColors[v],
|
||||
onchange: x => save(colorKey, valuesColors[x]),
|
||||
},
|
||||
/*LANG*/'colorize icon': {
|
||||
function showCircleMenus() {
|
||||
const menu = {
|
||||
'': { 'title': /*LANG*/'Colorize icons'},
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
};
|
||||
for(var circleId=1; circleId<=4; ++circleId) {
|
||||
const circleName = "circle" + circleId;
|
||||
const colorKey = circleName + "color";
|
||||
const colorizeIconKey = circleName + "colorizeIcon";
|
||||
menu[/*LANG*/'circle ' + circleId] = {
|
||||
value: settings[colorizeIconKey] || false,
|
||||
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
|
||||
onchange: x => save(colorizeIconKey, x),
|
||||
},
|
||||
};
|
||||
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
|
||||
onchange: x => save(colorizeIconKey, x),
|
||||
};
|
||||
}
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
After Width: | Height: | Size: 901 B |