Merge branch 'master' of github.com:ff2005/BangleApps into feature/nifty-clock-a

master
Filipe Fradique 2021-10-16 00:01:02 +01:00
commit dad29a9fd7
30 changed files with 451 additions and 45 deletions

View File

@ -13,6 +13,21 @@
],
"sortorder" : -10
},
{ "id": "health",
"name": "Health Tracking",
"tags": "tool,system,b2",
"icon": "app.png",
"version":"0.01",
"description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)",
"readme": "README.md",
"storage": [
{"name":"health.app.js","url":"app.js"},
{"name":"health.img","url":"app-icon.js","evaluate":true},
{"name":"health.boot.js","url":"boot.js"},
{"name":"health","url":"lib.js"}
],
"sortorder" : -10
},
{ "id": "moonphase",
"name": "Moonphase",
"icon": "app.png",
@ -55,7 +70,7 @@
"name": "Launcher (Bangle.js 2)",
"shortName":"Launcher",
"icon": "app.png",
"version":"0.02",
"version":"0.03",
"description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.",
"tags": "tool,system,launcher,b2,bno1",
"type":"launch",
@ -67,7 +82,7 @@
{ "id": "about",
"name": "About",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
"tags": "tool,system,b2",
"allow_emulator":true,
@ -500,9 +515,9 @@
{ "id": "speedo",
"name": "Speedo",
"icon": "speedo.png",
"version":"0.04",
"version":"0.05",
"description": "Show the current speed according to the GPS",
"tags": "tool,outdoors,gps",
"tags": "tool,outdoors,gps,b2",
"storage": [
{"name":"speedo.app.js","url":"speedo.js"},
{"name":"speedo.img","url":"speedo-icon.js","evaluate":true}
@ -653,7 +668,7 @@
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"icon": "widget.png",
"version":"0.12",
"version":"0.13",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"tags": "widget,battery,b2",
"type":"widget",
@ -1421,7 +1436,7 @@
"icon": "chrono.png",
"version":"0.01",
"description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.",
"tags": "Tools",
"tags": "tool",
"storage": [
{"name":"chrono.app.js","url":"chrono.js"},
{"name":"chrono.img","url":"chrono-icon.js","evaluate":true}
@ -1562,7 +1577,7 @@
"icon": "app.png",
"version":"0.03",
"description": "Chronometer (timer) which runs as widget.",
"tags": "tools,widget",
"tags": "tool,widget,b2",
"readme": "README.md",
"storage": [
{"name":"chronowid.wid.js","url":"widget.js"},
@ -3502,7 +3517,7 @@
"name": "Pastel Clock",
"shortName": "Pastel",
"icon": "pastel.png",
"version":"0.03",
"version":"0.04",
"description": "A Configurable clock with custom fonts and background",
"tags": "clock,b2",
"type":"clock",
@ -3573,6 +3588,17 @@
{"name":"score.json"}
]
},
{ "id": "menusmall",
"name": "Small Menus",
"icon": "app.png",
"version":"0.01",
"description": "Replace Bangle.js 2's menus with a version that contains smaller text",
"tags": "b2,bno1,system",
"type": "boot",
"storage": [
{"name":"menusmall.boot.js","url":"boot.js"}
]
},
{ "id": "ffcniftya",
"name": "Nifty-A Clock",
"icon": "app.png",

View File

@ -6,3 +6,4 @@
0.06: Actual pixels as of 12 Jun 2020
0.07: Pressing a button now exits immediately (fix #618)
0.08: Make about (mostly) work on non-240px screens
0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021

File diff suppressed because one or more lines are too long

View File

@ -5,11 +5,15 @@ The advantage is, that you can still see your normal watchface and other widgets
The widget is always active, but only shown when the timer is on.
Hours, minutes, seconds and timer status can be set with an app.
Depending on when you start the timer, it may alert up to 0,999 seconds early. This is because it checks only for full seconds. When there is less than one seconds left, it buzzes. This cannot be avoided without checking more than every second, which I would like to avoid.
When there is less than one seconds left on the timer it buzzes.
The widget has been tested on Bangle 1 and Bangle 2
## Screenshots
TBD
![](chrono_with_wave.jpg)
![](chrono_with_pastel.jpg)
## Features

1
apps/health/ChangeLog Normal file
View File

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

38
apps/health/README.md Normal file
View File

@ -0,0 +1,38 @@
# Health Tracking
Logs health data to a file every 10 minutes, and provides an app to view it
**BETA - requires firmware 2v11**
## Usage
Once installed, health data is logged automatically.
To view data, run the `Health` app from your watch.
## Features
Stores:
* Heart rate (TODO)
* Step count
* Movement
## Technical Info
Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and
writes data to a binary file (one per month).
A library (that can be used with `require("health").readXYZ` can then be used
to grab historical health info.
## TODO
* **Extend file format to include combined data for each day (to make graphs faster)**
* `interface` page for desktop to allow data to be viewed and exported in common formats
* More features in app:
* Step counting goal (ensure pedometers use this)
* Calendar view showing steps per day
* Yearly view
* Heart rate 'zone' graph
* .. other

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///8H5AYM7/5L/ACsBqtQAgMFqtABYcVqtVAgIDBqgLDAwITBDYNVrQiEAANQEQNVtWAFIYfCE4Xq0AuEAAdX1W0BZFe1XUHQgADvWrJogAE9WtBYl66ouD2oLEtQGBFwQQBBYgeBFwYjFA4QuCBYgfCFwYLCL4IICFwacCPwetEwYLCR4QJBFwbFCU4QhBFwbMDNAYuCHQQwFFwowFFwowFFwwwEFwzNGFwjxFFwowEFw7aFBQwwDFwwwEFwwwEFw4wDBRAwBFxAwCFxAwCFxIA/AB4A="))

60
apps/health/app.js Normal file
View File

@ -0,0 +1,60 @@
function menuMain() {
E.showMenu({
"":{title:"Health Tracking"},
"< Back":()=>load(),
"Step Counting":()=>menuStepCount(),
"Movement":()=>menuMovement()
});
}
function menuStepCount() {
E.showMenu({
"":{title:"Step Counting"},
"per hour":()=>stepsPerHour()
});
}
function menuMovement() {
E.showMenu({
"":{title:"Movement"},
"per hour":()=>movementPerHour()
});
}
function stepsPerHour() {
E.showMessage("Loading...");
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.steps);
g.clear(1);
Bangle.drawWidgets();
g.reset();
require("graph").drawBar(g, data, {
y:24,
miny: 0,
axes : true,
gridx : 6,
gridy : 500
});
Bangle.setUI("updown", ()=>menuStepCount());
}
function movementPerHour() {
E.showMessage("Loading...");
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.movement);
g.clear(1);
Bangle.drawWidgets();
g.reset();
require("graph").drawLine(g, data, {
y:24,
miny: 0,
axes : true,
gridx : 6,
ylabel : null
});
Bangle.setUI("updown", ()=>menuStepCount());
}
Bangle.loadWidgets();
Bangle.drawWidgets();
menuMain();

BIN
apps/health/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

38
apps/health/boot.js Normal file
View File

@ -0,0 +1,38 @@
Bangle.on("health", health => {
// ensure we write health info for *last* block
var d = new Date(Date.now() - 590000);
const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6;
const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24;
const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
function getRecordFN(d) {
return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw";
}
function getRecordIdx(d) {
return (DB_RECORDS_PER_DAY*(d.getDate()-1)) +
(DB_RECORDS_PER_HR*d.getHours()) +
(0|(d.getMinutes()*DB_RECORDS_PER_HR/60));
}
var rec = getRecordIdx(d);
var fn = getRecordFN(d);
var f = require("Storage").read(fn);
if (f) {
var dt = f.substr(DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_RECORD_LEN);
if (dt!="\xFF\xFF\xFF\xFF") {
print("HEALTH ERR: Already written!");
return;
}
} else {
require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header
}
var recordData = String.fromCharCode(
health.steps>>8,health.steps&255, // 16 bit steps
health.bpm, // 8 bit bpm
Math.min(health.movement / 8, 255)); // movement
require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN);
});

61
apps/health/lib.js Normal file
View File

@ -0,0 +1,61 @@
const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6;
const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24;
const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
function getRecordFN(d) {
return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw";
}
function getRecordIdx(d) {
return (DB_RECORDS_PER_DAY*(d.getDate()-1)) +
(DB_RECORDS_PER_HR*d.getHours()) +
(0|(d.getMinutes()*DB_RECORDS_PER_HR/60));
}
// Read all records from the given month
exports.readAllRecords = function(d, cb) {
var rec = getRecordIdx(d);
var fn = getRecordFN(d);
var f = require("Storage").read(fn);
var idx = DB_HEADER_LEN;
for (var day=0;day<31;day++) {
for (var hr=0;hr<24;hr++) {
for (var m=0;m<DB_RECORDS_PER_HR;m++) {
var h = f.substr(idx, DB_RECORD_LEN);
if (h!="\xFF\xFF\xFF\xFF") {
cb({
day:day+1, hr : hr, min:m*10,
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
});
}
idx += 4;
}
}
}
}
// Read all records from the given month
exports.readDay = function(d, cb) {
var rec = getRecordIdx(d);
var fn = getRecordFN(d);
var f = require("Storage").read(fn);
var idx = DB_HEADER_LEN + (DB_RECORD_LEN*DB_RECORDS_PER_DAY*(d.getDate()-1));
for (var hr=0;hr<24;hr++) {
for (var m=0;m<DB_RECORDS_PER_HR;m++) {
var h = f.substr(idx, DB_RECORD_LEN);
if (h!="\xFF\xFF\xFF\xFF") {
cb({
hr : hr, min:m*10,
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
});
}
idx += 4;
}
}
}

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Fix occasional missed image when scrolling up
0.03: Text wrapping, better font

View File

@ -14,17 +14,23 @@ var w = g.getWidth();
var h = g.getHeight();
var n = Math.ceil((h-24)/APPH);
var menuScrollMax = APPH*apps.length - (h-24);
// FIXME: not needed after 2v11
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
apps.forEach(app=>{
if (app.icon)
app.icon = s.read(app.icon); // should just be a link to a memory area
});
if (g.wrapString) { // FIXME: check not needed after 2v11
g.setFont(font);
apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n"));
}
function drawApp(i) {
var y = 24+i*APPH-menuScroll;
var app = apps[i];
if (!app || y<-APPH || y>=g.getHeight()) return;
g.setFont("6x8",2).setFontAlign(-1,0).drawString(app.name,64,y+32);
g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32);
if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){}
}

1
apps/menusmall/ChangeLog Normal file
View File

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

BIN
apps/menusmall/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

121
apps/menusmall/boot.js Normal file
View File

@ -0,0 +1,121 @@
"";//not entirely sure why we need this - related to how bootupdate adds these to .boot0
E.showMenu = function(items) {
g.clear(1).flip(); // clear screen if no menu supplied
Bangle.drawWidgets();
if (!items) {
Bangle.setUI();
return;
}
var w = g.getWidth();
var h = g.getHeight();
var menuItems = Object.keys(items);
var options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
options.fontHeight=14;
options.x=0;
options.x2=w-1;
options.y=24;
options.y2=h-12;
if (options.selected === undefined)
options.selected = 0;
var x = 0|options.x;
var x2 = options.x2||(g.getWidth()-1);
var y = 0|options.y;
var y2 = options.y2||(g.getHeight()-1);
if (options.title)
y += 15;
var loc = require("locale");
var l = {
lastIdx : 0,
draw : function(rowmin,rowmax) {
var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length);
var idx = E.clip(options.selected-(rows>>1),0,menuItems.length-rows);
if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled
l.lastIdx = idx;
var iy = y;
g.reset().setFontAlign(0,-1,0);
g.setFontCustom(atob("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"), 32, atob("AwIGCAgICAMFBQYIAwYDBwcFBgYHBgYGBgYDAwYHBgcHBgYGBgYGBgYEBgYGBgYGBgYGBgYGBggGBgYEBwQGBwQGBgYGBgYHBgYGBgYGBgYGBgYGBgYGBgYGBgQCBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHCAYGBgAGBgYGAAYGBQYABgMGBgQABgYHBgAGBgYGBgYGBgYGBgYGBgYEBAYGBgYGBgYGAAYGBgYGBgYHBgYGBgYGBgYGBgYGBgYGBwcGBgYGBgYABgYGBgYGBg=="), 15);
if (rowmin===undefined && options.title)
g.drawString(options.title,(x+x2)/2,y-14).drawLine(x,y-2,x2,y-2).
setColor(g.theme.fg).setBgColor(g.theme.bg);
iy += 12;
g.setColor((idx>0)?g.theme.fg:g.theme.bg).fillPoly([72,iy,104,iy,88,iy-12]);
if (rowmin!==undefined) {
if (idx<rowmin) {
iy += options.fontHeight*(rowmin-idx);
idx=rowmin;
}
if (idx+rows>rowmax) {
rows = 1+rowmax-rowmin;
}
}
var less = idx>0;
while (rows--) {
var name = menuItems[idx];
var item = items[name];
var hl = (idx==options.selected && !l.selectEdit);
g.setColor(hl ? g.theme.bgH : g.theme.bg);
g.fillRect(x,iy,x2,iy+options.fontHeight-1);
g.setColor(hl ? g.theme.fgH : g.theme.fg);
g.setFontAlign(-1,-1);
g.drawString(loc.translate(name),x+1,iy+1);
if ("object" == typeof item) {
var xo = x2;
var v = item.value;
if (item.format) v=item.format(v);
v = loc.translate(""+v);
if (l.selectEdit && idx==options.selected) {
xo -= 24 + 1;
g.setColor(g.theme.bgH).fillRect(xo-(g.stringWidth(v)+4),iy,x2,iy+options.fontHeight-1);
g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2});
}
g.setFontAlign(1,-1);
g.drawString(v,xo-2,iy+1);
}
g.setColor(g.theme.fg);
iy += options.fontHeight;
idx++;
}
g.setFontAlign(-1,-1);
g.setColor((idx<menuItems.length)?g.theme.fg:g.theme.bg).fillPoly([72,166,104,166,88,174]);
g.flip();
},
select : function(dir) {
var item = items[menuItems[options.selected]];
if ("function" == typeof item) item(l);
else if ("object" == typeof item) {
// if a number, go into 'edit mode'
if ("number" == typeof item.value)
l.selectEdit = l.selectEdit?undefined:item;
else { // else just toggle bools
if ("boolean" == typeof item.value) item.value=!item.value;
if (item.onchange) item.onchange(item.value);
}
l.draw();
}
},
move : function(dir) {
if (l.selectEdit) {
var item = l.selectEdit;
item.value -= (dir||1)*(item.step||1);
if (item.min!==undefined && item.value<item.min) item.value = item.min;
if (item.max!==undefined && item.value>item.max) item.value = item.max;
if (item.onchange) item.onchange(item.value);
l.draw(options.selected,options.selected);
} else {
var a=options.selected;
options.selected = (dir+options.selected)%menuItems.length;
if (options.selected<0) options.selected += menuItems.length;
l.draw(Math.min(a,options.selected), Math.max(a,options.selected));
}
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
return l;
};

View File

@ -8,6 +8,7 @@ function redraw() {
m.draw();
drawMarker();
if (WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) {
g.flip(); // force immediate draw on double-buffered screens - track will update later
g.setColor(0.75,0.2,0);
WIDGETS["gpsrec"].plotTrack(m);
}

View File

@ -1,3 +1,4 @@
0.01: First release
0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM
0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2
0.04: Leave space at the bottom for Chrono widget, set back option at first option

View File

@ -87,19 +87,19 @@ function draw() {
// avoid flicker on a bangle 1 by comparing with previous minute
if (mm_prev != mm) {
mm_prev = mm;
g.clearRect(0, 30, w, h);
g.clearRect(0, 30, w, h - 24);
}
} else {
// on a b2 safe to just clear anyway as there is no flicker
g.clearRect(0, 30, w, h);
g.clearRect(0, 30, w, h - 24);
}
// draw a grid like graph paper
if (settings.grid && process.env.HWVERSION !=1) {
g.setColor("#0f0");
for (var gx=20; gx <= w; gx += 20)
g.drawLine(gx, 30, gx, h);
for (var gy=30; gy <= h; gy += 20)
g.drawLine(gx, 30, gx, h - 24);
for (var gy=30; gy <= h - 24; gy += 20)
g.drawLine(0, gy, w, gy);
}

View File

@ -26,6 +26,7 @@
E.showMenu({
'': { 'title': 'Pastel Clock' },
'< Back': back,
'Font': {
value: 0 | font_options.indexOf(s.font),
min: 0, max: 4,
@ -50,7 +51,6 @@
s.date = !s.date
save()
},
},
'< Back': back,
}
})
})

View File

@ -3,3 +3,4 @@
0.03: Use offscreen buffer (not doublebuffer)
Use 'locale' to get internationalised speed
0.04: Start GPS after loading app, just in case widgets affect it (#449)
0.05: Use Layout lib for Bangle.js 2 compatibility

View File

@ -1,33 +1,61 @@
var buf = Graphics.createArrayBuffer(240,120,1,{msb:true});
var lastFix = {fix:0,satellites:0};
var Layout = require("Layout");
var layout;
var lastFix = {fix:-1,satellites:0};
function speedoImage() {
return require("heatshrink").decompress(atob("kkdxH+ABteAAwWOECImZDQ2CAQglUD4us2fX68ymQDB1omFESWtDgIACEYYACrolPBwddmWIEZWsmVWJYgiLwXX2YcB1gdDq+BAodWGIWsEhQiDRAWBmQdEAAhGBroFC1ojMC4etERIlDAggkHNIgAWSYYjFVwNWGwgAP5KkBEYoFC1ihBagwAL5W72vKJAxpExCiDABnQ4W12vD6AHBEYxnT4YhB3ghCSIhqDe4SIP3giBM4LfFEYpiMDoQhC3fDCA7+DfBwiCAARmFAAmtEYlYagMywISHEQhEId4UyEYleqwABEZBHERQwABroZBq5rR6BGLNZKzMAAPKRZKzJr2tfaAAKxD7CfgRsD1g1GAAwME2YGDwQjFNgOzwMyCwuCwIAEBg0yHoKODEYmCcYNWCwutAAuzBgg4BCwJGEEgj7JV5r7BIwgjEWrDVCEQYkCWgYAWNYIjF/z8awQfD"));
}
function onGPS(fix) {
lastFix = fix;
buf.clear();
buf.setFontAlign(0,0);
buf.setFont("6x8");
buf.drawString(fix.satellites+" satellites",120,6);
if (lastFix.fix != fix.fix) {
// if fix is different, change the layout
if (fix.fix) {
layout = new Layout( {
type:"v", c: [
{type:"txt", font:"6x8:2", label:"Speed" },
{type:"h", c: [
{type:"img", src:speedoImage, pad:4 },
{type:"txt", font:"35%", label:"--", fillx:true, id:"speed" },
]},
{type:"txt", font:"6x8", label:"--", id:"units" },
{type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
{type:"txt", font:"6x8", pad:3, label:"Satellites" }
]},
]},[],{lazy:true});
} else {
layout = new Layout( {
type:"v", c: [
{type:"txt", font:"6x8:2", label:"Speed" },
{type:"img", src:speedoImage, pad:4 },
{type:"txt", font:"6x8", label:"Waiting for GPS" },
{type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
{type:"txt", font:"6x8", pad:3, label:"Satellites" }
]},
]},[],{lazy:true});
}
g.clearRect(0,24,g.getWidth(),g.getHeight());
layout.render();
}
lastFix = fix;
if (fix.fix && isFinite(fix.speed)) {
var speed = require("locale").speed(fix.speed);
var m = speed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
var txt = (fix.speed<20) ? fix.speed.toFixed(1) : Math.round(fix.speed);
var value = m[1], units = m[2];
var s = 80;
buf.setFontVector(s);
buf.drawString(value,120,10+s/2);
buf.setFont("6x8",2);
buf.drawString(units,120,s+26);
} else {
buf.setFont("6x8",2);
buf.drawString("Waiting for GPS",120,56);
layout.speed.label = m[1];
layout.units.label = m[2];
}
g.reset();
g.drawImage({width:buf.getWidth(),height:buf.getHeight(),bpp:1,buffer:buf.buffer},0,70);
g.flip();
layout.sat.label = fix.satellites;
layout.render();
}
g.clear();
onGPS(lastFix);
onGPS({fix:0,satellites:0});
// onGPS({fix:1,satellites:3,speed:200}); // testing
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.on('GPS', onGPS);
Bangle.setGPSPower(1);
Bangle.setGPSPower(1, "app");

View File

@ -9,3 +9,4 @@
0.10: Add 'hide if charge greater than'
0.11: Don't overwrite existing settings on app update
0.12: Fixed for Bangle 2
0.13: Fillbar setting added, see README

View File

@ -4,5 +4,13 @@ Show the current battery level and charging status in the top right of the clock
Works with Bangle 1 and Bangle 2
![](screenshot.jpg)
When the fillbar setting is on the level colour will fill the entire
bar. This makes for an easier to read dsiplay when the charge is
below 50%.
![](widbatpc.full.jpg)
When the fillbar setting is off the level colour will follow the battry percentage
![](widbatpc.part.jpg)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -10,6 +10,7 @@
let s = {
'color': COLORS[0],
'percentage': true,
'fillbar': false,
'charger': true,
'hideifmorethan': 100,
}
@ -54,6 +55,11 @@
save('color')(s.color)
}
},
'Fill Bar': {
value: s.fillbar,
format: onOffFormat,
onchange: save('fillbar'),
},
'Hide if >': {
value: s.hideifmorethan||100,
min: 10,

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -80,7 +80,12 @@
var s = 39;
var x = this.x, y = this.y;
const l = E.getBattery();
const xl = x+4+l*(s-12)/100
let xl = x+4+l*(s-12)/100;
// show bar full in the level color, as you cant see the color if the bar is too small
if (setting('fillbar'))
xl = x+4+100*(s-12)/100;
c = levelColor(l);
if (Bangle.isCharging() && setting('charger')) {

View File

@ -40,10 +40,6 @@
<p id="requireHTTPS" class="hidden">
<b>STOP!</b> This page <b>must</b> be served over HTTPS. Please <a>reload this page via HTTPS</a>.
</p>
<div class="toast toast-success flex-centered" id="bangle2ks">
<img src="data:image/png;base64,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" />
&nbsp;Bangle.js 2 is now on KickStarter! &nbsp;<a href="https://www.kickstarter.com/projects/gfw/banglejs-2-the-open-smart-watch" target="_blank">Check it out here</a>
</div>
</div>