1
0
Fork 0

Merge branch 'espruino:master' into master

master
berkenbu 2022-02-16 17:41:25 -05:00 committed by GitHub
commit 29c4f1cbac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
168 changed files with 2956 additions and 1208 deletions

View File

@ -43,3 +43,7 @@ charge.
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev) This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
Written by: [Hugh Barney](https://github.com/hughbarney) For support
and discussion please post in the [Bangle JS
Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -1,2 +1,3 @@
0.01: Display pressure as number and hand 0.01: Display pressure as number and hand
0.02: Use theme color 0.02: Use theme color
0.03: workaround for some firmwares that return 'undefined' for first call to barometer

View File

@ -110,9 +110,13 @@ drawScaleLabels();
drawIcons(); drawIcons();
try { try {
Bangle.getPressure().then(data => { function baroHandler(data) {
drawHand(Math.round(data.pressure)); if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429
}); setTimeout(() => Bangle.getPressure().then(baroHandler), 500);
else
drawHand(Math.round(data.pressure));
}
Bangle.getPressure().then(baroHandler);
} catch(e) { } catch(e) {
print(e.message); print(e.message);
print("barometer not supporter, show a demo value"); print("barometer not supporter, show a demo value");

View File

@ -1,7 +1,7 @@
{ "id": "barometer", { "id": "barometer",
"name": "Barometer", "name": "Barometer",
"shortName":"Barometer", "shortName":"Barometer",
"version":"0.02", "version":"0.03",
"description": "A simple barometer that displays the current air pressure", "description": "A simple barometer that displays the current air pressure",
"icon": "barometer.png", "icon": "barometer.png",
"tags": "tool,outdoors", "tags": "tool,outdoors",

View File

@ -45,3 +45,4 @@
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035) 0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
0.40: Bootloader now rebuilds for new firmware versions 0.40: Bootloader now rebuilds for new firmware versions
0.41: Add Keyboard and Mouse Bluetooth HID option 0.41: Add Keyboard and Mouse Bluetooth HID option
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js

View File

@ -195,7 +195,19 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
// Append *.boot.js files // Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{ var getPriority = /.*\.(\d+)\.boot\.js$/;
require('Storage').list(/\.boot\.js/).sort((a,b)=>{
var aPriority = a.match(getPriority);
var bPriority = b.match(getPriority);
if (aPriority && bPriority){
return parseInt(aPriority[1]) - parseInt(bPriority[1]);
} else if (aPriority && !bPriority){
return -1;
} else if (!aPriority && bPriority){
return 1;
}
return a > b;
}).forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }() // we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
// which would cause an error! // which would cause an error!

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.41", "version": "0.42",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",

6
apps/bowserWF/README.md Normal file
View File

@ -0,0 +1,6 @@
# Bowser Watchface
Show your evil character.
With style!
Bowser jumps once every minute to advance the clock. hehe

View File

@ -0,0 +1 @@
E.toArrayBuffer(atob("Ly+EARERERERERERERERER//8RERERERERERERERERERERERERERERzP//d3EREREREREREREREREREREREREcz//3dxERERERERERERERERERERERERHMz/93dxERERERERERERERERERERERERd8zHd3f/8REREREREREREREREREREREXfMx3d3//ERERERERERERERERERERERF3d3d3d//3cRHBEREREREREREREREREXd3d3d3f/93fMDMERERERERERERERERF3d3d3d3//d3zAzBERERERERERERERERd3d3fMd3f//8zMwREREREREREREREREXd3d8zMd3f/zMzMERERERERERERERERF3d3fMzHd3/8zMzBERERERERER93d3//d3d3x3/Mx3fMzP8REREREREXzP/3d//3d3d8d3fMzMz/8RERERERERF8z/93f/93d3fHd3zMzM//ERERER//93zM//93//d3d3z/d/9////xEREREcz/d3zM/3d///d3d8zHERH/ERERERERHM/3d8zP93f//3d3fMxxER/xERERER/3zMd3d3d3d3f///d3zM8REREREREREf93d3d3d3d3d3d///d3zMERERERERERH/d3d3d3d3d3d3f//3d8zBERERERERERd3d3f///d3d3d3//93fMzP8RERERERH//3d3zP/3d//////3d3d8wRERERERER//93d8z/93f/////93d3fMEREREREREc//d3fMx3f//MzMd3d3d3dxERERERERHMx3d3d3d//8zMAPd3zMzHERERERERH/d3d3d3d3f3fMzwDMzAD8zBERERERER/3d3d3d3d393zM8AzMwA/MwRERERERH913d///d3d/d3AAzMzMAAzMERERERERREd3fM/3d//3d//MzMzMwP/BEREREREURHd3zP93f/93f/zMzMzMD/wRERERERFP/3d8zHd3//d3d3zMzMzAAMERERERER//93d3d3d//3d3d8zMzMwP8BEREREREf//d3d3d3f/93d3fMzMzMD/ARERERERFMzHf/93d3/3d3d3EczM/wABERERERERR3x3fMd3f/93d3dxEREREREREREREREUd8d3zHd3//d3d3cRERERERERERERERFHd3d3d///d3d3dxERERERERERERERERR3d3f///93d3dxEREREREREREREREREUd3d3////d3d3cRERERERERERERERERH//////3d3d3d8ERERERERERERERERER/////MzMx3fMz/8REREREREREREREREf////zMzMd3zM//EREREREREREREREREf/8zMzMzMzMzP//ERERERERERERERERERzMzM/8zP/xEREREREREREREREREREREczMzP/Mz/8REREREREREREREREREREczMzMz//8///xERERERERERERERERERA="))

102
apps/bowserWF/app.js Normal file
View File

@ -0,0 +1,102 @@
var sprite = {
width : 47, height : 47, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("kmSpICFn/+BAwCImV//VICJuT//SogRMpmT/2SCJtSyQDB/4RMymRkmX/gRLygDC3/piVhCJElAYf/pNIkgRIlIDCl/6pVBkIRIGwWJEYPypMJCI9KGwQRBLANIPRI2CGoPkyVCBwmeyVLTYNJom8yImBz4gEqV/6Vf+g2BPwf/IIq8C/+kyVRkgDBp/5CIX/+mkz/+y/9BIOf0v6///5LdCz+kCIOk34RBYQMSp5XBGQVk/pNBAQP/9IyBxGSv4yCk/1OIK8EC4QgEpM/JgJ+EGoIRBTApQCEYvplLOFXIIdBO4SqBeQJABGoeTDQMlk5WCAAPSYQLgEz4aBlM/9IgB/7CCcAvP/QsBiVfUwOJBgUiCIcmpAVCy/+pMAKwMkRgIRCp6VBAwW6qVOgmSgPkwgRDv53E6WSuEkyEPRgmf2VJv5HBl2SgAKBwEJRgnJiVKp/Sr/0y/yBQOQv56DKwVSv2STwO/DgWD/BADmaDByRoBYoQRCgFCCIf/+jgDNwOUAwMg/kSPQbODX4IJBAwUH8B6DsmRl5oBl7OBklMyV+gBoDycSxMpiVLZwS8EAQeYyjaByR6BBIJBDAQnEIgbFCogOFRgQDBr//I4L0EAQsxAYP//5WCGQ6MCAAKbCpKYEAQiMB//kIQOUyf+CJF/CIIEBTYOfcgQRHBQv/CJKnBpP8GRTCDJIPkGRQCB5I3C/n/EZUgA"))
};
const boxes = {
width : 122, height : 56, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("kmZkmSpICPwgDBmQUQAQMJAYNkFiOSiQDB5JESAYQsSpADByYsSyBZBydt23bAR+wgFJkwUQAQNggGSposR23AgMkzZESwECpM2IiUAgmSFiW2gDlBFiVsgDlBFiXYgDNBL4MDWZy2FgEGWZy2FgENWZy2EL4MbWZpTBWwZfBXJpTCWwZiCWZpTBWwZiCWZsbWwhiCWZpWCWwTORWwgXRWwgXRWwZESWwZESWwZESWwYXRWwgXRW362/W362/W362/W362/W362/W362/W362/W362/W362/W362/WwuAgazOWwsAgyzOWwsAhqzOWwhfBjazNKYK2DL4K5NKYS2DMQSzNKYK2DMQSzNja2EMQSzNKwS2CZyK2EC6K2EC6K2DIiS2DIiS2DIiUAFoMAAFTkBFtckyAtrLgWSpICnLIIsqyVAgAsqpIA="))
};
const background = {
width : 176, height : 176, bpp : 3,
transparent : 5,
buffer : require("heatshrink").decompress(atob("kmSpIC/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ATWAgEAIP1///8iRB8gf/AAOCIPdIIARBBoJB/+E4IP4ABghB9v4CB8BB5g/92//9pB7wP/97FEIO9IgDACAAn8iVBIOlHH4xBDnA+wyY9IAAmB/BB//5B/IOQ/OAARBup5B/yV/IP5B/IP5BRt5B7/wDC7aD8/w+B+3bBgP7IP5B7HYNt23/AQPfIPX/9oCC24IDINwCBIRAAHIOACBHI3+g4EC/l/4BByAQkA//wpED//4gGAhJB3pMAgQFBgEBH3AC/AX4C/AX4C/AX4C/AX4C/AUOAgBB/v//ghB9gf///gH3UgiVIIAJBBwRB5j+CIIf8uBB5//wIIXb//+hJB6o/92/7v5B7/0/97GCIPYAG4MgIP/BjkSIP34/hB//5B/AAQ+0IP5B/IP5BN7ZB97///wCBIPX93yAB2wCB+5B5tv//dt24CB35B5v/+n/t+P/I4PH8ESIO38gFA/+CgH/+EIgiD3gACCPoMAgQ+2AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/AX4C/ASVIgAACgRB/IPY8GkAHBiRB/IPBLKgJB/IP5B/AQUAkmQghB/IP2AgEAyVAiRB/IP5BBpMAIP5B/IIUkgBB/IP5BpoAsBgJBOgEEIIoIBIP5BlyE27dt2EEIJ4CBBAlIgRBgpEAhu2IIO24ESQwxB/IJQhGkEJIL8GHwQCDgOweQpB/IKMkwAKJILVgAofYeQhBzsEAIKICLoESILmBQARBBtuwgZB3kA4B4ENIgJBcpMAIMYCDIOcAgEbHYgCGsEJkhEBE6cBIP5BZfYQ+JIIkDsEBIP5BVyEAIKtAHxgCDwBEBINk2IKCGCIKmSpECIP5BUkEBHyACD2BBUFoMJIP5BSpEbHyQCDIP5BXkmAIP5B/AQcAbKJB/ILH/AAP8hM/AgWSv4KCAAP+gmfAoXJk4ME//gpIEC8mTBgvwkgEC+QRDAAX4gVPAgP5kgsCLwWQh/kMIUf5LuFg4jBAoMBKAJ5EwF/AoUA/yFFoE/CI6RDgY+BCIQsDIP5B/IP5B/IP5B/IJ/AIJfghJBKv0EIJcAIJfwIP5BMhMAAAMEz5BGgmABoVJII9IBgUkII8kBgUSII8CoAMBhJB/IIsQoMAYoP/AAP4YpAMC/+BII9/BgXAYpAMC8DFIBgXwIIcCIP6DCgkQh/kCIRBIbQcBIJAFCgBBICI5BE/IRDFgQA="))
};
numbersDims = {
width: 20,
height: 44
};
const numbers = [
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKYQIDAwUEBxMAAQNAgECpMgAQMkB4IOIAQQLCgEQBwQaBgEBB1oCBBwYCCiRWDCIRWEO5wOHAX4CnA=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKYIIEwEAggOKNIQODyAHCBxQsWB3TUFgMgA4sSBwzU/AVA=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBwwyDDwUSCgVAAwIOBEwI7EpI7FBw4FDghZGHwgOEF4Y+CEYQ+DBxQADNAIAFNAIOFa/4CoA=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKosSAwsBBw4aCoEAgQjEBoIpEBwtIBoIUEwEAggUDBwwyDDoWQA4ZWHhIIEJQoOCgI+EBwMQEAYOJO4oLBO4oRDJQrX/AU4")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARNIKgQIDwAGBgQOJNQYOCyAHDBxEggB6BBwYDBiVABxIjBCIIODF4YOEAAkBV40QBwxiDNAosEB0IC/AUg")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5UFkmQAwkCBxIdGCIIIDBxAsTgAaEkEASooOBiQOVJQgOBiBKDBxMSJQwRBLIgRCBwjX/AVA=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/ARGQKgYICAwcCBxADBiQdDkEANYoOGEAYyEHYoOIHYqfFBxIdDBAMQFgZHCBysSFgwRBO46GFa/4CnA")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ5VGiAGFgIOIDQUgBwUCEYQOJGQYNBHAlADQgOHwEAggUDpANBCgYpBBwmQAwJiGhIjDB1gC/AU4A=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKYYICAwcEBxGQgAaDgVJgACBDQQOJgB6CBwcAiQODHa4AEhIRBpAHDiARBwAGCgIgCFIYOCFIYOHiQrEJQxlCBwzX/AVAA=")),
require("heatshrink").decompress(atob("ikswcBkmSpIC/AQ8gKggIBAwkCBw+QCIQLCgIRCDQcQBzkSTAsBHYoOIL4gOCMooOENAYOCoA4EBwoqDgiGGF4gOEa/4CoA=")),
];
digitPositions = [ // relative to the box
{x:13, y:6}, {x:32, y:6},
{x:74, y:6}, {x:93, y:6},
];
var drawTimeout;
const animation_duration = 1; // seconds
const animation_steps = 20;
const jump_height = 45; // top coordinate of the jump
const seconds_per_minute = 60;
function draw() {
const now = new Date();
g.drawImage(background, 0, 0);
var boxTL_x = 27; var boxTL_y = 29;
var sprite_TL_x = 72; var sprite_TL_y = 161 - sprite.height;
const seconds = now.getSeconds()%seconds_per_minute + now.getMilliseconds()/1000;
const hours = now.getHours();
const minutes = now.getMinutes();
var time_advance = seconds / animation_duration;
if (time_advance < 0.5) {
sprite_TL_y += (jump_height - sprite_TL_y) * time_advance * 2;
} else if (time_advance < 1) {
sprite_TL_y = jump_height + (sprite_TL_y-jump_height) * (time_advance-0.5) * 2;
}
const box_penetration = boxTL_y + boxes.height - sprite_TL_y;
if (box_penetration > 0) {
boxTL_y -= box_penetration;
}
g.drawImage(boxes, boxTL_x, boxTL_y);
g.drawImage(numbers[(hours / 10) >> 0], boxTL_x+digitPositions[0].x, boxTL_y+digitPositions[0].y);
g.drawImage(numbers[(hours % 10) >> 0], boxTL_x+digitPositions[1].x, boxTL_y+digitPositions[1].y);
g.drawImage(numbers[(minutes / 10) >> 0], boxTL_x+digitPositions[2].x, boxTL_y+digitPositions[2].y);
g.drawImage(numbers[(minutes % 10) >> 0], boxTL_x+digitPositions[3].x, boxTL_y+digitPositions[3].y);
g.drawImage(sprite, sprite_TL_x, sprite_TL_y);
Bangle.drawWidgets();
const timeout = time_advance <= 1?
animation_duration / animation_steps
: (seconds_per_minute - seconds);
setTimeout( _=>{
drawTimeout = undefined;
draw();
}, timeout * 1000);
}
// Clear the screen once, at startup
g.setTheme({bg:"#00f",fg:"#fff",dark:true}).clear();
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) {
clearTimeout(drawTimeout);
}
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
draw();

BIN
apps/bowserWF/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
{ "id": "bowserWF",
"name": "Bowser Watchface",
"shortName":"Bowser Watchface",
"version":"0.01",
"description": "Let bowser show you the time",
"icon": "app.png",
"tags": "",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"bowserWF.app.js","url":"app.js"},
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -17,3 +17,6 @@
0.06: Fix bug if no request waiting time is set 0.06: Fix bug if no request waiting time is set
Fix bug if no connection data was cached Fix bug if no connection data was cached
Fix error during disconnect Fix error during disconnect
0.07: Recorder icon only blue if values actually arive
Adds some preset modes and a custom one
Restructure the settings menu

View File

@ -29,6 +29,7 @@ Heart Rate Service (`180D`) and characteristic (`2A37`).
So far it has been tested on: So far it has been tested on:
* CooSpo Bluetooth Heart Rate Monitor * CooSpo Bluetooth Heart Rate Monitor
* Wahoo TICKR X 2
## Internals ## Internals
@ -36,7 +37,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
## TODO ## TODO
* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off
* A widget to show connection state? * A widget to show connection state?
* Specify a specific device by address? * Specify a specific device by address?

View File

@ -548,9 +548,7 @@
E.on("kill", ()=>{ E.on("kill", ()=>{
if (gatt && gatt.connected){ if (gatt && gatt.connected){
log("Got killed, trying to disconnect"); log("Got killed, trying to disconnect");
var promise = gatt.disconnect(); var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
promise.then(()=>log("Disconnected on kill"));
promise.catch((e)=>log("Error during disconnnect on kill", e));
} }
}); });
} }

View File

@ -1,4 +1,5 @@
{ {
"mode": 1,
"enabled": true, "enabled": true,
"replace": true, "replace": true,
"debuglog": false, "debuglog": false,
@ -6,6 +7,12 @@
"allowFallback": true, "allowFallback": true,
"warnDisconnect": false, "warnDisconnect": false,
"fallbackTimeout": 10, "fallbackTimeout": 10,
"custom_replace": false,
"custom_debuglog": false,
"custom_startWithHrm": false,
"custom_allowFallback": false,
"custom_warnDisconnect": false,
"custom_fallbackTimeout": 10,
"gracePeriodNotification": 0, "gracePeriodNotification": 0,
"gracePeriodConnect": 0, "gracePeriodConnect": 0,
"gracePeriodService": 0, "gracePeriodService": 0,

View File

@ -2,7 +2,7 @@
"id": "bthrm", "id": "bthrm",
"name": "Bluetooth Heart Rate Monitor", "name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM", "shortName": "BT HRM",
"version": "0.06", "version": "0.07",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
@ -12,7 +12,7 @@
"storage": [ "storage": [
{"name":"bthrm.app.js","url":"bthrm.js"}, {"name":"bthrm.app.js","url":"bthrm.js"},
{"name":"bthrm.recorder.js","url":"recorder.js"}, {"name":"bthrm.recorder.js","url":"recorder.js"},
{"name":"bthrm.boot.js","url":"boot.js"}, {"name":"bthrm.0.boot.js","url":"boot.js"},
{"name":"bthrm.img","url":"app-icon.js","evaluate":true}, {"name":"bthrm.img","url":"app-icon.js","evaluate":true},
{"name":"bthrm.settings.js","url":"settings.js"}, {"name":"bthrm.settings.js","url":"settings.js"},
{"name":"bthrm.default.json","url":"default.json"} {"name":"bthrm.default.json","url":"default.json"}

View File

@ -32,7 +32,7 @@
Bangle.removeListener('BTHRM', onHRM); Bangle.removeListener('BTHRM', onHRM);
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder"); if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
}, },
draw : (x,y) => g.setColor((Bangle.isBTHRMConnected && Bangle.isBTHRMConnected())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
}; };
} }
}) })

View File

@ -20,182 +20,147 @@
var mainmenu = { var mainmenu = {
'': { 'title': 'Bluetooth HRM' }, '': { 'title': 'Bluetooth HRM' },
'< Back': back, '< Back': back,
'Use BT HRM': { 'Mode': {
value: !!settings.enabled, value: 0 | settings.mode,
format: v => settings.enabled ? "On" : "Off", min: 0,
max: 3,
format: v => ["Off", "Default", "Both", "Custom"][v],
onchange: v => { onchange: v => {
writeSettings("enabled",v); settings.mode = v;
switch (v){
case 0:
writeSettings("enabled",false);
break;
case 1:
writeSettings("enabled",true);
writeSettings("replace",true);
writeSettings("debuglog",false);
writeSettings("startWithHrm",true);
writeSettings("allowFallback",true);
writeSettings("fallbackTimeout",10);
break;
case 2:
writeSettings("enabled",true);
writeSettings("replace",false);
writeSettings("debuglog",false);
writeSettings("startWithHrm",false);
writeSettings("allowFallback",false);
break;
case 3:
writeSettings("enabled",true);
writeSettings("replace",settings.custom_replace);
writeSettings("debuglog",settings.custom_debuglog);
writeSettings("startWithHrm",settings.custom_startWithHrm);
writeSettings("allowFallback",settings.custom_allowFallback);
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
break;
}
writeSettings("mode",v);
} }
}, },
'Replace HRM': { 'Custom Mode': function() { E.showMenu(submenu_custom); },
value: !!settings.replace, 'Debug': function() { E.showMenu(submenu_debug); }
format: v => settings.replace ? "On" : "Off", };
onchange: v => {
writeSettings("replace",v);
}
},
'Start with HRM': {
value: !!settings.startWithHrm,
format: v => settings.startWithHrm ? "On" : "Off",
onchange: v => {(function(back) {
function writeSettings(key, value) {
var s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
require('Storage').writeJSON(FILE, s);
readSettings();
}
function readSettings(){ var submenu_debug = {
settings = Object.assign( '' : { title: "Debug"},
require('Storage').readJSON("bthrm.default.json", true) || {}, '< Back': function() { E.showMenu(mainmenu); },
require('Storage').readJSON(FILE, true) || {} 'Alert on disconnect': {
); value: !!settings.warnDisconnect,
} format: v => settings.warnDisconnect ? "On" : "Off",
var FILE="bthrm.json";
var settings;
readSettings();
var mainmenu = {
'': { 'title': 'Bluetooth HRM' },
'< Back': back,
'Use BT HRM': {
value: !!settings.enabled,
format: v => settings.enabled ? "On" : "Off",
onchange: v => { onchange: v => {
writeSettings("enabled",v); writeSettings("warnDisconnect",v);
} }
}, },
'Replace HRM': { 'Debug log': {
value: !!settings.replace, value: !!settings.debuglog,
format: v => settings.replace ? "On" : "Off", format: v => settings.debuglog ? "On" : "Off",
onchange: v => { onchange: v => {
writeSettings("replace",v); writeSettings("debuglog",v);
}
},
'Grace periods': function() { E.showMenu(submenu_grace); }
};
var submenu_custom = {
'' : { title: "Custom mode"},
'< Back': function() { E.showMenu(mainmenu); },
'Replace HRM': {
value: !!settings.custom_replace,
format: v => settings.custom_replace ? "On" : "Off",
onchange: v => {
writeSettings("custom_replace",v);
} }
}, },
'Start w. HRM': { 'Start w. HRM': {
value: !!settings.startWithHrm, value: !!settings.custom_startWithHrm,
format: v => settings.startWithHrm ? "On" : "Off", format: v => settings.custom_startWithHrm ? "On" : "Off",
onchange: v => { onchange: v => {
writeSettings("startWithHrm",v); writeSettings("custom_startWithHrm",v);
} }
}, },
'HRM Fallback': { 'HRM Fallback': {
value: !!settings.allowFallback, value: !!settings.custom_allowFallback,
format: v => settings.allowFallback ? "On" : "Off", format: v => settings.custom_allowFallback ? "On" : "Off",
onchange: v => { onchange: v => {
writeSettings("allowFallback",v); writeSettings("custom_allowFallback",v);
} }
}, },
'Fallback Timeout': { 'Fallback Timeout': {
value: settings.fallbackTimeout, value: settings.custom_fallbackTimeout,
min: 5, min: 5,
max: 60, max: 60,
step: 5, step: 5,
format: v=>v+"s", format: v=>v+"s",
onchange: v => { onchange: v => {
writeSettings("fallbackTimout",v*1000); writeSettings("custom_fallbackTimout",v*1000);
} }
}, },
'Conn. Alert': { };
value: !!settings.warnDisconnect,
format: v => settings.warnDisconnect ? "On" : "Off", var submenu_grace = {
'' : { title: "Grace periods"},
'< Back': function() { E.showMenu(submenu_debug); },
'Request': {
value: settings.gracePeriodRequest,
min: 0,
max: 3000,
step: 100,
format: v=>v+"ms",
onchange: v => { onchange: v => {
writeSettings("warnDisconnect",v); writeSettings("gracePeriodRequest",v);
} }
}, },
'Debug log': { 'Connect': {
value: !!settings.debuglog, value: settings.gracePeriodConnect,
format: v => settings.debuglog ? "On" : "Off", min: 0,
max: 3000,
step: 100,
format: v=>v+"ms",
onchange: v => { onchange: v => {
writeSettings("debuglog",v); writeSettings("gracePeriodConnect",v);
} }
}, },
'Grace periods >': function() { E.showMenu(submenu); } 'Notification': {
}; value: settings.gracePeriodNotification,
min: 0,
var submenu = { max: 3000,
'' : { title: "Grace periods"}, step: 100,
'< Back': function() { E.showMenu(mainmenu); }, format: v=>v+"ms",
'Request': { onchange: v => {
value: settings.gracePeriodRequest, writeSettings("gracePeriodNotification",v);
min: 0, }
max: 3000, },
step: 100, 'Service': {
format: v=>v+"ms", value: settings.gracePeriodService,
onchange: v => { min: 0,
writeSettings("gracePeriodRequest",v); max: 3000,
} step: 100,
}, format: v=>v+"ms",
'Connect': { onchange: v => {
value: settings.gracePeriodConnect, writeSettings("gracePeriodService",v);
min: 0, }
max: 3000, }
step: 100,
format: v=>v+"ms",
onchange: v => {
writeSettings("gracePeriodConnect",v);
}
},
'Notification': {
value: settings.gracePeriodNotification,
min: 0,
max: 3000,
step: 100,
format: v=>v+"ms",
onchange: v => {
writeSettings("gracePeriodNotification",v);
}
},
'Service': {
value: settings.gracePeriodService,
min: 0,
max: 3000,
step: 100,
format: v=>v+"ms",
onchange: v => {
writeSettings("gracePeriodService",v);
}
}
};
E.showMenu(mainmenu);
})
writeSettings("startWithHrm",v);
}
},
'Fallback to HRM': {
value: !!settings.allowFallback,
format: v => settings.allowFallback ? "On" : "Off",
onchange: v => {
writeSettings("allowFallback",v);
}
},
'Fallback Timeout': {
value: settings.fallbackTimeout,
min: 5,
max: 60,
step: 5,
format: v=>v+"s",
onchange: v => {
writeSettings("fallbackTimout",v*1000);
}
},
'Conn. Alert': {
value: !!settings.warnDisconnect,
format: v => settings.warnDisconnect ? "On" : "Off",
onchange: v => {
writeSettings("warnDisconnect",v);
}
},
'Debug log': {
value: !!settings.debuglog,
format: v => settings.debuglog ? "On" : "Off",
onchange: v => {
writeSettings("debuglog",v);
}
},
'Grace periods': function() { E.showMenu(submenu); }
}; };
var submenu = { var submenu = {

View File

@ -1,11 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Make overriding the HRM event optional 0.02: Write available data on reset or kill
Emit BTHRM event for external sensor
Add recorder app plugin
0.03: Prevent readings from internal sensor mixing into BT values
Mark events with src property
Show actual source of event in app
0.04: Allow reading additional data if available: HRM battery and position
Better caching of scanned BT device properties
New setting for not starting the BTHRM together with HRM
Save some RAM by not definining functions if disabled in settings

View File

@ -11,34 +11,30 @@ var currentSlot = 0;
var hrvSlots = [10,20,30,60,120,300]; var hrvSlots = [10,20,30,60,120,300];
var hrvValues = {}; var hrvValues = {};
var rrRmsProgress; var rrRmsProgress;
var saved = false;
var rrNumberOfValues = 0; var rrNumberOfValues = 0;
var rrSquared = 0; var rrSquared = 0;
var rrLastValue var rrLastValue;
var rrMax; var rrMax;
var rrMin; var rrMin;
function calcHrv(rr){ function calcHrv(rr){
//Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/ //Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/
for (currentRr of rr){ for (var currentRr of rr){
if (!rrMax) rrMax = currentRr; if (!rrMax) rrMax = currentRr;
if (!rrMin) rrMin = currentRr; if (!rrMin) rrMin = currentRr;
rrMax = Math.max(rrMax, currentRr); rrMax = Math.max(rrMax, currentRr);
rrMin = Math.min(rrMin, currentRr); rrMin = Math.min(rrMin, currentRr);
//print("Calc for: " + currentRr);
rrNumberOfValues++; rrNumberOfValues++;
if (!rrLastValue){ if (!rrLastValue){
rrLastValue = currentRr; rrLastValue = currentRr;
continue; continue;
} }
rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr); rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr);
//print("rr²: " + rrSquared);
rrLastValue = currentRr; rrLastValue = currentRr;
} }
var rms = Math.sqrt(rrSquared / rrNumberOfValues); var rms = Math.sqrt(rrSquared / rrNumberOfValues);
//print("rms: " + rms);
return rms; return rms;
} }
@ -56,17 +52,36 @@ function draw(y, hrv) {
if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms"; if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms";
g.setFontVector(16).drawString(str,px,y+44+(i*17)); g.setFontVector(16).drawString(str,px,y+44+(i*17));
} }
g.setRotation(3); g.setRotation(3);
g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10); g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10);
g.setRotation(0); g.setRotation(0);
} }
function write(){
if (!hrvValues[hrvSlots[0]]){
return;
}
var file = require('Storage').open("bthrv.csv", "a");
var data = new Date(startingTime).toISOString();
for (var i = 0; i < hrvSlots.length; i++ ){
data += ",";
if (hrvValues[hrvSlots[i]]){
data += hrvValues[hrvSlots[i]];
}
}
data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues;
data += "\n";
file.write(data);
Bangle.buzz(500);
}
function onBtHrm(e) { function onBtHrm(e) {
if (e.rr && !startingTime) Bangle.buzz(500); if (e.rr && !startingTime) Bangle.buzz(500);
if (e.rr && !startingTime) startingTime=Date.now(); if (e.rr && !startingTime) startingTime=Date.now();
//print("Event:" + e.rr);
var hrv = calcHrv(e.rr); var hrv = calcHrv(e.rr);
if (hrv){ if (hrv){
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){ if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
@ -74,35 +89,25 @@ function onBtHrm(e) {
currentSlot++; currentSlot++;
} }
} }
if (!saved && currentSlot == hrvSlots.length){
var file = require('Storage').open("bthrv.csv", "a");
var data = new Date(startingTime).toISOString();
for (var c of hrvSlots){
data+=","+hrvValues[c];
}
data+="," + rrMax + "," + rrMin + ","+rrNumberOfValues;
data+="\n";
file.write(data);
saved = true;
Bangle.buzz(500);
}
if (hrv){ if (hrv){
if (!ui){ if (!ui){
Bangle.setUI("leftright", ()=>{ Bangle.setUI("leftright", ()=>{
resetHrv(); resetHrv();
clear(30); clear(30);
}); });
ui = true; ui = true;
} }
draw(30, hrv); draw(30, hrv);
} }
} }
function resetHrv(){ function resetHrv(){
write();
hrvValues={}; hrvValues={};
startingTime=undefined; startingTime=undefined;
currentSlot=0; currentSlot=0;
saved=false;
rrNumberOfValues = 0; rrNumberOfValues = 0;
rrSquared = 0; rrSquared = 0;
rrLastValue = undefined; rrLastValue = undefined;
@ -117,7 +122,6 @@ g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
if (Bangle.setBTHRMPower){ if (Bangle.setBTHRMPower){
Bangle.on('BTHRM', onBtHrm); Bangle.on('BTHRM', onBtHrm);
Bangle.setBTHRMPower(1,'bthrv'); Bangle.setBTHRMPower(1,'bthrv');
@ -133,6 +137,11 @@ if (Bangle.setBTHRMPower){
file.write(data); file.write(data);
} }
E.on('kill', ()=>{
write();
Bangle.setBTHRMPower(0,'bthrv');
});
g.reset().setFont("6x8",2).setFontAlign(0,0); g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
} else { } else {
@ -140,4 +149,3 @@ if (Bangle.setBTHRMPower){
g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16); g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16);
} }
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrv'));

View File

@ -2,7 +2,7 @@
"id": "bthrv", "id": "bthrv",
"name": "Bluetooth Heart Rate variance calculator", "name": "Bluetooth Heart Rate variance calculator",
"shortName": "BT HRV", "shortName": "BT HRV",
"version": "0.01", "version": "0.02",
"description": "Calculates HRV from a a BT HRM with interval data", "description": "Calculates HRV from a a BT HRM with interval data",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",

View File

@ -19,3 +19,4 @@
Colors of circles can be configured Colors of circles can be configured
Color depending on value (green -> red, red -> green) option Color depending on value (green -> red, red -> green) option
Good HRM value will not be overwritten so fast anymore Good HRM value will not be overwritten so fast anymore
0.10: Use roboto font for time, date and day of week and center align them

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
{
"minHR": 40,
"maxHR": 200,
"confidence": 0,
"stepGoal": 10000,
"stepDistanceGoal": 8000,
"stepLength": 0.8,
"batteryWarn": 30,
"showWidgets": false,
"weatherCircleData": "humidity",
"circleCount": 3,
"circle1": "hr",
"circle2": "steps",
"circle3": "battery",
"circle4": "weather",
"circle1color": "green-red",
"circle2color": "#0000ff",
"circle3color": "red-green",
"circle4color": "#ffff00",
"circle1colorizeIcon": true,
"circle2colorizeIcon": true,
"circle3colorizeIcon": true,
"circle4colorizeIcon": false,
"hrmValidity": 60
}

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock", { "id": "circlesclock",
"name": "Circles clock", "name": "Circles clock",
"shortName":"Circles clock", "shortName":"Circles clock",
"version":"0.09", "version":"0.10",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
@ -13,7 +13,8 @@
"storage": [ "storage": [
{"name":"circlesclock.app.js","url":"app.js"}, {"name":"circlesclock.app.js","url":"app.js"},
{"name":"circlesclock.img","url":"app-icon.js","evaluate":true}, {"name":"circlesclock.img","url":"app-icon.js","evaluate":true},
{"name":"circlesclock.settings.js","url":"settings.js"} {"name":"circlesclock.settings.js","url":"settings.js"},
{"name":"circlesclock.default.json","url":"default.json"}
], ],
"data": [ "data": [
{"name":"circlesclock.json"} {"name":"circlesclock.json"}

View File

@ -1,7 +1,11 @@
(function(back) { (function(back) {
const SETTINGS_FILE = "circlesclock.json"; const SETTINGS_FILE = "circlesclock.json";
const storage = require('Storage'); const storage = require('Storage');
let settings = storage.readJSON(SETTINGS_FILE, 1) || {}; let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
function save(key, value) { function save(key, value) {
settings[key] = value; settings[key] = value;
storage.write(SETTINGS_FILE, settings); storage.write(SETTINGS_FILE, settings);
@ -10,8 +14,8 @@
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"]; const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"]; const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", "cyan", "white", "black", "green->red", "red->green"];
const weatherData = ["empty", "humidity", "wind"]; const weatherData = ["empty", "humidity", "wind"];
@ -20,7 +24,7 @@
'': { 'title': 'Circles clock' }, '': { 'title': 'Circles clock' },
/*LANG*/'< Back': back, /*LANG*/'< Back': back,
/*LANG*/'circle count': { /*LANG*/'circle count': {
value: "circleCount" in settings ? settings.circleCount : 3, value: settings.circleCount,
min: 3, min: 3,
max : 4, max : 4,
step: 1, step: 1,
@ -33,7 +37,7 @@
/*LANG*/'heartrate': ()=>showHRMenu(), /*LANG*/'heartrate': ()=>showHRMenu(),
/*LANG*/'steps': ()=>showStepMenu(), /*LANG*/'steps': ()=>showStepMenu(),
/*LANG*/'battery warn': { /*LANG*/'battery warn': {
value: "batteryWarn" in settings ? settings.batteryWarn : 30, value: settings.batteryWarn,
min: 10, min: 10,
max : 100, max : 100,
step: 10, step: 10,
@ -43,12 +47,12 @@
onchange: x => save('batteryWarn', x), onchange: x => save('batteryWarn', x),
}, },
/*LANG*/'show widgets': { /*LANG*/'show widgets': {
value: "showWidgets" in settings ? settings.showWidgets : false, value: !!settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'), format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x), onchange: x => save('showWidgets', x),
}, },
/*LANG*/'weather circle': { /*LANG*/'weather data': {
value: settings.weatherCircleData ? weatherData.indexOf(settings.weatherCircleData) : 1, value: weatherData.indexOf(settings.weatherCircleData),
min: 0, max: 2, min: 0, max: 2,
format: v => weatherData[v], format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]), onchange: x => save('weatherCircleData', weatherData[x]),
@ -62,7 +66,7 @@
'': { 'title': /*LANG*/'Heartrate' }, '': { 'title': /*LANG*/'Heartrate' },
/*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'minimum': { /*LANG*/'minimum': {
value: "minHR" in settings ? settings.minHR : 40, value: settings.minHR,
min: 0, min: 0,
max : 250, max : 250,
step: 5, step: 5,
@ -72,7 +76,7 @@
onchange: x => save('minHR', x), onchange: x => save('minHR', x),
}, },
/*LANG*/'maximum': { /*LANG*/'maximum': {
value: "maxHR" in settings ? settings.maxHR : 200, value: settings.maxHR,
min: 20, min: 20,
max : 250, max : 250,
step: 5, step: 5,
@ -82,7 +86,7 @@
onchange: x => save('maxHR', x), onchange: x => save('maxHR', x),
}, },
/*LANG*/'min. confidence': { /*LANG*/'min. confidence': {
value: "confidence" in settings ? settings.confidence : 0, value: settings.confidence,
min: 0, min: 0,
max : 100, max : 100,
step: 10, step: 10,
@ -92,7 +96,7 @@
onchange: x => save('confidence', x), onchange: x => save('confidence', x),
}, },
/*LANG*/'valid period': { /*LANG*/'valid period': {
value: "hrmValidity" in settings ? settings.hrmValidity : 30, value: settings.hrmValidity,
min: 10, min: 10,
max : 600, max : 600,
step: 10, step: 10,
@ -110,7 +114,7 @@
'': { 'title': /*LANG*/'Steps' }, '': { 'title': /*LANG*/'Steps' },
/*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'goal': { /*LANG*/'goal': {
value: "stepGoal" in settings ? settings.stepGoal : 10000, value: settings.stepGoal,
min: 2000, min: 2000,
max : 50000, max : 50000,
step: 2000, step: 2000,
@ -120,7 +124,7 @@
onchange: x => save('stepGoal', x), onchange: x => save('stepGoal', x),
}, },
/*LANG*/'distance goal': { /*LANG*/'distance goal': {
value: "stepDistanceGoal" in settings ? settings.stepDistanceGoal : 8000, value: settings.stepDistanceGoal,
min: 2000, min: 2000,
max : 30000, max : 30000,
step: 1000, step: 1000,
@ -130,7 +134,7 @@
onchange: x => save('stepDistanceGoal', x), onchange: x => save('stepDistanceGoal', x),
}, },
/*LANG*/'step length': { /*LANG*/'step length': {
value: "stepLength" in settings ? settings.stepLength : 0.8, value: settings.stepLength,
min: 0.1, min: 0.1,
max : 1.5, max : 1.5,
step: 0.01, step: 0.01,
@ -142,9 +146,6 @@
}; };
E.showMenu(menu); E.showMenu(menu);
} }
const defaultCircleTypes = ["steps", "hr", "battery", "weather"];
function showCircleMenu(circleId) { function showCircleMenu(circleId) {
const circleName = "circle" + circleId; const circleName = "circle" + circleId;
const colorKey = circleName + "color"; const colorKey = circleName + "color";
@ -154,19 +155,19 @@
'': { 'title': /*LANG*/'Circle ' + circleId }, '': { 'title': /*LANG*/'Circle ' + circleId },
/*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'data': { /*LANG*/'data': {
value: settings[circleName]!=undefined ? valuesCircleTypes.indexOf(settings[circleName]) : valuesCircleTypes.indexOf(defaultCircleTypes[circleId -1]), value: valuesCircleTypes.indexOf(settings[circleName]),
min: 0, max: valuesCircleTypes.length - 1, min: 0, max: valuesCircleTypes.length - 1,
format: v => namesCircleTypes[v], format: v => namesCircleTypes[v],
onchange: x => save(circleName, valuesCircleTypes[x]), onchange: x => save(circleName, valuesCircleTypes[x]),
}, },
/*LANG*/'color': { /*LANG*/'color': {
value: settings[colorKey] ? valuesColors.indexOf(settings[colorKey]) : 0, value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1, min: 0, max: valuesColors.length - 1,
format: v => namesColors[v], format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]), onchange: x => save(colorKey, valuesColors[x]),
}, },
/*LANG*/'colorize icon': { /*LANG*/'colorize icon': {
value: colorizeIconKey in settings ? settings[colorizeIconKey] : false, value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'), format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
onchange: x => save(colorizeIconKey, x), onchange: x => save(colorizeIconKey, x),
}, },

View File

@ -3,3 +3,4 @@
0.21: Fixed settings menu, four more fonts 0.21: Fixed settings menu, four more fonts
0.22: Changed timing code, original "Nunito" Font is back! 0.22: Changed timing code, original "Nunito" Font is back!
0.23: Customizer! Unused fonts no longer take up precious memory. 0.23: Customizer! Unused fonts no longer take up precious memory.
0.24: Added previews to the customizer.

View File

@ -1,70 +1,74 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
<style>
input[type=checkbox] {
opacity:0;
}
input[type=checkbox] + label {
opacity:0.2;
}
input[type=checkbox]:checked + label {
opacity:1;
}
</style>
</head> </head>
<body> <body>
<h3>&nbsp;&nbsp;&nbsp;Select Fonts to upload:</h3>
<form> <form>
<input type="checkbox" id="BarlowCond"> <input type="checkbox" id="BarlowCond">
<label for="BarlowCond">Barlow Condensed</label><br> <label for="BarlowCond"><img src="fonts/BarlowCond-p1.png"> <img src="fonts/BarlowCond-p2.png"></label><br>
<input type="checkbox" id="BebasNeue"> <input type="checkbox" id="BebasNeue">
<label for="BebasNeue">Bebas Neue</label><br> <label for="BebasNeue"><img src="fonts/BebasNeue-p1.png"> <img src="fonts/BebasNeue-p2.png"></label><br>
<input type="checkbox" id="Dekko"> <input type="checkbox" id="Dekko">
<label for="Dekko">Dekko</label><br> <label for="Dekko"><img src="fonts/Dekko-p1.png"> <img src="fonts/Dekko-p2.png"></label><br>
<input type="checkbox" id="DinAlternate"> <input type="checkbox" id="DinAlternate">
<label for="DinAlternate">Din Alternate</label><br> <label for="DinAlternate"><img src="fonts/DinAlternate-p1.png"> <img src="fonts/DinAlternate-p2.png"></label><br>
<input type="checkbox" id="Dosis">
<label for="Dosis">Dosis</label><br>
<input type="checkbox" id="Impact"> <input type="checkbox" id="Impact">
<label for="Impact">Impact</label><br> <label for="Impact"><img src="fonts/Impact-p1.png"> <img src="fonts/Impact-p2.png"></label><br>
<input type="checkbox" id="Nunito"> <input type="checkbox" id="Nunito">
<label for="Nunito">Nunito</label><br> <label for="Nunito"><img src="fonts/Nunito-p1.png"> <img src="fonts/Nunito-p2.png"></label><br>
<input type="checkbox" id="OpenSansEC"> <input type="checkbox" id="OpenSansEC">
<label for="OpenSansEC">Open Sans Extra Condensed</label><br> <label for="OpenSansEC"><img src="fonts/OpenSansEC-p1.png"> <img src="fonts/OpenSansEC-p2.png"></label><br>
<input type="checkbox" id="Phosphate"> <input type="checkbox" id="Phosphate">
<label for="Phosphate">Phosphate</label><br> <label for="Phosphate"><img src="fonts/Phosphate-p1.png"> <img src="fonts/Phosphate-p2.png"></label><br>
<input type="checkbox" id="Quicksand"> <input type="checkbox" id="Quicksand">
<label for="Quicksand">Quicksand</label><br> <label for="Quicksand"><img src="fonts/Quicksand-p1.png"> <img src="fonts/Quicksand-p2.png"></label><br>
<input type="checkbox" id="SairaCond">
<label for="SairaCond">Saira Condensed</label><br>
<input type="checkbox" id="SairaEC"> <input type="checkbox" id="SairaEC">
<label for="SairaEC">Saira Extra Condensed</label><br> <label for="SairaEC"><img src="fonts/SairaEC-p1.png"> <img src="fonts/SairaEC-p2.png"></label><br>
<input type="checkbox" id="Teko"> <input type="checkbox" id="Teko">
<label for="Teko">Teko</label><br> <label for="Teko"><img src="fonts/Teko-p1.png"> <img src="fonts/Teko-p2.png"></label><br>
<input type="checkbox" id="Yumaro"> <input type="checkbox" id="Yumaro">
<label for="Yumaro">Yumaro</label><br> <label for="Yumaro"><img src="fonts/Yumaro-p1.png"> <img src="fonts/Yumaro-p2.png"></label><br>
<input type="checkbox" id="YuseiMagic"> <input type="checkbox" id="YuseiMagic">
<label for="YuseiMagic">Yusei Magic</label><br> <label for="YuseiMagic"><img src="fonts/YuseiMagic-p1.png"> <img src="fonts/YuseiMagic-p2.png"></label><br>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p> <p><button id="upload" class="btn btn-primary">Upload selected Fonts</button></p>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
<script> <script>
FontList = ["BarlowCond", "BebasNeue", "Dekko", "DinAlternate", "Dosis", FontList = ["BarlowCond", "BebasNeue", "Dekko", "DinAlternate",
"Impact", "Nunito", "OpenSansEC", "Phosphate", "Quicksand", "SairaCond", "SairaEC", "Impact", "Nunito", "OpenSansEC", "Phosphate", "Quicksand", "SairaEC",
"Yumaro", "YuseiMagic"] "Yumaro", "YuseiMagic"]
// When the 'upload' button is clicked... // When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function() { document.getElementById("upload").addEventListener("click", function() {
var n=0; var n=0;
var fonts = []; var fonts = [];
for (fontName of FontList) { for (fontName of FontList) {
if (document.getElementById(fontName).checked==true) { if (document.getElementById(fontName).checked==true) {
var f = new Object(); var f = new Object();
f.name="contourclock-"+n+".json"; fonts.push({
f.url="font-"+fontName+".json"; name:"contourclock-"+n+".json",
//fonts.push({name:"contourclock-"+n+".json", url:"font-"+fontName+".json"}); url:"font-"+fontName+".json"
fonts.push(f); });
//console.log("contourclock-"+n+".json <- font-"+fontName+".json"); n++;
n++; }
} }
} if (n>0)
//console.log(fonts[0]); sendCustomizedApp({storage:fonts});
sendCustomizedApp(storage=fonts); else
/*sendCustomizedApp({ alert("Please select at least one Font!");
storage:[
{name:"myapp.app.js", url:"app.js", content:app},
]
});*/
});
</script>
});
</script>
</form>
</body> </body>
</html> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

@ -0,0 +1 @@

View File

@ -1,13 +1,12 @@
{ "id": "contourclock", { "id": "contourclock",
"name": "Contour Clock", "name": "Contour Clock",
"shortName" : "Contour Clock", "shortName" : "Contour Clock",
"version":"0.23", "version":"0.24",
"icon": "app.png", "icon": "app.png",
"description": "A Minimalist clockface with large Digits. Now with more fonts!", "description": "A Minimalist clockface with large Digits. Now with more fonts!",
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],
"tags": "clock", "tags": "clock",
"custom": "custom.html", "custom": "custom.html",
"allow_emulator":true,
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],
"type": "clock", "type": "clock",
"storage": [ "storage": [

View File

@ -5,3 +5,4 @@
0.05: add Bangle 2 version 0.05: add Bangle 2 version
0.06: Adds settings page (hide clocks or launchers) 0.06: Adds settings page (hide clocks or launchers)
0.07: Adds setting for directly launching app on touch for Bangle 2 0.07: Adds setting for directly launching app on touch for Bangle 2
0.08: Optimize line wrapping for Bangle 2

View File

@ -45,11 +45,23 @@ function draw_icon(p,n,selected) {
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
g.setFontAlign(0,-1,0).setFont("6x8",1); g.setFontAlign(0,-1,0).setFont("6x8",1);
var txt = apps[p*4+n].name.split(" "); var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
for (var i = 0; i < txt.length; i++) { var lineY = 0;
txt[i] = txt[i].trim(); var line = "";
g.drawString(txt[i],x+36,y+54+i*8); while (txt.length > 0){
var c = txt.shift();
if (c.length + 1 + line.length > 13){
if (line.length > 0){
g.drawString(line.trim(),x+36,y+54+lineY*8);
lineY++;
}
line = c;
} else {
line += " " + c;
}
} }
g.drawString(line.trim(),x+36,y+54+lineY*8);
} }
function drawPage(p){ function drawPage(p){

View File

@ -1,7 +1,7 @@
{ {
"id": "dtlaunch", "id": "dtlaunch",
"name": "Desktop Launcher", "name": "Desktop Launcher",
"version": "0.07", "version": "0.08",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png", "icon": "icon.png",

View File

@ -6,3 +6,4 @@
0.06: Bangle.js 2 support 0.06: Bangle.js 2 support
0.07: Fix "previous" button image 0.07: Fix "previous" button image
0.08: Fix scrolling title background color 0.08: Fix scrolling title background color
0.09: Move event listener from widget to boot code, stops music from showing up in messages

View File

@ -175,10 +175,8 @@ function rIcon(l) {
} }
let layout; let layout;
function makeUI() { function makeUI() {
global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
delete (global.gbmusic_active);
const Layout = require("Layout"); const Layout = require("Layout");
layout = new Layout({ layout = new Layout({
type: "v", c: [ type: "v", c: [
@ -331,7 +329,7 @@ function formatNum(info) {
* Update music info * Update music info
* @param {Object} info - Gadgetbridge musicinfo event * @param {Object} info - Gadgetbridge musicinfo event
*/ */
function musicInfo(info) { function info(info) {
scrollStop(); scrollStop();
layout.title.label = info.track || ""; layout.title.label = info.track || "";
layout.album.label = info.album || ""; layout.album.label = info.album || "";
@ -360,7 +358,7 @@ let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long
* Update music state * Update music state
* @param {Object} e - Gadgetbridge musicstate event * @param {Object} e - Gadgetbridge musicstate event
*/ */
function musicState(e) { function state(e) {
stat = e.state; stat = e.state;
// if paused for five minutes, load the clock // if paused for five minutes, load the clock
// (but timeout resets if we get new info, even while paused) // (but timeout resets if we get new info, even while paused)
@ -584,8 +582,8 @@ function startEmulator() {
println: (line) => {console.log("Bluetooth:", line);}, println: (line) => {console.log("Bluetooth:", line);},
}; };
// some example info // some example info
GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); info({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2});
GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); state({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1});
} }
} }
function startWatches() { function startWatches() {
@ -596,25 +594,6 @@ function startWatches() {
function start() { function start() {
makeUI(); makeUI();
// start listening for music updates
const _GB = global.GB;
global.GB = (event) => {
// we eat music events!
switch(event.t) {
case "musicinfo":
musicInfo(event);
break;
case "musicstate":
musicState(event);
break;
default:
// pass on other events
if (_GB) {
setTimeout(_GB, 0, event);
}
return;
}
};
startWatches(); startWatches();
tick(); tick();
startEmulator(); startEmulator();
@ -625,11 +604,11 @@ function init() {
let saved = require("Storage").readJSON("gbmusic.load.json", true); let saved = require("Storage").readJSON("gbmusic.load.json", true);
require("Storage").erase("gbmusic.load.json"); require("Storage").erase("gbmusic.load.json");
if (saved) { if (saved) {
// autoloaded: load state was saved by widget // autoloaded: load state as saved by widget
auto = true; auto = true;
start(); start();
musicInfo(saved.info); info(saved.info);
musicState(saved.state); state(saved.state);
return; return;
} }

37
apps/gbmusic/boot.js Normal file
View File

@ -0,0 +1,37 @@
setTimeout( // make other boot code run first, so we override e.g. android.boot.js GB
() => {
const APP = global.__FILE__==="gbmusic.app.js",
a = !!(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart;
let s, i; // state, info
/**
* Save current song and check if we want to load the gbmusic app
*
* Only runs while other apps are loaded
*/
function check() {
if (s!=="play" || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled
delete (i.t);
// store info and launch music app
require("Storage").writeJSON("gbmusic.load.json", {
state: s,
info: i,
});
load("gbmusic.app.js");
}
global.GB = (_GB => e => {
// we eat music events!
switch(e.t) {
case "musicinfo":
i = e;
return APP ? info(e) : check();
case "musicstate":
s = e.state;
return APP ? state(e) : check();
default:
// pass on other events
if (_GB) setTimeout(_GB, 0, e);
}
})(global.GB);
}, 1);

View File

@ -2,7 +2,7 @@
"id": "gbmusic", "id": "gbmusic",
"name": "Gadgetbridge Music Controls", "name": "Gadgetbridge Music Controls",
"shortName": "Music Controls", "shortName": "Music Controls",
"version": "0.08", "version": "0.09",
"description": "Control the music on your Gadgetbridge-connected phone", "description": "Control the music on your Gadgetbridge-connected phone",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}],
@ -14,7 +14,7 @@
"storage": [ "storage": [
{"name":"gbmusic.app.js","url":"app.js"}, {"name":"gbmusic.app.js","url":"app.js"},
{"name":"gbmusic.settings.js","url":"settings.js"}, {"name":"gbmusic.settings.js","url":"settings.js"},
{"name":"gbmusic.wid.js","url":"widget.js"}, {"name":"gbmusic.boot.js","url":"boot.js"},
{"name":"gbmusic.img","url":"icon.js","evaluate":true} {"name":"gbmusic.img","url":"icon.js","evaluate":true}
], ],
"data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}] "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}]

View File

@ -1,44 +0,0 @@
(() => {
if (global.gbmusic_active || !(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart) {
return;
}
if (typeof __FILE__ === 'string') { // only exists since 2v09
const info = require("Storage").readJSON(__FILE__.split(".")[0]+".info", 1) || false;
if (info && info.type!=="clock") { // info can have no type (but then it isn't a clock)
return;
}
}
let state, info;
function checkMusic() {
if (state!=="play" || !info) {
return;
}
// playing music: launch music app
require("Storage").writeJSON("gbmusic.load.json", {
state: state,
info: info,
});
load("gbmusic.app.js");
}
const _GB = global.GB;
global.GB = (event) => {
// we eat music events!
switch(event.t) {
case "musicinfo":
info = event;
delete (info.t);
checkMusic();
break;
case "musicstate":
state = event.state;
checkMusic();
break;
default:
if (_GB) {
setTimeout(_GB, 0, event);
}
}
};
})();

View File

@ -107,3 +107,8 @@ try {
* Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) * Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control)
and [here](https://github.com/thasti/utrak/blob/master/gps.c) and [here](https://github.com/thasti/utrak/blob/master/gps.c)
Written by: [Hugh Barney, with support from Gordon Williams](https://github.com/hughbarney) For support
and discussion please post in the [Bangle JS
Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -280,3 +280,8 @@ The following error codes will be displayed if one of the dependancies is not me
* Add a small graph to the heart rate monitor app * Add a small graph to the heart rate monitor app
* Add a facility to call the Arrow calibration process * Add a facility to call the Arrow calibration process
* Maybe create waypoints.json file if missing * Maybe create waypoints.json file if missing
Written by: [Hugh Barney](https://github.com/hughbarney) For support
and discussion please post in the [Bangle JS
Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -14,4 +14,5 @@
0.14: Added altitude as an option to display. 0.14: Added altitude as an option to display.
0.15: Using wpedom to count steps. 0.15: Using wpedom to count steps.
0.16: Improved stability. Wind can now be shown. 0.16: Improved stability. Wind can now be shown.
0.17: Settings for mph/kph and other minor improvements. 0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.

View File

@ -11,7 +11,7 @@ with Gadgetbride and the weather app must be installed.
## Features ## Features
* LCARS Style watch face. * LCARS Style watch face.
* Full screen mode - widgets are still loaded but not shown. * Enable or disable fullscreen mode (widgets are always loaded, but hidden if fullscreen).
* Tab on left/right to switch between different screens. * Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.) * Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random and real images of planets. * Shows random and real images of planets.
@ -33,7 +33,7 @@ with Gadgetbride and the weather app must be installed.
## Multiple screens support ## Multiple screens support
Access different screens via tap on the left/ right side of the screen Access different screens via tap on the left/ right side of the screen
![](screenshot.png) ![](screenshot_1.png)
![](screenshot_2.png) ![](screenshot_2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

View File

@ -7,6 +7,7 @@ let settings = {
dataRow2: "Temp", dataRow2: "Temp",
dataRow3: "Battery", dataRow3: "Battery",
speed: "kph", speed: "kph",
fullscreen: false,
}; };
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) { for (const key in saved_settings) {
@ -30,23 +31,39 @@ let lcarsViewPos = 0;
// let hrmValue = 0; // let hrmValue = 0;
var plotMonth = false; var plotMonth = false;
/* /*
* Requirements and globals * Requirements and globals
*/ */
var bgLeft = { var bgLeftFullscreen = {
width : 27, height : 176, bpp : 3, width : 27, height : 176, bpp : 3,
transparent : 0, transparent : 0,
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A==")) buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAFCh/eX5Q/KAwdCAGVbtu27YCCoAJBkuWrNlAQRGCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))
}; };
var bgRight = { var bgLeftNotFullscreen = {
width : 27, height : 152, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AAUM2XLlgCCwAJBBAuy4EAmQIF5cggAIGlmwgYIG2XIF42wF4ImGF4ImHJoQmGJoQdJhZNHNY47CgRNGBIJZHHgRiGBIRQ/KH5QCAGVbtu27YCCoAJBkuWrNlAQRkCiwRDAQPQBIMJCIYCBsAJBgomEtu0WoQmEy1YBIMBHYttIwQ7FyxQ/KHFlFAQ7F2weCHYplKChRTCCg5TCHw5TMAD0GzVp0wCCBBGaBIMaBAtpwECBA2mwEJBAugDgMmCIwJBF5EABAtoeQQvGCYQdPJoI7LMQzTCLJKAGzAJBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A="))
};
var bgRightFullscreen = {
width : 27, height : 176, bpp : 3, width : 27, height : 176, bpp : 3,
transparent : 0, transparent : 0,
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA=")) buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAnUP7y/KH4yGeVYAJrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgAA="))
}; };
var bgRightNotFullscreen = {
width : 27, height : 152, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress(atob("lmy5YCDBIUyBAmy5AJBhYUG2EAhgIFAQMAgQIGCgQABCg4ABEAwUNFI2AKZHAKZEgGRZTGOIUDQxJxGKH5Q/agwAxrdt23bAQVABIMly1ZsoCCMgUWCIYCB6AJBhIRDAQNgBIMFEwlt2i1CEwmWrAJBgI7FtpGCHYuWKH5QxEwpQDlo7F0A7IqBZBEwo7BCIwCBJo53CJoxiCJpIAdgOmzVpAQR/CgAIEAQJ2CBAoCBBIMmCg1oD4QLGFQUCCjQ+CKYw+CKY4JCKYwoCGRMaGREJDoroCgwdFzBlLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJox/CgA="))
};
var bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen;
var bgRight= settings.fullscreen ? bgRightFullscreen : bgRightNotFullscreen;
var iconEarth = { var iconEarth = {
width : 50, height : 50, bpp : 3, width : 50, height : 50, bpp : 3,
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA=")) buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
@ -217,7 +234,7 @@ function drawHorizontalBgLine(color, x1, x2, y, h){
function drawInfo(){ function drawInfo(){
if(lcarsViewPos != 0){ if(lcarsViewPos != 0 || !settings.fullscreen){
return; return;
} }
@ -276,9 +293,10 @@ function drawState(){
function drawPosition0(){ function drawPosition0(){
// Draw background image // Draw background image
g.drawImage(bgLeft, 0, 0); var offset = settings.fullscreen ? 0 : 24;
drawHorizontalBgLine(cBlue, 25, 120, 0, 4); g.drawImage(bgLeft, 0, offset);
drawHorizontalBgLine(cBlue, 130, 176, 0, 4); drawHorizontalBgLine(cBlue, 25, 120, offset, 4);
drawHorizontalBgLine(cBlue, 130, 176, offset, 4);
drawHorizontalBgLine(cPurple, 20, 70, 80, 4); drawHorizontalBgLine(cPurple, 20, 70, 80, 4);
drawHorizontalBgLine(cPurple, 80, 176, 80, 4); drawHorizontalBgLine(cPurple, 80, 176, 80, 4);
drawHorizontalBgLine(cOrange, 35, 110, 87, 4); drawHorizontalBgLine(cOrange, 35, 110, 87, 4);
@ -304,15 +322,26 @@ function drawPosition0(){
var currentDate = new Date(); var currentDate = new Date();
var timeStr = locale.time(currentDate,1); var timeStr = locale.time(currentDate,1);
g.setFontAntonioLarge(); g.setFontAntonioLarge();
g.drawString(timeStr, 27, 10); if(settings.fullscreen){
g.drawString(timeStr, 27, 10);
} else {
g.drawString(timeStr, 27, 33);
}
// Write date // Write date
g.setColor(cWhite); g.setColor(cWhite);
g.setFontAntonioMedium(); g.setFontAntonioMedium();
var dayStr = locale.dow(currentDate, true).toUpperCase(); if(settings.fullscreen){
dayStr += " " + currentDate.getDate(); var dayStr = locale.dow(currentDate, true).toUpperCase();
dayStr += " " + locale.month(currentDate, 1).toUpperCase(); dayStr += " " + currentDate.getDate();
g.drawString(dayStr, 30, 56); dayStr += " " + locale.month(currentDate, 1).toUpperCase();
g.drawString(dayStr, 30, 56);
} else {
var dayStr = locale.dow(currentDate, true).toUpperCase();
var date = currentDate.getDate();
g.drawString(dayStr, 128, 35);
g.drawString(date, 128, 55);
}
// Draw data // Draw data
g.setFontAlign(-1, -1, 0); g.setFontAlign(-1, -1, 0);
@ -327,8 +356,11 @@ function drawPosition0(){
function drawPosition1(){ function drawPosition1(){
// Draw background image // Draw background image
g.drawImage(bgRight, 149, 0); var offset = settings.fullscreen ? 0 : 24;
drawHorizontalBgLine(cBlue, 0, 140, 0, 4); g.drawImage(bgRight, 149, offset);
if(settings.fullscreen){
drawHorizontalBgLine(cBlue, 0, 140, offset, 4);
}
drawHorizontalBgLine(cPurple, 0, 80, 80, 4); drawHorizontalBgLine(cPurple, 0, 80, 80, 4);
drawHorizontalBgLine(cPurple, 90, 150, 80, 4); drawHorizontalBgLine(cPurple, 90, 150, 80, 4);
drawHorizontalBgLine(cOrange, 0, 50, 87, 4); drawHorizontalBgLine(cOrange, 0, 50, 87, 4);
@ -388,8 +420,13 @@ function drawPosition1(){
g.setFontAlign(1, 1, 0); g.setFontAlign(1, 1, 0);
g.setFontAntonioMedium(); g.setFontAntonioMedium();
g.setColor(cWhite); g.setColor(cWhite);
g.drawString("M-HRM", 154, 27);
g.drawString("M-STEPS [K]", 154, 115); if(settings.fullscreen){
g.drawString("M-HRM", 154, 27);
g.drawString("M-STEPS [K]", 154, 115);
} else {
g.drawString("MONTH", 154, 115);
}
// Plot day // Plot day
} else { } else {
@ -429,8 +466,13 @@ function drawPosition1(){
g.setFontAlign(1, 1, 0); g.setFontAlign(1, 1, 0);
g.setFontAntonioMedium(); g.setFontAntonioMedium();
g.setColor(cWhite); g.setColor(cWhite);
g.drawString("D-HRM", 154, 27);
g.drawString("D-STEPS", 154, 115); if(settings.fullscreen){
g.drawString("D-HRM", 154, 27);
g.drawString("D-STEPS", 154, 115);
} else {
g.drawString("DAY", 154, 115);
}
} }
} }
@ -451,6 +493,13 @@ function draw(){
} else if (lcarsViewPos == 1) { } else if (lcarsViewPos == 1) {
drawPosition1(); drawPosition1();
} }
// After drawing the watch face, we can draw the widgets
if(settings.fullscreen){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else {
Bangle.drawWidgets();
}
} }
@ -501,8 +550,9 @@ function getWeather(){
weather.hum = weather.hum + "%"; weather.hum = weather.hum + "%";
// Wind // Wind
var speedFactor = settings.speed == "kph" ? 1.60934 : 1.0; const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
weather.wind = Math.round(weather.wind * speedFactor); var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934;
weather.wind = Math.round(wind[1] * speedFactor);
return weather return weather
} }
@ -652,16 +702,7 @@ Bangle.on('touch', function(btn, e){
// Show launcher when middle button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
/*
* we are not drawing the widgets as we are taking over the whole screen
* so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared.
*/
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
// Clear the screen once, at startup and draw clock // Clear the screen once, at startup and draw clock
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
draw(); draw();
// After drawing the watch face, we can draw the widgets
// Bangle.drawWidgets();

View File

@ -9,6 +9,7 @@
dataRow2: "Steps", dataRow2: "Steps",
dataRow3: "Temp", dataRow3: "Temp",
speed: "kph", speed: "kph",
fullscreen: false,
}; };
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) { for (const key in saved_settings) {
@ -52,6 +53,14 @@
save(); save();
}, },
}, },
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
},
'Speed': { 'Speed': {
value: 0 | speedOptions.indexOf(settings.speed), value: 0 | speedOptions.indexOf(settings.speed),
min: 0, max: 1, min: 0, max: 1,

View File

@ -3,13 +3,15 @@
"name": "LCARS Clock", "name": "LCARS Clock",
"shortName":"LCARS", "shortName":"LCARS",
"icon": "lcars.png", "icon": "lcars.png",
"version":"0.17", "version":"0.18",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.", "description": "Library Computer Access Retrieval System (LCARS) clock.",
"type": "clock", "type": "clock",
"tags": "clock", "tags": "clock",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [
{"url":"screenshot_1.png"},
{"url":"screenshot_3.png"}],
"storage": [ "storage": [
{"name":"lcars.app.js","url":"lcars.app.js"}, {"name":"lcars.app.js","url":"lcars.app.js"},
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true}, {"name":"lcars.img","url":"lcars.icon.js","evaluate":true},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

BIN
apps/lcars/screenshot_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
apps/lcars/screenshot_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -30,3 +30,4 @@
If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267)
0.19: Use a larger font for message text if it'll fit 0.19: Use a larger font for message text if it'll fit
0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) 0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031)
0.21: Improve list readability on dark theme

View File

@ -27,7 +27,7 @@ var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
var colBg = g.theme.dark ? "#141":"#4f4"; var colBg = g.theme.dark ? "#141":"#4f4";
var colSBg1 = g.theme.dark ? "#121":"#cFc"; var colSBg1 = g.theme.dark ? "#121":"#cFc";
var colSBg2 = g.theme.dark ? "#242":"#9F9"; var colSBg2 = g.theme.dark ? "#000":"#9F9";
// hack for 2v10 firmware's lack of ':size' font handling // hack for 2v10 firmware's lack of ':size' font handling
try { try {
g.setFont("6x8:2"); g.setFont("6x8:2");

View File

@ -1,7 +1,7 @@
{ {
"id": "messages", "id": "messages",
"name": "Messages", "name": "Messages",
"version": "0.20", "version": "0.21",
"description": "App to display notifications from iOS and Gadgetbridge", "description": "App to display notifications from iOS and Gadgetbridge",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",

View File

@ -1,2 +1,3 @@
0.01: First release 0.01: First release
0.02: Enhanced icon, make it bolder 0.02: Enhanced icon, make it bolder
0.03: Fixed issue with defaulting back to London

View File

@ -4,7 +4,7 @@
"icon": "mylocation.png", "icon": "mylocation.png",
"type": "app", "type": "app",
"screenshots": [{"url":"screenshot_1.png"}], "screenshots": [{"url":"screenshot_1.png"}],
"version":"0.02", "version":"0.03",
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
"readme": "README.md", "readme": "README.md",
"tags": "tool,utility", "tags": "tool,utility",

View File

@ -9,32 +9,35 @@ let s = {
'lat': 51.5072, 'lat': 51.5072,
'lon': 0.1276, 'lon': 0.1276,
'location': "London" 'location': "London"
} };
function loadSettings() { function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || s; settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
for (const key in settings) {
s[key] = settings[key]
}
} }
function save() { function save() {
settings = s settings = s;
require('Storage').write(SETTINGS_FILE, settings) require('Storage').write(SETTINGS_FILE, settings);
} }
const locations = ["London", "Newcastle", "Edinburgh", "Paris", "New York", "Tokyo","???"]; const locations = ["London" ,"Newcastle","Edinburgh", "Paris" , "New York" , "Tokyo" , "Frankfurt", "Auckland", "???"];
const lats = [51.5072 ,54.9783 ,55.9533 ,48.8566 ,40.7128 ,35.6762, 0.0]; const lats = [ 51.5072 , 54.9783 , 55.9533 , 48.8566 , 40.7128 , 35.6762 , 50.1236 , -36.9 , 0.0 ];
const lons = [-0.1276 ,-1.6178 ,-3.1883 ,2.3522 , -74.0060 ,139.6503, 0.0]; const lons = [ -0.1276 , -1.6178 , -3.1883 , 2.3522 , -74.0060 , 139.6503 , 8.6553 , 174.7832 , 0.0 ];
function setFromGPS() { function setFromGPS() {
Bangle.on('GPS', (gps) => { Bangle.on('GPS', (gps) => {
//console.log("."); //console.log(".");
if (gps.fix === 0) return; if (gps.fix === 0) return;
//console.log("fix from GPS"); //console.log("fix from GPS");
s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' } s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' };
Bangle.buzz(1500); // buzz on first position Bangle.buzz(1500); // buzz on first position
Bangle.setGPSPower(0); Bangle.setGPSPower(0);
save(); save();
Bangle.setUI("updown", ()=>{ load() }); Bangle.setUI("updown", ()=>{ load(); });
E.showPrompt("Location has been saved from the GPS fix",{ E.showPrompt("Location has been saved from the GPS fix",{
title:"Location Saved", title:"Location Saved",
buttons : {"OK":1} buttons : {"OK":1}
@ -49,13 +52,13 @@ function setFromGPS() {
} }
function showMainMenu() { function showMainMenu() {
console.log("showMainMenu"); //console.log("showMainMenu");
const mainmenu = { const mainmenu = {
'': { 'title': 'My Location' }, '': { 'title': 'My Location' },
'<Back': ()=>{ load(); }, '<Back': ()=>{ load(); },
'City': { 'City': {
value: 0 | locations.indexOf(s.location), value: 0 | locations.indexOf(s.location),
min: 0, max: 6, min: 0, max: locations.length - 1,
format: v => locations[v], format: v => locations[v],
onchange: v => { onchange: v => {
if (v != 6) { if (v != 6) {
@ -67,7 +70,7 @@ function showMainMenu() {
} }
}, },
'Set From GPS': ()=>{ setFromGPS(); } 'Set From GPS': ()=>{ setFromGPS(); }
} };
return E.showMenu(mainmenu); return E.showMenu(mainmenu);
} }

1
apps/neonx/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial release

20
apps/neonx/README.md Normal file
View File

@ -0,0 +1,20 @@
# Neon X and IO X Clock
| ![Neon X](neonx-screenshot.png) | ![Neon IO X](neoniox-screenshot.png) |
|---------------------------------|--------------------------------------|
| <center>Neon X</center> | <center>Neon IO X</center> |
This is a clock based on Pebble's Neon X and Neon IO X watchfaces by Sam Jerichow.
Can be switched between in the Settings menu, which can be accessed through
the app/widget settings menu of the Bangle.js
## Settings
### Neon IO X:
Activate the Neon IO X clock look, a bit hard to read until one gets used to it.
### Thickness
The thickness of watch lines, from 1 to 5.
### Date on touch
Shows the current date as DD MM on touch and reverts back to time after 5 seconds or with another touch.

20
apps/neonx/metadata.json Normal file
View File

@ -0,0 +1,20 @@
{
"id": "neonx",
"name": "Neon X & IO X Clock",
"shortName": "Neon X Clock",
"version": "0.01",
"description": "Pebble Neon X & Neon IO X for Bangle.js",
"icon": "neonx.png",
"type": "clock",
"readme": "README.md",
"tags": "neonx,neonio,neoniox,clock",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url": "neonx-screenshot.png"}, {"url": "neoniox-screenshot.png"}],
"storage": [
{"name": "neonx.app.js", "url": "neonx.app.js"},
{"name": "neonx.img", "url": "neonx-icon.js", "evaluate": true},
{"name": "neonx.settings.js", "url": "neonx.settings.js"}
],
"data": [{"name": "neonx.json"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
apps/neonx/neonx-icon.js Normal file
View File

@ -0,0 +1 @@
E.toArrayBuffer(atob("MDCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAALaAAAAAAAAAAG2AAAAAAAAAABQAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAgABttttttttsAAEkAAAAAAAEkANtttttttttgAEkAAAAAAAEkABttttttttsAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAEkAAAAAAAEkAAAAAAAAAAAAAAgAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAAAAEkABttttttttsAAAAAAAAAAAEkANtttttttttgAAAAAAAAAAAgABttttttttsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

153
apps/neonx/neonx.app.js Normal file
View File

@ -0,0 +1,153 @@
/**
* Bangle.js Neon X/IO X Clock
*
* Author: Bundyo
* Repo: https://github.com/bundyo/BangleApps/tree/master/apps/neonx
* Initial code based on Numerals Clock by Raik M.
* Pebble Watchface Author: Sam Jerichow
* Created: February 2022
*/
const digits = {
0:[[15,15,85,15,85,85,15,85,15,15]],
1:[[85,15,85,85]],
2:[[15,15,85,15,85,50], [15,50,15,85,85,85]],
3:[[15,15,85,15,85,85,15,85]],
4:[[15,15,15,50], [85,15,85,85]],
5:[[85,15,15,15,15,50], [85,50,85,85,15,85]],
6:[[85,15,15,15,15,85,85,85,85,50]],
7:[[15,15,85,15,85,85]],
8:[[15,15,85,15],[15,85,85,85]],
9:[[15,50,15,15,85,15,85,85,15,85]],
};
const colors = {
x: [
["#FF00FF", "#00FFFF"],
["#00FF00", "#FFFF00"]
],
io: [
["#FF00FF", "#FFFF00"],
["#00FF00", "#00FFFF"]
]
};
const is12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
const screenWidth = g.getWidth();
const halfWidth = screenWidth / 2;
const scale = screenWidth / 240;
const REFRESH_RATE = 10E3;
let interval = 0;
let showingDate = false;
function drawLine(poly, thickness){
for (let i = 0; i < poly.length; i = i + 2){
if (poly[i + 2] === undefined) {
break;
}
if (poly[i] !== poly[i + 2]) {
g.fillRect(poly[i], poly[i + 1] - thickness / 2, poly[i + 2], poly[i + 3] + thickness / 2);
} else {
g.fillRect(poly[i] - thickness / 2, poly[i + 1], poly[i + 2] + thickness / 2, poly[i + 3]);
}
g.fillCircle(poly[i], poly[i + 1], thickness / 2);
g.fillCircle(poly[i + 2], poly[i + 3], thickness / 2);
}
}
let settings = require('Storage').readJSON('neonx.json', 1);
if (!settings) {
settings = {
thickness: 4,
io: 0,
showDate: 1
};
}
function drawClock(num){
let tx, ty;
for (let x = 0; x <= 1; x++) {
for (let y = 0; y <= 1; y++) {
const current = ((y + 1) * 2 + x - 1);
let newScale = scale;
g.setColor(colors[settings.io ? 'io' : 'x'][y][x]);
if (!settings.io) {
tx = (x * 100 + 18) * newScale;
ty = (y * 100 + 32) * newScale;
} else {
newScale = 0.33 + current * 0.4;
tx = (halfWidth - 139) * newScale + halfWidth;
ty = (halfWidth - 139) * newScale + halfWidth + 12;
}
for (let i = 0; i < digits[num[y][x]].length; i++) {
drawLine(g.transformVertices(digits[num[y][x]][i], { x: tx, y: ty, scale: newScale}), settings.thickness);
}
}
}
}
function draw(date){
let d = new Date();
let l1, l2;
showingDate = date;
if (date) {
setUpdateInt(0);
l1 = ('0' + (new Date()).getDate()).substr(-2);
l2 = ('0' + ((new Date()).getMonth() + 1)).substr(-2);
setTimeout(_ => {
draw();
setUpdateInt(1);
}, 5000);
} else {
l1 = ('0' + (d.getHours() % (is12hour ? 12 : 24))).substr(-2);
l2 = ('0' + d.getMinutes()).substr(-2);
}
g.clearRect(0,24,240,240);
drawClock([l1, l2]);
}
function setUpdateInt(set){
if (interval) {
clearInterval(interval);
}
if (set) {
interval = setInterval(draw, REFRESH_RATE);
}
}
g.clear(1);
Bangle.setUI("clock");
setUpdateInt(1);
draw();
if (settings.showDate) {
Bangle.on('touch', () => draw(!showingDate));
}
Bangle.on('lcdPower', function(on){
if (on){
draw();
setUpdateInt(1);
} else setUpdateInt(0);
});
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/neonx/neonx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

View File

@ -0,0 +1,54 @@
(function(back) {
function updateSettings() {
storage.write('neonx.json', neonXSettings);
}
function resetSettings() {
neonXSettings = {
thickness: 4,
io: 0,
showDate: 1
};
updateSettings();
}
let neonXSettings = storage.readJSON('neonx.json',1);
if (!neonXSettings) resetSettings();
let thicknesses = [1, 2, 3, 4, 5];
const menu = {
"" : { "title":"Neon X & IO"},
"< Back": back,
"Neon IO X": {
value: 0 | neonXSettings.io,
min: 0, max: 1,
format: v => v ? "On" : "Off",
onchange: v => {
neonXSettings.io = v;
updateSettings();
}
},
"Thickness": {
value: 0 | thicknesses.indexOf(neonXSettings.thickness),
min: 0, max: thicknesses.length - 1,
format: v => thicknesses[v],
onchange: v => {
neonXSettings.thickness = thicknesses[v];
updateSettings();
}
},
"Date on touch": {
value: 0 | neonXSettings.showDate,
min: 0, max: 1,
format: v => v ? "On" : "Off",
onchange: v => {
neonXSettings.showDate = v;
updateSettings();
}
}
};
E.showMenu(menu);
})

View File

@ -13,3 +13,6 @@
which requires 2.11.27 firmware to reset at midnight which requires 2.11.27 firmware to reset at midnight
0.13: call process.memory(false) to avoid triggering a GC of memory 0.13: call process.memory(false) to avoid triggering a GC of memory
supported in pre 2.12.13 firmware supported in pre 2.12.13 firmware
0.14: incorporated lazybones idle timer, configuration settings to come
0.15: fixed tendancy for mylocation to default to London
added setting to enable/disable idle timer warning

View File

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

View File

@ -4,9 +4,18 @@ const storage = require('Storage');
const locale = require("locale"); const locale = require("locale");
const SETTINGS_FILE = "pastel.json"; const SETTINGS_FILE = "pastel.json";
const LOCATION_FILE = "mylocation.json"; const LOCATION_FILE = "mylocation.json";
const w = g.getWidth();
const h = g.getHeight();
let settings; let settings;
let location; let location;
// variable for controlling idle alert
let lastStep = getTime();
let lastStepTime = '??';
let warned = 0;
let idle = false;
let IDLE_MINUTES = 26;
// cloud, sun, partSun, snow, rain, storm, error // cloud, sun, partSun, snow, rain, storm, error
// create 1 bit, max contrast, brightness set to 85 // create 1 bit, max contrast, brightness set to 85
var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA==")); var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA=="));
@ -16,16 +25,24 @@ var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAj
var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA==")); var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA=="));
var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A==")); var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A=="));
// saves having to recode all the small font calls
function setSmallFont() {
g.setFontLatoSmall();
}
function loadSettings() { function loadSettings() {
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
settings.grid = settings.grid||false; settings.grid = settings.grid||false;
settings.font = settings.font||"Lato"; settings.font = settings.font||"Lato";
settings.idle_check = settings.idle_check||true;
} }
// requires the myLocation app // requires the myLocation app
function loadLocation() { function loadLocation() {
location = require("Storage").readJSON(LOCATION_FILE,1)||{"lat":51.5072,"lon":0.1276,"location":"London"}; location = require("Storage").readJSON(LOCATION_FILE,1)||{};
location.lat = location.lat||51.5072;
location.lon = location.lon||0.1276;
location.location = location.location||"London";
} }
function extractTime(d){ function extractTime(d){
@ -71,17 +88,18 @@ function getSteps() {
if (WIDGETS.wpedom !== undefined) if (WIDGETS.wpedom !== undefined)
return WIDGETS.wpedom.getSteps(); return WIDGETS.wpedom.getSteps();
else else
return '???' return '???';
} }
} }
const infoData = { const infoData = {
ID_BLANK: { calc: () => '' }, ID_BLANK: { calc: () => '' },
ID_DATE: { calc: () => {var d = (new Date).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
ID_DAY: { calc: () => {var d = require("locale").dow(new Date).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
ID_SR: { calc: () => 'Sunrise: ' + sunRise }, ID_SR: { calc: () => 'Sunrise: ' + sunRise },
ID_SS: { calc: () => 'Sunset: ' + sunSet }, ID_SS: { calc: () => 'Sunset: ' + sunSet },
ID_STEP: { calc: () => 'Steps: ' + getSteps() }, ID_STEP: { calc: () => 'Steps: ' + getSteps() },
ID_LAST: { calc: () => 'Last Step: ' + lastStepTime },
ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }, ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' },
ID_MEM: { calc: () => {var val = process.memory(false); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} }, ID_MEM: { calc: () => {var val = process.memory(false); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} },
ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} }, ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} },
@ -152,6 +170,14 @@ function getWeather() {
} }
function draw() { function draw() {
if (!idle)
drawClock();
else
drawIdle();
queueDraw();
}
function drawClock() {
var d = new Date(); var d = new Date();
var da = d.toString().split(" "); var da = d.toString().split(" ");
var time = da[4].substr(0,5); var time = da[4].substr(0,5);
@ -166,11 +192,8 @@ function draw() {
if (parseInt(hh) > 12) if (parseInt(hh) > 12)
hh = h2.substr(h2.length -2); hh = h2.substr(h2.length -2);
var w = g.getWidth();
var h = g.getHeight();
var x = (g.getWidth()/2); var x = (g.getWidth()/2);
var y = (g.getHeight()/3); var y = (g.getHeight()/3);
var weatherJson = getWeather(); var weatherJson = getWeather();
var w_temp; var w_temp;
var w_icon; var w_icon;
@ -190,7 +213,8 @@ function draw() {
} }
g.reset(); g.reset();
g.clearRect(0, 30, w, h - 24); g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
// draw a grid like graph paper // draw a grid like graph paper
if (settings.grid && process.env.HWVERSION !=1) { if (settings.grid && process.env.HWVERSION !=1) {
@ -249,6 +273,141 @@ function draw() {
queueDraw(); queueDraw();
} }
///////////////// IDLE TIMER /////////////////////////////////////
function log_debug(o) {
//print(o);
}
function drawIdle() {
let mins = Math.round((getTime() - lastStep) / 60);
g.reset();
g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
g.setColor(g.theme.fg);
setSmallFont();
g.setFontAlign(0, 0);
g.drawString('Last step was', w/2, (h/3));
g.drawString(mins + ' minutes ago', w/2, 20+(h/3));
dismissBtn.draw();
}
/////////////// BUTTON CLASS ///////////////////////////////////////////
// simple on screen button class
function BUTTON(name,x,y,w,h,c,f,tx) {
this.name = name;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = c;
this.callback = f;
this.text = tx;
}
// if pressed the callback
BUTTON.prototype.check = function(x,y) {
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
log_debug(this.name + ":callback\n");
this.callback();
return true;
}
return false;
};
BUTTON.prototype.draw = function() {
g.setColor(this.color);
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
g.setColor("#000"); // the icons and boxes are drawn black
setSmallFont();
g.setFontAlign(0, 0);
g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2));
g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h));
};
function dismissPrompt() {
idle = false;
warned = false;
lastStep = getTime();
Bangle.buzz(100);
draw();
}
var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss");
Bangle.on('touch', function(button, xy) {
if (idle && dismissBtn.check(xy.x, xy.y)) return;
});
// if we get a step then we are not idle
Bangle.on('step', s => {
setLastStepTime();
lastStep = getTime();
// redraw if we had been idle
if (idle == true) {
dismissPrompt();
}
idle = false;
warned = 0;
});
function setLastStepTime() {
var date = new Date();
lastStepTime = require("locale").time(date,1);
}
function checkIdle() {
if (!settings.idle_check) {
idle = false;
warned = false;
return;
}
let hour = (new Date()).getHours();
let active = (hour >= 9 && hour < 21);
//let active = true;
let dur = getTime() - lastStep;
if (active && dur > IDLE_MINUTES * 60) {
drawIdle();
if (warned++ < 3) {
buzzer(warned);
log_debug("checkIdle: warned=" + warned);
Bangle.setLocked(false);
}
idle = true;
} else {
idle = false;
warned = 0;
}
}
setLastStepTime();
// timeout for multi-buzzer
var buzzTimeout;
// n buzzes
function buzzer(n) {
log_debug("buzzer n=" + n);
if (n-- < 1) return;
Bangle.buzz(250);
if (buzzTimeout) clearTimeout(buzzTimeout);
buzzTimeout = setTimeout(function() {
buzzTimeout = undefined;
buzzer(n);
}, 500);
}
///////////////////////////////////////////////////////////////////////////////
// timeout used to update every minute // timeout used to update every minute
var drawTimeout; var drawTimeout;
@ -258,6 +417,7 @@ function queueDraw() {
drawTimeout = setTimeout(function() { drawTimeout = setTimeout(function() {
drawTimeout = undefined; drawTimeout = undefined;
prevInfo(); prevInfo();
checkIdle();
draw(); draw();
}, 60000 - (Date.now() % 60000)); }, 60000 - (Date.now() % 60000));
} }

View File

@ -5,6 +5,7 @@
let s = { let s = {
'grid': false, 'grid': false,
'weather': false, 'weather': false,
'idle_check': true,
'font': "Lato" 'font': "Lato"
} }
@ -51,6 +52,24 @@
s.weather = !s.weather; s.weather = !s.weather;
save(); save();
}, },
},
// for use when the new menu system goes live
/*
'Idle Warning': {
value: s.idle_check,
onchange : v => {
s.idle_check = v;
save();
},
},
*/
'Idle Warning': {
value: s.idle_check,
format: () => (s.idle_check ? 'Yes' : 'No'),
onchange: () => {
s.idle_check = !s.idle_check;
save();
},
} }
}) })
}) })

View File

@ -4,8 +4,11 @@
</head> </head>
<body> <body>
<div id="tracks"></div> <div id="tracks"></div>
<div class="container" id="toastcontainer" stlye="position:fixed; bottom:8px; left:0px; right:0px; z-index: 100;"></div>
<script src="../../core/lib/interface.js"></script> <script src="../../core/lib/interface.js"></script>
<script src="../../core/js/ui.js"></script>
<script src="../../core/js/utils.js"></script>
<script> <script>
var domTracks = document.getElementById("tracks"); var domTracks = document.getElementById("tracks");
@ -70,9 +73,15 @@ ${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 0); }, 0);
showToast("Download finished.", "success");
} }
function saveGPX(track, title) { function saveGPX(track, title) {
if (!track || !track[0] || !"Time" in track[0] || !track[0].Time) {
showToast("Error in trackfile.", "error");
return;
}
var gpx = `<?xml version="1.0" encoding="UTF-8"?> var gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"> <gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<metadata> <metadata>
@ -109,6 +118,7 @@ function saveGPX(track, title) {
document.body.removeChild(a); document.body.removeChild(a);
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}, 0); }, 0);
showToast("Download finished.", "success");
} }
function saveCSV(track, title) { function saveCSV(track, title) {
@ -121,6 +131,7 @@ function saveCSV(track, title) {
}).join(",")+"\n"; }).join(",")+"\n";
}); });
Util.saveCSV(title, csv); Util.saveCSV(title, csv);
showToast("Download finished.", "success");
} }
function trackLineToObject(headers, l) { function trackLineToObject(headers, l) {
@ -171,6 +182,10 @@ function getTrackList() {
return {headers:headers,l:data}; return {headers:headers,l:data};
})(${JSON.stringify(filename)})`, trackInfo=>{ })(${JSON.stringify(filename)})`, trackInfo=>{
console.log(filename," => ",trackInfo); console.log(filename," => ",trackInfo);
if (!trackInfo || !"headers" in trackInfo) {
showToast("Error loading track list.", "error");
resolve();
}
trackInfo.headers = trackInfo.headers.split(","); trackInfo.headers = trackInfo.headers.split(",");
trackList.push({ trackList.push({
filename : filename, filename : filename,

View File

@ -1 +1,3 @@
1.0: Initial version of game 0.01: Initial version of game
0.02: Fix mistake preventing game from ending in some cases.
0.03: Update help screen with more details.

Some files were not shown because too many files have changed in this diff Show More