diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog
index f5638fdd2..ffe9de081 100644
--- a/apps/about/ChangeLog
+++ b/apps/about/ChangeLog
@@ -10,3 +10,4 @@
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
0.11: Bangle.js2: New pixels, btn1 to exit
0.12: Actual pixels as of 29th Nov 2021
+0.13: Bangle.js 2: Use setUI to add software back button
diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js
index 978d36193..0c40314a8 100644
--- a/apps/about/app-bangle2.js
+++ b/apps/about/app-bangle2.js
@@ -69,4 +69,7 @@ function drawImage() {
// TODO: a nice little animation before
setTimeout(drawInfo, 1000);
-setWatch(_=>load(), BTN1);
+Bangle.setUI({
+ mode : "custom",
+ back : load
+});
diff --git a/apps/about/metadata.json b/apps/about/metadata.json
index 6c22bdc56..648576576 100644
--- a/apps/about/metadata.json
+++ b/apps/about/metadata.json
@@ -1,7 +1,7 @@
{
"id": "about",
"name": "About",
- "version": "0.12",
+ "version": "0.13",
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
"icon": "app.png",
"tags": "tool,system",
diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog
index c913481ff..3811425ac 100644
--- a/apps/activityreminder/ChangeLog
+++ b/apps/activityreminder/ChangeLog
@@ -6,4 +6,5 @@
0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night
0.07: Fix bug on the cutting edge firmware
0.08: Use default Bangle formatter for booleans
-0.09: New app screen (instead of showing settings or the alert) and some optimisations
\ No newline at end of file
+0.09: New app screen (instead of showing settings or the alert) and some optimisations
+0.10: Add software back button via setUI
diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js
index 97f03ce97..81e10d8dd 100644
--- a/apps/activityreminder/app.js
+++ b/apps/activityreminder/app.js
@@ -47,8 +47,12 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
drawInfo();
+ Bangle.setUI({
+ mode : "custom",
+ back : load
+ })
}
run();
-})();
\ No newline at end of file
+})();
diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json
index 9e8c552b1..a7fb0c487 100644
--- a/apps/activityreminder/metadata.json
+++ b/apps/activityreminder/metadata.json
@@ -3,7 +3,7 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
- "version":"0.09",
+ "version":"0.10",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog
index 2c59c3cc2..16a90242b 100644
--- a/apps/agenda/ChangeLog
+++ b/apps/agenda/ChangeLog
@@ -3,3 +3,4 @@
0.03: Disable past events display from settings
0.04: Added awareness of allDay field
0.05: Displaying calendar colour and name
+0.06: Added clkinfo for clocks.
\ No newline at end of file
diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js
new file mode 100644
index 000000000..a80c09002
--- /dev/null
+++ b/apps/agenda/agenda.clkinfo.js
@@ -0,0 +1,29 @@
+(function() {
+ var agendaItems = {
+ name: "Agenda",
+ img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
+ items: []
+ };
+
+ var now = new Date();
+ var agenda = storage.readJSON("android.calendar.json")
+ .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
+ .sort((a,b)=>a.timestamp - b.timestamp);
+
+ agenda.forEach((entry, i) => {
+
+ var title = entry.title.slice(0,18);
+ var date = new Date(entry.timestamp*1000);
+ var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
+ dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
+
+ agendaItems.items.push({
+ name: "agendaEntry-" + i,
+ get: () => ({ text: title + "\n" + dateStr, img: null}),
+ show: function() { agendaItems.items[i].emit("redraw"); },
+ hide: function () {}
+ });
+ });
+
+ return agendaItems;
+})
\ No newline at end of file
diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json
index 982870905..2bce8ca56 100644
--- a/apps/agenda/metadata.json
+++ b/apps/agenda/metadata.json
@@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
- "version": "0.05",
+ "version": "0.06",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
@@ -12,6 +12,7 @@
"storage": [
{"name":"agenda.app.js","url":"agenda.js"},
{"name":"agenda.settings.js","url":"settings.js"},
+ {"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"},
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
],
"data": [{"name":"agenda.settings.json"}]
diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json
index d3863be52..e2f818d97 100644
--- a/apps/agpsdata/metadata.json
+++ b/apps/agpsdata/metadata.json
@@ -1,9 +1,9 @@
{ "id": "agpsdata",
- "name": "A-GPS Data",
+ "name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data",
"icon": "agpsdata.png",
"version":"0.02",
- "description": "Download assisted GPS (A-GPS) data directly to your Bangle.js **using Gadgetbridge**",
+ "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"tags": "boot,tool,assisted,gps,agps,http",
"allow_emulator":true,
"supports": ["BANGLEJS2"],
diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog
index f7e95b5fa..9e75d889a 100644
--- a/apps/antonclk/ChangeLog
+++ b/apps/antonclk/ChangeLog
@@ -10,4 +10,5 @@
week is buffered until date or timezone changes
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
0.08: fixed calendar weeknumber not shortened to two digits
-0.09: Use default Bangle formatter for booleans
\ No newline at end of file
+0.09: Use default Bangle formatter for booleans
+0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js
index 4b1e71bda..07f67f696 100644
--- a/apps/antonclk/app.js
+++ b/apps/antonclk/app.js
@@ -1,7 +1,4 @@
// Clock with large digits using the "Anton" bold font
-
-const SETTINGSFILE = "antonclk.json";
-
Graphics.prototype.setFontAnton = function(scale) {
// Actual height 69 (68 - 0)
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78 + (scale << 8) + (1 << 16));
@@ -12,23 +9,28 @@ Graphics.prototype.setFontAntonSmall = function(scale) {
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16));
};
+{ // must be inside our own scope here so that when we are unloaded everything disappears
+
+const SETTINGSFILE = "antonclk.json";
+const isBangle1 = (process.env.HWVERSION == 1);
+
// variables defined from settings
-var secondsMode;
-var secondsColoured;
-var secondsWithColon;
-var dateOnMain;
-var dateOnSecs;
-var weekDay;
-var calWeek;
-var upperCase;
-var vectorFont;
+let secondsMode;
+let secondsColoured;
+let secondsWithColon;
+let dateOnMain;
+let dateOnSecs;
+let weekDay;
+let calWeek;
+let upperCase;
+let vectorFont;
// dynamic variables
-var drawTimeout;
-var queueMillis = 1000;
-var secondsScreen = true;
+let drawTimeout;
+let queueMillis = 1000;
+let secondsScreen = true;
+
-var isBangle1 = (process.env.HWVERSION == 1);
//For development purposes
/*
@@ -102,7 +104,7 @@ function isoStr(date) {
return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2);
}
-var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested)
+let calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested)
function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
dateNoTime = date; dateNoTime.setHours(0,0,0,0);
if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2];
@@ -215,16 +217,21 @@ g.clear();
// Set dynamic state and perform initial drawing
updateState();
// Register hooks for LCD on/off event and screen lock on/off event
-Bangle.on('lcdPower', on => {
- updateState();
-});
-Bangle.on('lock', on => {
- updateState();
-});
+Bangle.on('lcdPower', updateState);
+Bangle.on('lock', updateState);
// Show launcher when middle button pressed
-Bangle.setUI("clock");
+Bangle.setUI({
+ mode : "clock",
+ remove : function() {
+ // Called to unload all of the clock app
+ Bangle.removeListener('lcdPower', updateState);
+ Bangle.removeListener('lock', updateState);
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ delete Graphics.prototype.setFontAnton;
+ delete Graphics.prototype.setFontAntonSmall;
+ }});
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
-
-// end of file
\ No newline at end of file
+}
diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json
index 16bdf3aa8..b6134d1a1 100644
--- a/apps/antonclk/metadata.json
+++ b/apps/antonclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "antonclk",
"name": "Anton Clock",
- "version": "0.09",
+ "version": "0.10",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
diff --git a/apps/assistedgps/metadata.json b/apps/assistedgps/metadata.json
index 1dbc42c87..4c91dcd35 100644
--- a/apps/assistedgps/metadata.json
+++ b/apps/assistedgps/metadata.json
@@ -1,11 +1,12 @@
{
"id": "assistedgps",
- "name": "Assisted GPS Update (AGPS)",
+ "name": "Assisted GPS Updater (AGPS)",
"version": "0.03",
- "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
+ "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
+ "sortorder": -1,
"icon": "app.png",
"type": "RAM",
- "tags": "tool,outdoors,agps",
+ "tags": "tool,outdoors,agps,gps,a-gps",
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"customConnect": true,
diff --git a/apps/banglexercise/ChangeLog b/apps/banglexercise/ChangeLog
index 5f1d3bd7d..6cf589541 100644
--- a/apps/banglexercise/ChangeLog
+++ b/apps/banglexercise/ChangeLog
@@ -2,3 +2,4 @@
0.02: Add sit ups
Add more feedback to the user about the exercises
Clean up code
+0.03: Add software back button on main menu
diff --git a/apps/banglexercise/app.js b/apps/banglexercise/app.js
index bc6e35f07..9659ee81f 100644
--- a/apps/banglexercise/app.js
+++ b/apps/banglexercise/app.js
@@ -71,7 +71,8 @@ function showMainMenu() {
let menu;
menu = {
"": {
- title: "BanglExercise"
+ title: "BanglExercise",
+ back: load
}
};
@@ -381,4 +382,5 @@ Bangle.on('HRM', function(hrm) {
});
g.clear(1);
+Bangle.loadWidgets();
showMainMenu();
diff --git a/apps/banglexercise/metadata.json b/apps/banglexercise/metadata.json
index 9bb93f112..f4ce1894b 100644
--- a/apps/banglexercise/metadata.json
+++ b/apps/banglexercise/metadata.json
@@ -1,7 +1,7 @@
{ "id": "banglexercise",
"name": "BanglExercise",
"shortName":"BanglExercise",
- "version":"0.02",
+ "version":"0.03",
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog
index b373f1876..c92d139bb 100644
--- a/apps/bigdclock/ChangeLog
+++ b/apps/bigdclock/ChangeLog
@@ -4,3 +4,4 @@
0.04: bug fix
0.05: proper fix for the race condition in queueDraw()
0.06: Tell clock widgets to hide.
+0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge
diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js
index 3adf96984..a8e2b38df 100644
--- a/apps/bigdclock/bigdclock.app.js
+++ b/apps/bigdclock/bigdclock.app.js
@@ -11,6 +11,8 @@ Graphics.prototype.setFontOpenSans = function(scale) {
};
var drawTimeout;
+var lastBattCheck = 0;
+var width = 0;
function queueDraw(millis_now) {
if (drawTimeout) clearTimeout(drawTimeout);
@@ -24,12 +26,15 @@ function draw() {
var date = new Date();
var h = date.getHours(),
m = date.getMinutes();
- var d = date.getDate(),
- w = date.getDay(); // d=1..31; w=0..6
- const level = E.getBattery();
- const width = level + (level/2);
+ var d = date.getDate();
var is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
- var dows = require("date_utils").dows(0,1);
+ var dow = require("date_utils").dows(0,1)[date.getDay()];
+
+ if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) {
+ lastBattcheck = date.getTime();
+ width = E.getBattery();
+ width += width/2;
+ }
g.reset();
g.clear();
@@ -47,24 +52,35 @@ function draw() {
g.drawString(d, g.getWidth() -6, 98);
g.setFont('Vector', 52);
g.setFontAlign(-1, -1);
- g.drawString(dows[w].slice(0,2).toUpperCase(), 6, 103);
+ g.drawString(dow.slice(0,2).toUpperCase(), 6, 103);
g.fillRect(9,159,166,171);
g.fillRect(167,163,170,167);
if (Bangle.isCharging()) {
g.setColor(1,1,0);
- } else if (level > 40) {
- g.setColor(0,1,0);
+ g.fillRect(12,162,12+width,168);
} else {
g.setColor(1,0,0);
+ g.fillRect(12,162,57,168);
+ g.setColor(1,1,0);
+ g.fillRect(58,162,72,168);
+ g.setColor(0,1,0);
+ g.fillRect(73,162,162,168);
}
- g.fillRect(12,162,12+width,168);
- if (level < 100) {
+ if (width < 150) {
g.setColor(g.theme.bg);
g.fillRect(12+width+1,162,162,168);
}
- g.setColor(0, 1, 0);
+ if (Bangle.isCharging()) {
+ g.setColor(1,1,0);
+ } else if (width <= 45) {
+ g.setColor(1,0,0);
+ } else if (width <= 60) {
+ g.setColor(1,1,0);
+ } else {
+ g.setColor(0, 1, 0);
+ }
g.fillRect(0, 90, g.getWidth(), 94);
// widget redraw
diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json
index ce91d921e..30352ca1a 100644
--- a/apps/bigdclock/metadata.json
+++ b/apps/bigdclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "bigdclock",
"name": "Big digit clock containing just the essentials",
"shortName":"Big digit clk",
- "version":"0.06",
+ "version":"0.07",
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
"icon": "bigdclock.png",
"type": "clock",
diff --git a/apps/bigdclock/screenshot.png b/apps/bigdclock/screenshot.png
index 8a12b266e..acac53ea9 100644
Binary files a/apps/bigdclock/screenshot.png and b/apps/bigdclock/screenshot.png differ
diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog
index 59bf9eb96..546c83894 100644
--- a/apps/bwclk/ChangeLog
+++ b/apps/bwclk/ChangeLog
@@ -18,4 +18,6 @@
0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set.
0.19: Fix - Compatibility with "Digital clock widget"
0.20: Better handling of async data such as getPressure.
-0.21: On the default menu the week of year can be shown.
\ No newline at end of file
+0.21: On the default menu the week of year can be shown.
+0.22: Use the new clkinfo module for the menu.
+0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
\ No newline at end of file
diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md
index dfb9bf515..d869fa2cf 100644
--- a/apps/bwclk/README.md
+++ b/apps/bwclk/README.md
@@ -1,12 +1,15 @@
# BW Clock
-A very minimalistic clock to mainly show date and time.
+A very minimalistic clock.

## Features
-The BW clock provides many features and also 3rd party integrations:
+The BW clock implements features that are exposed by other apps through the `clkinfo` module.
+For example, if you install the HomeAssistant app, this menu item will be shown if you click right
+and additionally allows you to send triggers directly from the clock (select triggers via up/down and
+send via click center). Here are examples of other apps that are integrated:
+
- Bangle data such as steps, heart rate, battery or charging state.
-- A timer can be set directly. *Requirement: Scheduler library*
- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
@@ -20,27 +23,27 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is
- Your bangle uses the sys color settings so you can change the color too.
## Menu structure
-2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to set a timer or send a HomeAssistant trigger.
+2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant.
-Simply click left / right to go through the menu entries such as Bangle, Timer etc.
+Simply click left / right to go through the menu entries such as Bangle, Weather etc.
and click up/down to move into this sub-menu. You can then click in the middle of the screen
-to e.g. send a trigger via HomeAssistant once you selected it.
+to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend
+on the app that provide this sub-menu through the `clkinfo` module.
```
- +5min
- |
- Bangle -- Timer[Optional] -- Agenda 1[Optional] -- Weather[Optional] -- HomeAssistant [Optional]
- | | | | |
- Bpm -5min Agenda 2 Temperature Trigger1
- | | | |
- Steps ... ... ...
+ Bangle -- Agenda -- Weather -- HomeAssistant
+ | | | |
+ Battery Entry 1 Temperature Trigger1
+ | | | |
+ Steps ... ... ...
|
- Battery
+ ...
```
## Thanks to
-Icons created by Flaticon
+- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
+- Icons created by Flaticon
## Creator
[David Peer](https://github.com/peerdavid)
diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js
index cd1979e98..7dcca9d75 100644
--- a/apps/bwclk/app.js
+++ b/apps/bwclk/app.js
@@ -1,19 +1,21 @@
-/************
+/************************************************
* Includes
*/
const locale = require('locale');
const storage = require('Storage');
+const clock_info = require("clock_info");
-/************
- * Statics
+
+/************************************************
+ * Globals
*/
const SETTINGS_FILE = "bwclk.setting.json";
-const TIMER_IDX = "bwclk_timer";
-const TIMER_AGENDA_IDX = "bwclk_agenda";
const W = g.getWidth();
const H = g.getHeight();
+var lock_input = false;
-/************
+
+/************************************************
* Settings
*/
let settings = {
@@ -29,8 +31,7 @@ for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
-
-/************
+/************************************************
* Assets
*/
// Manrope font
@@ -45,14 +46,12 @@ Graphics.prototype.setLargeFont = function(scale) {
return this;
};
-
Graphics.prototype.setMediumFont = function(scale) {
// Actual height 41 (42 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
return this;
};
-
Graphics.prototype.setSmallFont = function(scale) {
// Actual height 28 (27 - 0)
this.setFontCustom(
@@ -64,7 +63,6 @@ Graphics.prototype.setSmallFont = function(scale) {
return this;
};
-
Graphics.prototype.setMiniFont = function(scale) {
// Actual height 16 (15 - 0)
this.setFontCustom(
@@ -76,8 +74,6 @@ Graphics.prototype.setMiniFont = function(scale) {
return this;
};
-
-
function imgLock(){
return {
width : 16, height : 16, bpp : 1,
@@ -86,434 +82,100 @@ function imgLock(){
}
}
-function imgSteps(){
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA=="))
- }
-}
-function imgBattery(){
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA"))
- }
-}
-
-function imgCharging() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
- }
-}
-
-function imgBpm() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA"))
- }
-}
-
-function imgTemperature() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA=="))
- }
-}
-
-function imgWeather(){
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 0,
- buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA="))
- }
-}
-
-function imgWind () {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A=="))
- }
-}
-
-function imgHumidity () {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("//7///+YCB+ICB8ACE4F/AQX9AQP54H//AOB+F/34CBj/gn8f4E+h/Aj0H4Ecg+AjED4ACE8E4gfwvEDEgICB/kHGwMP"))
- }
-}
-
-function imgTimer() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA=="))
- }
-}
-
-function imgWatch() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC"))
- }
-}
-
-function imgHomeAssistant() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AF84CB4YCBwICBCAP+jFH/k8g/4kkH+AFB8ACB4cY4eHzPhgmZkHnzPn8fb4/gvwUD8EYARhAC"))
- }
-}
-
-function imgAgenda() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : require("heatshrink").decompress(atob("/4AFnPP+ALBAQX4CIgLFAQvggEBAQvAgEDAQMCwEAgwTBhgiB/AlCGQ8BGQQ"))
- }
-}
-
-function imgMountain() {
- return {
- width : 24, height : 24, bpp : 1,
- transparent : 1,
- buffer : atob("//////////////////////3///n///D//uZ//E8//A+/+Z+f8//P5//n7//3z//zn//5AAAAAAAA////////////////////")
- }
-}
-
-/************
- * 2D MENU with entries of:
- * [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
- *
+/************************************************
+ * Menu
*/
-var menu = [
- [
- function(){ return [ null, null ] },
- function(){ return [ "Week " + weekOfYear(), null ] },
- ],
- [
- function(){ return [ "Bangle", imgWatch() ] },
- function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
- function(){ return [ getSteps(), imgSteps() ] },
- function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
- function(){ return [ measureAltitude, imgMountain() ]},
+// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
+var bwItems = {
+ name: null,
+ img: null,
+ items: [
+ { name: "WeekOfYear",
+ get: () => ({ text: "Week " + weekOfYear(), img: null}),
+ show: function() { bwItems.items[0].emit("redraw"); },
+ hide: function () {}
+ },
]
-]
+};
-/*
- * Timer Menu
- */
-try{
- require('sched');
- menu.push([
- function(){
- var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer";
- return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ]
- },
- ]);
-} catch(ex) {
- // If sched is not installed, we hide this menu item
-}
-
-
-/*
- * AGENDA MENU
- * Note that we handle the agenda differently in order to hide old entries...
- */
-var agendaIdx = 0;
-var agendaTimerIdx = 0;
-if(storage.readJSON("android.calendar.json") !== undefined){
- function nextAgendaEntry(){
- agendaIdx += 1;
- }
-
- function previousAgendaEntry(){
- agendaIdx -= 1;
- }
-
- menu.push([
- function(){
- var now = new Date();
- var agenda = storage.readJSON("android.calendar.json")
- .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
- .sort((a,b)=>a.timestamp - b.timestamp);
-
- if(agenda.length <= 0){
- return ["All done", imgAgenda()]
- }
-
- agendaIdx = agendaIdx < 0 ? 0 : agendaIdx;
- agendaIdx = agendaIdx >= agenda.length ? agendaIdx -1 : agendaIdx;
-
- var entry = agenda[agendaIdx];
- var title = entry.title.slice(0,14);
- var date = new Date(entry.timestamp*1000);
- var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
- dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
-
- function dynImgAgenda(){
- if(isAlarmEnabled(TIMER_AGENDA_IDX) && agendaTimerIdx == agendaIdx){
- return imgTimer();
- } else {
- return imgAgenda();
- }
- }
-
- return [title + "\n" + dateStr, dynImgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), function(){
- try{
- var alarm = require('sched')
-
- // If other time, we disable the old one and enable this one.
- if(agendaIdx != agendaTimerIdx){
- agendaTimerIdx = -1;
- alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
- }
-
- // Disable alarm if enabled
- if(isAlarmEnabled(TIMER_AGENDA_IDX)){
- agendaTimerIdx = -1;
- alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
- alarm.reload();
- return
- }
-
- // Otherwise, set alarm for given event
- agendaTimerIdx = agendaIdx;
- alarm.setAlarm(TIMER_AGENDA_IDX, {
- msg: title,
- timer : parseInt((date - now)),
- });
- alarm.reload();
- } catch(ex){ }
- }]
- },
- ]);
-}
-
-
-/*
- * WEATHER MENU
- */
-if(storage.readJSON('weather.json') !== undefined){
- menu.push([
- function(){ return [ "Weather", imgWeather() ] },
- function(){ return [ getWeather().temp, imgTemperature() ] },
- function(){ return [ getWeather().hum, imgHumidity() ] },
- function(){ return [ getWeather().wind, imgWind() ] },
- ]);
-}
-
-
-/*
- * HOME ASSISTANT MENU
- */
-try{
- var triggers = require("ha.lib.js").getTriggers();
- var haMenu = [
- function(){ return [ "Home", imgHomeAssistant() ] },
- ];
-
- triggers.forEach(trigger => {
- haMenu.push(function(){
- return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){
- var ha = require("ha.lib.js");
- ha.sendTrigger("TRIGGER_BW");
- ha.sendTrigger(trigger.trigger);
- }]
- });
- })
- menu.push(haMenu);
-} catch(ex){
- // If HomeAssistant is not installed, we hide this item
-}
-
-
-function getMenuEntry(){
- // In case the user removes HomeAssistant entries, showInfo
- // could be larger than infoArray.length...
- settings.menuPosX = settings.menuPosX % menu.length;
- settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length;
- var menuEntry = menu[settings.menuPosX][settings.menuPosY]();
-
- if(menuEntry[0] == null){
- return menuEntry;
- }
-
- // For the first entry we always convert it into a callback function
- // such that the menu is compatible with async functions such as
- // measuring the pressure, altitude or sending http requests...
- if(typeof menuEntry[0] !== 'function'){
- var value = menuEntry[0];
- menuEntry[0] = function(callbackFun){
- callbackFun(String(value), settings.menuPosX, settings.menuPosY);
- }
- }
- return menuEntry;
-}
-
-
-/************
- * Helper
- */
-function isFullscreen(){
- var s = settings.screen.toLowerCase();
- if(s == "dynamic"){
- return Bangle.isLocked()
- } else {
- return s == "full"
- }
-}
-
-function getSteps() {
- var steps = 0;
- try{
- if (WIDGETS.wpedom !== undefined) {
- steps = WIDGETS.wpedom.getSteps();
- } else if (WIDGETS.activepedom !== undefined) {
- steps = WIDGETS.activepedom.getSteps();
- } else {
- steps = Bangle.getHealthStatus("day").steps;
- }
- } catch(ex) {
- // In case we failed, we can only show 0 steps.
- }
-
- return steps;
-}
-
-
-function getWeather(){
- var weatherJson;
-
- try {
- weatherJson = storage.readJSON('weather.json');
- var weather = weatherJson.weather;
-
- // Temperature
- weather.temp = locale.temp(weather.temp-273.15);
-
- // Humidity
- weather.hum = weather.hum + "%";
-
- // Wind
- const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
- weather.wind = Math.round(wind[1]) + "kph";
-
- return weather
-
- } catch(ex) {
- // Return default
- }
-
- return {
- temp: " ? ",
- hum: " ? ",
- txt: " ? ",
- wind: " ? ",
- wdir: " ? ",
- wrose: " ? "
- };
-}
-
-// From https://weeknumber.com/how-to/javascript
function weekOfYear() {
- var date = new Date();
- date.setHours(0, 0, 0, 0);
- // Thursday in current week decides the year.
- date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
- // January 4 is always in week 1.
- var week1 = new Date(date.getFullYear(), 0, 4);
- // Adjust to Thursday in week 1 and count number of weeks from date to week1.
- return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
- - 3 + (week1.getDay() + 6) % 7) / 7);
+ var date = new Date();
+ date.setHours(0, 0, 0, 0);
+ // Thursday in current week decides the year.
+ date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
+ // January 4 is always in week 1.
+ var week1 = new Date(date.getFullYear(), 0, 4);
+ // Adjust to Thursday in week 1 and count number of weeks from date to week1.
+ return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
+ - 3 + (week1.getDay() + 6) % 7) / 7);
}
-function isAlarmEnabled(idx){
- try{
- var alarm = require('sched');
- var alarmObj = alarm.getAlarm(idx);
- if(alarmObj===undefined || !alarmObj.on){
- return false;
+// Load menu
+var menu = clock_info.load();
+menu = menu.concat(bwItems);
+
+
+// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
+if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
+ settings.menuPosX = 0;
+ settings.menuPosY = 0;
+}
+
+// Set draw functions for each item
+menu.forEach((menuItm, x) => {
+ menuItm.items.forEach((item, y) => {
+ function drawItem() {
+ // For the clock, we have a special case, as we don't wanna redraw
+ // immediately when something changes. Instead, we update data each minute
+ // to save some battery etc. Therefore, we hide (and disable the listener)
+ // immedeately after redraw...
+ item.hide();
+
+ // After drawing the item, we enable inputs again...
+ lock_input = false;
+
+ var info = item.get();
+ drawMenuItem(info.text, info.img);
}
- return true;
-
- } catch(ex){ }
- return false;
-}
+ item.on('redraw', drawItem);
+ })
+});
-function getAlarmMinutes(idx){
- if(!isAlarmEnabled(idx)){
- return -1;
+function canRunMenuItem(){
+ if(settings.menuPosY == 0){
+ return false;
}
- var alarm = require('sched');
- var alarmObj = alarm.getAlarm(idx);
- return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
+ var menuEntry = menu[settings.menuPosX];
+ var item = menuEntry.items[settings.menuPosY-1];
+ return item.run !== undefined;
}
-function increaseAlarm(idx){
+function runMenuItem(){
+ if(settings.menuPosY == 0){
+ return;
+ }
+
+ var menuEntry = menu[settings.menuPosX];
+ var item = menuEntry.items[settings.menuPosY-1];
try{
- var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0;
- var alarm = require('sched');
- alarm.setAlarm(idx, {
- timer : (minutes+5)*60*1000,
- });
- alarm.reload();
- } catch(ex){ }
-}
-
-
-function decreaseAlarm(idx){
- try{
- var minutes = getAlarmMinutes(idx);
- minutes -= 5;
-
- var alarm = require('sched')
- alarm.setAlarm(idx, undefined);
-
- if(minutes > 0){
- alarm.setAlarm(idx, {
- timer : minutes*60*1000,
- });
+ var ret = item.run();
+ if(ret){
+ Bangle.buzz(300, 0.6);
}
-
- alarm.reload();
- } catch(ex){ }
-}
-
-
-function measureAltitude(callbackFun){
- var oldX = settings.menuPosX;
- var oldY = settings.menuPosY;
- try{
- Bangle.getPressure().then(data=>{
- if(data && data.altitude && data.altitude > -100){
- callbackFun(Math.round(data.altitude) + "m", oldX, oldY);
- } else {
- callbackFun("???", oldX, oldY);
- }
- });
- }catch(ex){
- callbackFun("err", oldX, oldY);
+ } catch (ex) {
+ // Simply ignore it...
}
}
-/************
- * DRAW
+/************************************************
+ * Draw
*/
function draw() {
// Queue draw again
@@ -521,7 +183,7 @@ function draw() {
// Draw clock
drawDate();
- drawTime();
+ drawMenuAndTime();
drawLock();
drawWidgets();
}
@@ -529,12 +191,12 @@ function draw() {
function drawDate(){
// Draw background
- var y = H/5*2;
- g.reset().clearRect(0,0,W,W);
+ var y = H/5*2 + (isFullscreen() ? 0 : 8);
+ g.reset().clearRect(0,0,W,y);
// Draw date
y = parseInt(y/2)+4;
- y += isFullscreen() ? 0 : 13;
+ y += isFullscreen() ? 0 : 8;
var date = new Date();
var dateStr = date.getDate();
dateStr = ("0" + dateStr).substr(-2);
@@ -557,11 +219,8 @@ function drawDate(){
}
-function drawTime(){
+function drawTime(y, smallText){
// Draw background
- var y = H/5*2 + (isFullscreen() ? 0 : 8);
- g.setColor(g.theme.fg);
- g.fillRect(0,y,W,H);
var date = new Date();
// Draw time
@@ -577,56 +236,65 @@ function drawTime(){
// Set y coordinates correctly
y += parseInt((H - y)/2) + 5;
- var menuEntry = getMenuEntry();
- var menuTextFun = menuEntry[0];
- var menuImg = menuEntry[1];
- var printImgLeft = settings.menuPosY != 0;
-
// Show large or small time depending on info entry
- if(menuTextFun == null){
- g.setLargeFont();
- g.drawString(timeStr, W/2, y);
- return;
- } else {
+ if(smallText){
y -= 15;
g.setMediumFont();
- g.drawString(timeStr, W/2, y);
+ } else {
+ g.setLargeFont();
}
+ g.drawString(timeStr, W/2, y);
+}
- // Async set the menu (could be that some data is async fetched)
- menuTextFun((menuText, oldX, oldY) => {
+function drawMenuItem(text, image){
+ // First clear the time region
+ var y = H/5*2 + (isFullscreen() ? 0 : 8);
- // We display the text IFF the user did not change the menu
- if(settings.menuPosX != oldX || settings.menuPosY != oldY){
- return;
- }
+ g.setColor(g.theme.fg);
+ g.fillRect(0,y,W,H);
- // As its a callback, we have to ensure that the color
- // font etc. is still correct...
- g.setColor(g.theme.bg);
+ // Draw menu text
+ var hasText = (text != null && text != "");
+ if(hasText){
g.setFontAlign(0,0);
- y += 35;
- if(menuText.split('\n').length > 1){
+ // For multiline text we show an even smaller font...
+ text = String(text);
+ if(text.split('\n').length > 1){
g.setMiniFont();
} else {
g.setSmallFont();
}
- var imgWidth = 0;
- if(menuImg){
- imgWidth = 24.0;
- var strWidth = g.stringWidth(menuText);
- var scale = imgWidth / menuImg.width;
- g.drawImage(
- menuImg,
- W/2 + (printImgLeft ? -strWidth/2-4 : strWidth/2+4) - parseInt(imgWidth/2),
- y - parseInt(imgWidth/2),
- { scale: scale }
- );
+ var imgWidth = image == null ? 0 : 24;
+ var strWidth = g.stringWidth(text);
+ g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
+ g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
+
+ if(image != null){
+ var scale = imgWidth / image.width;
+ g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
}
- g.drawString(menuText, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
- });
+ }
+
+ // Draw time
+ drawTime(y, hasText);
+}
+
+
+function drawMenuAndTime(){
+ var menuEntry = menu[settings.menuPosX];
+
+ // The first entry is the overview...
+ if(settings.menuPosY == 0){
+ drawMenuItem(menuEntry.name, menuEntry.img);
+ return;
+ }
+
+ // Draw item if needed
+ lock_input = true;
+ var item = menuEntry.items[settings.menuPosY-1];
+ item.show();
}
@@ -647,9 +315,19 @@ function drawWidgets(){
}
+function isFullscreen(){
+ var s = settings.screen.toLowerCase();
+ if(s == "dynamic"){
+ return Bangle.isLocked()
+ } else {
+ return s == "full"
+ }
+}
-/*
- * Draw timeout
+
+
+/************************************************
+ * Listener
*/
// timeout used to update every minute
var drawTimeout;
@@ -692,7 +370,7 @@ Bangle.on('charging',function(charging) {
drawTimeout = undefined;
// Jump to battery
- settings.menuPosX = 1;
+ settings.menuPosX = 0;
settings.menuPosY = 1;
draw();
});
@@ -710,17 +388,15 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right;
+ if(lock_input){
+ return;
+ }
+
if(is_lower){
Bangle.buzz(40, 0.6);
- settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length;
+ settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
- // Handle custom menu entry function
- var menuEntry = getMenuEntry();
- if(menuEntry.length > 2){
- menuEntry[2]();
- }
-
- drawTime();
+ drawMenuAndTime();
}
if(is_upper){
@@ -730,53 +406,29 @@ Bangle.on('touch', function(btn, e){
Bangle.buzz(40, 0.6);
settings.menuPosY = settings.menuPosY-1;
- settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].length-1 : settings.menuPosY;
+ settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
- // Handle custom menu entry function
- var menuEntry = getMenuEntry();
- if(menuEntry.length > 3){
- menuEntry[3]();
- }
-
- drawTime();
+ drawMenuAndTime();
}
if(is_right){
- // A bit hacky but we ensure that always the first agenda entry is shown...
- agendaIdx = 0;
-
Bangle.buzz(40, 0.6);
settings.menuPosX = (settings.menuPosX+1) % menu.length;
settings.menuPosY = 0;
- drawTime();
+ drawMenuAndTime();
}
if(is_left){
- // A bit hacky but we ensure that always the first agenda entry is shown...
- agendaIdx = 0;
-
Bangle.buzz(40, 0.6);
settings.menuPosY = 0;
settings.menuPosX = settings.menuPosX-1;
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
- drawTime();
+ drawMenuAndTime();
}
if(is_center){
- var menuEntry = getMenuEntry();
- if(menuEntry.length > 4 && menuEntry[4] != null){
- Bangle.buzz(80, 0.6).then(()=>{
- try{
- menuEntry[4]();
- setTimeout(()=>{
- Bangle.buzz(80, 0.6);
- drawTime();
- }, 250);
- } catch(ex){
- // In case it fails, we simply ignore it.
- }
- }
- );
+ if(canRunMenuItem()){
+ runMenuItem();
}
}
});
@@ -791,9 +443,10 @@ E.on("kill", function(){
});
-/*
- * Draw clock the first time
+/************************************************
+ * Startup Clock
*/
+
// The upper part is inverse i.e. light if dark and dark if light theme
// is enabled. In order to draw the widgets correctly, we invert the
// dark/light theme as well as the colors.
diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json
index b6896ab5e..fbae0e1e7 100644
--- a/apps/bwclk/metadata.json
+++ b/apps/bwclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "bwclk",
"name": "BW Clock",
- "version": "0.21",
+ "version": "0.23",
"description": "A very minimalistic clock to mainly show date and time.",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog
index e78b4ccd0..c28526abd 100644
--- a/apps/ha/ChangeLog
+++ b/apps/ha/ChangeLog
@@ -1,2 +1,4 @@
0.01: Release
-0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
\ No newline at end of file
+0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
+0.03: Added clkinfo for clocks.
+0.04: Feedback if clkinfo run is called.
\ No newline at end of file
diff --git a/apps/ha/ha.clkinfo.js b/apps/ha/ha.clkinfo.js
new file mode 100644
index 000000000..ad7f51c57
--- /dev/null
+++ b/apps/ha/ha.clkinfo.js
@@ -0,0 +1,26 @@
+(function() {
+ var ha = require("ha.lib.js");
+ var triggers = ha.getTriggers();
+
+ var haItems = {
+ name: "Home",
+ img: atob("GBiBAf/////////n///D//+B//8A//48T/wkD/gkD/A8D+AYB8AYA4eZ4QyZMOyZN+fb5+D/B+B+B+A8B+AYB+AYB+AYB+AYB+A8Bw=="),
+ items: []
+ };
+
+ triggers.forEach((trigger, i) => {
+ haItems.items.push({
+ name: "haTrigger-" + i,
+ get: () => ({ text: trigger.display, img: trigger.getIcon()}),
+ show: function() { haItems.items[i].emit("redraw"); },
+ hide: function () {},
+ run: function() {
+ ha.sendTrigger("TRIGGER_BW");
+ ha.sendTrigger(trigger.trigger);
+ return true;
+ }
+ });
+ });
+
+ return haItems;
+})
\ No newline at end of file
diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json
index 63308b933..8ccaea598 100644
--- a/apps/ha/metadata.json
+++ b/apps/ha/metadata.json
@@ -1,7 +1,7 @@
{
"id": "ha",
"name": "HomeAssistant",
- "version": "0.02",
+ "version": "0.04",
"description": "Integrates your BangleJS into HomeAssistant.",
"icon": "ha.png",
"type": "app",
@@ -20,6 +20,7 @@
"storage": [
{"name":"ha.app.js","url":"ha.app.js"},
{"name":"ha.lib.js","url":"ha.lib.js"},
+ {"name":"ha.clkinfo.js","url":"ha.clkinfo.js"},
{"name":"ha.img","url":"ha.icon.js","evaluate":true}
]
}
diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog
index 44866b9f3..36852e0b7 100644
--- a/apps/launch/ChangeLog
+++ b/apps/launch/ChangeLog
@@ -14,3 +14,4 @@
Add /*LANG*/ tags for internationalisation
0.13: Add fullscreen mode
0.14: Use default Bangle formatter for booleans
+0.15: Support for unload and quick return to the clock on 2v16
diff --git a/apps/launch/app.js b/apps/launch/app.js
index 556e61bfd..d53f0dcdf 100644
--- a/apps/launch/app.js
+++ b/apps/launch/app.js
@@ -1,7 +1,8 @@
-var s = require("Storage");
-var scaleval = 1;
-var vectorval = 20;
-var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
+{ // must be inside our own scope here so that when we are unloaded everything disappears
+let s = require("Storage");
+let scaleval = 1;
+let vectorval = 20;
+let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
let settings = Object.assign({
showClocks: true,
fullscreen: false
@@ -20,7 +21,7 @@ if ("font" in settings){
scaleval = (font.split("x")[1])/20;
}
}
-var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type));
+let apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type));
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first
@@ -69,18 +70,30 @@ E.showScroller({
}
});
+function returnToClock() {
+ // unload everything manually
+ // ... or we could just call `load();` but it will be slower
+ Bangle.setUI(); // remove scroller's handling
+ if (lockTimeout) clearTimeout(lockTimeout);
+ Bangle.removeListener("lock", lockHandler);
+ // now load the default clock - just call .bootcde as this has the code already
+ setTimeout(eval,0,s.read(".bootcde"));
+}
+
// on bangle.js 2, the screen is used for navigating, so the single button goes back
// on bangle.js 1, the buttons are used for navigating
if (process.env.HWVERSION==2) {
- setWatch(_=>load(), BTN1, {edge:"falling"});
+ setWatch(returnToClock, BTN1, {edge:"falling"});
}
// 10s of inactivity goes back to clock
Bangle.setLocked(false); // unlock initially
-var lockTimeout;
-Bangle.on("lock", locked => {
+let lockTimeout;
+function lockHandler(locked) {
if (lockTimeout) clearTimeout(lockTimeout);
lockTimeout = undefined;
if (locked)
- lockTimeout = setTimeout(_=>load(), 10000);
-});
+ lockTimeout = setTimeout(returnToClock, 10000);
+}
+Bangle.on("lock", lockHandler);
+}
diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json
index 19ca74e73..ec070e44e 100644
--- a/apps/launch/metadata.json
+++ b/apps/launch/metadata.json
@@ -2,7 +2,7 @@
"id": "launch",
"name": "Launcher",
"shortName": "Launcher",
- "version": "0.14",
+ "version": "0.15",
"description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog
index 262cba1fa..da3b3ab5c 100644
--- a/apps/messages/ChangeLog
+++ b/apps/messages/ChangeLog
@@ -66,4 +66,8 @@
0.50: Add `getMessages` and `status` functions to library
Option to disable auto-open of messages
Option to make message icons monochrome (not colored)
- messages widget buzz now returns a promise
\ No newline at end of file
+ messages widget buzz now returns a promise
+0.51: Emit "message events"
+ Setting to hide widget
+ Add custom event handlers to prevent default app form loading
+ Move WIDGETS.messages.buzz() to require("messages").buzz()
\ No newline at end of file
diff --git a/apps/messages/README.md b/apps/messages/README.md
index 2e583d1c2..72a989146 100644
--- a/apps/messages/README.md
+++ b/apps/messages/README.md
@@ -25,7 +25,7 @@ it starts getting clipped.
* `Auto-Open Music` - Should the app automatically open when the phone starts playing music?
* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app?
* `Flash Icon` - Toggle flashing of the widget icon.
-* `Widget messages` - The maximum amount of message icons to show on the widget.
+* `Widget messages` - The maximum amount of message icons to show on the widget, or `Hide` the widget completely.
## New Messages
@@ -56,6 +56,24 @@ _2. What the notify icon looks like (it's touchable on Bangle.js2!)_

+## Events (for app/widget developers)
+
+When a new message arrives, a `"message"` event is emitted, you can listen for
+it like this:
+
+```js
+myMessageListener = Bangle.on("message", (type, message)=>{
+ if (message.handled) return; // another app already handled this message
+ // is one of "text", "call", "alarm", "map", "music", or "clearAll"
+ if (type === "clearAll") return; // not a message
+ // see `messages/lib.js` for possible formats
+ // message.t could be "add", "modify" or "remove"
+ E.showMessage(`${message.title}\n${message.body}`, `${message.t} ${type} message`);
+ // You can prevent the default `message` app from loading by setting `message.handled = true`:
+ message.handled = true;
+});
+```
+
## Requests
diff --git a/apps/messages/app.js b/apps/messages/app.js
index 40dff9635..20fa8aaa3 100644
--- a/apps/messages/app.js
+++ b/apps/messages/app.js
@@ -54,8 +54,7 @@ var onMessagesModified = function(msg) {
// TODO: if new, show this new one
if (msg && msg.id!=="music" && msg.new && active!="map" &&
!((require('Storage').readJSON('setting.json', 1) || {}).quiet)) {
- if (WIDGETS["messages"]) WIDGETS["messages"].buzz(msg.src);
- else Bangle.buzz();
+ require("messages").buzz(msg.src);
}
if (msg && msg.id=="music") {
if (msg.state && msg.state!="play") openMusic = false; // no longer playing music to go back to
@@ -356,13 +355,13 @@ function checkMessages(options) {
// If we have a new message, show it
if (options.showMsgIfUnread && newMessages.length) {
showMessage(newMessages[0].id);
- // buzz after showMessage, so beingbusy during layout doesn't affect the buzz pattern
+ // buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
if (global.BUZZ_ON_NEW_MESSAGE) {
// this is set if we entered the messages app by loading `messages.new.js`
// ... but only buzz the first time we view a new message
global.BUZZ_ON_NEW_MESSAGE = false;
// messages.buzz respects quiet mode - no need to check here
- WIDGETS.messages.buzz(newMessages[0].src);
+ require("messages").buzz(newMessages[0].src);
}
return;
}
diff --git a/apps/messages/lib.js b/apps/messages/lib.js
index d8599c93d..ed71ec04b 100644
--- a/apps/messages/lib.js
+++ b/apps/messages/lib.js
@@ -8,15 +8,11 @@ function openMusic() {
/* Push a new message onto messages queue, event is:
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool}
{t:"add",id:int, id:"music", state, artist, track, etc} // add new
- {t:"remove-",id:int} // remove
+ {t:"remove",id:int} // remove
{t:"modify",id:int, title:string} // modified
*/
exports.pushMessage = function(event) {
- var messages, inApp = "undefined"!=typeof MESSAGES;
- if (inApp)
- messages = MESSAGES; // we're in an app that has already loaded messages
- else // no app - load messages
- messages = require("Storage").readJSON("messages.json",1)||[];
+ var messages = exports.getMessages();
// now modify/delete as appropriate
var mIdx = messages.findIndex(m=>m.id==event.id);
if (event.t=="remove") {
@@ -35,69 +31,81 @@ exports.pushMessage = function(event) {
else Object.assign(messages[mIdx], event);
if (event.id=="music" && messages[mIdx].state=="play") {
messages[mIdx].new = true; // new track, or playback (re)started
+ type = 'music';
}
}
require("Storage").writeJSON("messages.json",messages);
+ var message = mIdx<0 ? {id:event.id, t:'remove'} : messages[mIdx];
// if in app, process immediately
- if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
+ if ("undefined"!=typeof MESSAGES) return onMessagesModified(message);
+ // emit message event
+ var type = 'text';
+ if (["call", "music", "map"].includes(message.id)) type = message.id;
+ if (message.src && message.src.toLowerCase().startsWith("alarm")) type = "alarm";
+ Bangle.emit("message", type, message);
// update the widget icons shown
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages,true);
+ var handleMessage = () => {
// if no new messages now, make sure we don't load the messages app
- if (event.t=="remove" && exports.messageTimeout && !messages.some(m=>m.new)) {
- clearTimeout(exports.messageTimeout);
- delete exports.messageTimeout;
- }
- // ok, saved now
- if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) {
- // just load the app to display music: no buzzing
- load("messages.app.js");
- } else if (event.t!="add") {
- // we only care if it's new
- return;
- } else if(event.new == false) {
- return;
- }
- // otherwise load messages/show widget
- var loadMessages = Bangle.CLOCK || event.important;
- var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
- var appSettings = require('Storage').readJSON('messages.settings.json',1)||{};
- var unlockWatch = appSettings.unlockWatch;
- // don't auto-open messages in quiet mode if quietNoAutOpn is true
- if((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
- loadMessages = false;
- delete appSettings;
- // after a delay load the app, to ensure we have all the messages
- if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
- exports.messageTimeout = setTimeout(function() {
- exports.messageTimeout = undefined;
- // if we're in a clock or it's important, go straight to messages app
- if (loadMessages){
- if(!quiet && unlockWatch){
- Bangle.setLocked(false);
- Bangle.setLCDPower(1); // turn screen on
- }
- // we will buzz when we enter the messages app
- return load("messages.new.js");
+ if (event.t=="remove" && exports.messageTimeout && !messages.some(m => m.new)) {
+ clearTimeout(exports.messageTimeout);
+ delete exports.messageTimeout;
}
- if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz once to let someone know
- if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages);
- }, 500);
+ // ok, saved now
+ if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) {
+ // just load the app to display music: no buzzing
+ load("messages.app.js");
+ } else if (event.t!="add") {
+ // we only care if it's new
+ return;
+ } else if (event.new==false) {
+ return;
+ }
+ // otherwise load messages/show widget
+ var loadMessages = Bangle.CLOCK || event.important;
+ var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
+ var appSettings = require('Storage').readJSON('messages.settings.json', 1) || {};
+ var unlockWatch = appSettings.unlockWatch;
+ // don't auto-open messages in quiet mode if quietNoAutOpn is true
+ if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
+ loadMessages = false;
+ delete appSettings;
+ // after a delay load the app, to ensure we have all the messages
+ if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
+ exports.messageTimeout = setTimeout(function() {
+ exports.messageTimeout = undefined;
+ // if we're in a clock or it's important, go straight to messages app
+ if (loadMessages) {
+ if (!quiet && unlockWatch) {
+ Bangle.setLocked(false);
+ Bangle.setLCDPower(1); // turn screen on
+ }
+ // we will buzz when we enter the messages app
+ return load("messages.new.js");
+ }
+ if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages);
+ exports.buzz(message.src);
+ }, 500);
+ };
+ setTimeout(()=>{
+ if (!message.handled) handleMessage();
+ },0);
}
/// Remove all messages
-exports.clearAll = function(event) {
- var messages, inApp = "undefined"!=typeof MESSAGES;
- if (inApp) {
+exports.clearAll = function() {
+ if ("undefined"!= typeof MESSAGES) { // we're in a messages app, clear that as well
MESSAGES = [];
- messages = MESSAGES; // we're in an app that has already loaded messages
- } else // no app - empty messages
- messages = [];
- // Save all messages
- require("Storage").writeJSON("messages.json",messages);
- // update app if in app
- if (inApp) return onMessagesModified();
+ }
+ // Clear all messages
+ require("Storage").writeJSON("messages.json", []);
// if we have a widget, update it
if (global.WIDGETS && WIDGETS.messages)
- WIDGETS.messages.update(messages);
+ WIDGETS.messages.update([]);
+ // let message listeners know
+ Bangle.emit("message", "clearAll", {}); // guarantee listeners an object as `message`
+ // clearAll cannot be marked as "handled"
+ // update app if in app
+ if ("function"== typeof onMessagesModified) onMessagesModified();
}
/**
@@ -126,6 +134,45 @@ exports.getMessages = function() {
}
};
+/**
+ * Start buzzing for new message
+ * @param {string} msgSrc Message src to buzz for
+ * @return {Promise} Resolves when initial buzz finishes (there might be repeat buzzes later)
+ */
+exports.buzz = function(msgSrc) {
+ exports.stopBuzz(); // cancel any previous buzz timeouts
+ if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode
+
+ var pattern;
+ if (msgSrc && msgSrc.toLowerCase() === "phone") {
+ // special vibration pattern for incoming calls
+ pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls;
+ } else {
+ pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate;
+ }
+ if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here
+ if (!pattern) return Promise.resolve();
+
+ var repeat = (require('Storage').readJSON("messages.settings.json", true) || {}).repeat;
+ if (repeat===undefined) repeat=4; // repeat may be zero
+ if (repeat) {
+ exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000);
+ var vibrateTimeout = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateTimeout;
+ if (vibrateTimeout===undefined) vibrateTimeout=60;
+ if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopTimeout, vibrateTimeout*1000);
+ }
+ return require("buzz").pattern(pattern);
+};
+/**
+ * Stop buzzing
+ */
+exports.stopBuzz = function() {
+ if (exports.buzzTimeout) clearTimeout(exports.buzzTimeout);
+ delete exports.buzzTimeout;
+ if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
+ delete exports.stopTimeout;
+};
+
exports.getMessageImage = function(msg) {
/*
* icons should be 24x24px or less with 1bpp colors and 'Transparency to Color'
diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json
index da2e0945a..057a95026 100644
--- a/apps/messages/metadata.json
+++ b/apps/messages/metadata.json
@@ -1,7 +1,7 @@
{
"id": "messages",
"name": "Messages",
- "version": "0.50",
+ "version": "0.51",
"description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",
diff --git a/apps/messages/settings.js b/apps/messages/settings.js
index 0edb17797..09c9db455 100644
--- a/apps/messages/settings.js
+++ b/apps/messages/settings.js
@@ -73,7 +73,8 @@
},
/*LANG*/'Widget messages': {
value:0|settings().maxMessages,
- min: 1, max: 5,
+ min: 0, max: 5,
+ format: v => v ? v :/*LANG*/"Hide",
onchange: v => updateSetting("maxMessages", v)
},
/*LANG*/'Icon color mode': {
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
index a5e1f8b6c..c8d132f82 100644
--- a/apps/messages/widget.js
+++ b/apps/messages/widget.js
@@ -1,4 +1,5 @@
(() => {
+if ((require('Storage').readJSON("messages.settings.json", true) || {}).maxMessages===0) return;
function filterMessages(msgs) {
return msgs.filter(msg => msg.new && msg.id != "music")
@@ -14,15 +15,14 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
}
Bangle.removeListener('touch', this.touch);
if (!this.width) return;
- var c = (Date.now()-this.t)/1000;
- let settings = Object.assign({flash:true, maxMessages:3, repeat:4, vibrateTimeout:60},require('Storage').readJSON("messages.settings.json", true) || {});
+ let settings = Object.assign({flash:true, maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {});
if (recall !== true || settings.flash) {
var msgsShown = E.clip(this.msgs.length, 0, settings.maxMessages);
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23);
for(let i = 0;i < msgsShown;i++) {
const msg = this.msgs[i];
const colors = [g.theme.bg, g.setColor(require("messages").getMessageImageCol(msg)).getColor()];
- if (settings.flash && (c&1)) {
+ if (settings.flash && ((Date.now()/1000)&1)) {
if (colors[1] == g.theme.fg) {
colors.reverse();
} else {
@@ -35,38 +35,13 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/});
}
}
- if (csettings.repeat*1000) { // the period between vibrations
- this.l = Date.now();
- WIDGETS["messages"].buzz(); // buzz every 4 seconds
- }
WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000);
if (process.env.HWVERSION>1) Bangle.on('touch', this.touch);
-},update:function(rawMsgs, quiet) {
+},update:function(rawMsgs) {
const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {});
this.msgs = filterMessages(rawMsgs);
- if (this.msgs.length === 0) {
- delete this.t;
- delete this.l;
- } else {
- this.t=Date.now(); // first time
- this.l=Date.now()-10000; // last buzz
- if (quiet) this.t -= 500000; // if quiet, set last time in the past so there is no buzzing
- }
this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages);
Bangle.drawWidgets();
-},buzz:function(msgSrc) { // return a promise
- if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode
- var pattern;
- if (msgSrc != undefined && msgSrc.toLowerCase() == "phone") {
- // special vibration pattern for incoming calls
- pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls;
- } else {
- pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate;
- }
- if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here
- return require("buzz").pattern(pattern);
},touch:function(b,c) {
var w=WIDGETS["messages"];
if (!w||!w.width||c.xw.x+w.width||c.yw.y+24) return;
@@ -74,8 +49,7 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
}};
/* We might have returned here if we were in the Messages app for a
-message but then the watch was never viewed. In that case we don't
-want to buzz but should still show that there are unread messages. */
+message but then the watch was never viewed. */
if (global.MESSAGES===undefined)
- WIDGETS["messages"].update(require("messages").getMessages(), true);
+ WIDGETS["messages"].update(require("messages").getMessages());
})();
diff --git a/apps/numberchaser/app-icon.js b/apps/numberchaser/app-icon.js
new file mode 100644
index 000000000..a4bb5054d
--- /dev/null
+++ b/apps/numberchaser/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwkA/4AC+c/AoYAR+QTTj4DBkYXS+YUB+cSl4YS+P/mUiL4RLQ+chiUziPyAAIXPmJEC+Ui+UhO6QABj8iVKocEACUTC60j+YXWPoZHTkcyC6sibYaPpC63ziXzkYXUkXyO6nykMykIXUl8xmcykQ2BSZ4XBkTXB+LdBMgPyDRnzj8vmf/AIMyDgMjAoIALiQSCVYI0CD4QAL+MTIILFCI4TJOmMRkUzn40CGQRLBMRipBiIABkR2DTAIAQmURF4KcBZScxn5qBACgWWbwUhMCT7CmU/WQQAR+awBF6jdBVggXSPCwXXVAPyJCkimUieKinBI6sxAQIvUeAMyMQIXT+MjI6iNC+RIT+bAB+cSYiZdBMQMzSCkf+ZIUGAMiYKsjkTbVkMSl4A=="))
diff --git a/apps/numberchaser/app.js b/apps/numberchaser/app.js
new file mode 100644
index 000000000..f68119fb2
--- /dev/null
+++ b/apps/numberchaser/app.js
@@ -0,0 +1,104 @@
+var randomNumber;
+var guessNumber = 1;
+
+function mathRandomInt(a, b) {
+ if (a > b) {
+ // Swap a and b to ensure a is smaller.
+ var c = a;
+ a = b;
+ b = c;
+ }
+ return Math.floor(Math.random() * (b - a + 1) + a);
+}
+
+/**
+ * Describe this function...
+ */
+function game() {
+
+ g.drawString('',0,20,true);
+ E.showMenu(numMenu);
+ console.log(randomNumber);
+}
+
+var numMenu = {
+ "" : {
+ "title" : "Number Chaser",
+ },
+ "Guess Number" : {
+ value : guessNumber,
+ min:1,max:100,step:1,
+ onchange : v => { guessNumber=v; }
+ },
+ "OK" : function () {
+ g.clear();
+ if (guessNumber == randomNumber) {
+ //if guess is correct
+ g.setFont("Vector",13);g.setFontAlign(-1,-1);
+ status = "You won! ";
+ gameOver();
+ } else {
+ //if guess is incorrect
+ g.setFont("Vector",13);g.setFontAlign(-1,-1);
+ if (guessNumber > randomNumber) {
+ //Decreases number if guess is greater
+ randomNumber = randomNumber - 1;
+ status = "Too high!";
+ } else if (guessNumber < randomNumber) {
+ //Increases number if guess is lower
+ status = "Too low!";
+ randomNumber = randomNumber + 1;
+ }
+ if (randomNumber < 0 || randomNumber > 100) {
+ //You lose when the number is out of the 1 to 100 range
+ g.setFont("Vector",13);g.setFontAlign(-1,-1);
+ g.drawString('You have lost\nNumber is out\nof range.',10,10,true);
+ status = "You lost!";
+ } else {
+ g.drawString(status+"\nTry again!",10,10);
+ Bangle.on('tap', function() {
+ delay(3000).then(() => game());
+ }
+ );
+ }
+ }
+ }
+};
+
+function gameOver()
+{
+ E.showPrompt(status+'Play again?',{title:""+'Number Chaser'}).then(function(a) {
+ if (a) {
+ randomNumber = mathRandomInt(1, 100);
+ game();
+ } else {
+ load();
+ }
+ }
+ );
+}
+
+function delay(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
+}
+
+function instructions()
+{
+ g.setFont("Vector",13);g.setFontAlign(-1,-1);
+ g.drawString('Guess the number\nbetween 1 and 100.\nGuess too high, it\ndecreases by 1.\nToo low, it increases\nby 1.\nIf the number\ngoes below 0 or\nabove 100, it\nis out of range\nand you have\nlost.',10,10,true);
+ randomNumber = mathRandomInt(1, 100);
+ delay(10000).then(() => game());
+}
+
+
+g.clear();
+E.showPrompt('Do you need instructions?',{title:""+'Number Chaser'}).then(function(a)
+ { if (a) {
+ instructions();
+ } else
+ {
+ randomNumber = mathRandomInt(1, 100);
+ game();
+ }
+ }
+);
diff --git a/apps/numberchaser/metadata.json b/apps/numberchaser/metadata.json
new file mode 100644
index 000000000..f9b6ff4b2
--- /dev/null
+++ b/apps/numberchaser/metadata.json
@@ -0,0 +1,13 @@
+{ "id": "numberchaser",
+ "name": "Number Chaser",
+ "shortName":"Number Chaser",
+ "version":"0.01",
+ "description": "A number guessing game, but the number goes up or down based on if you're guessing too high or too low.",
+ "icon": "numberchaser.png",
+ "tags": "game,fun",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "storage": [
+ {"name":"numberchaser.app.js","url":"app.js"},
+ {"name":"numberchaser.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/numberchaser/numberchaser.png b/apps/numberchaser/numberchaser.png
new file mode 100644
index 000000000..2042cc22b
Binary files /dev/null and b/apps/numberchaser/numberchaser.png differ
diff --git a/apps/openstmap/custom.html b/apps/openstmap/custom.html
index 6f611dd86..3bba997e7 100644
--- a/apps/openstmap/custom.html
+++ b/apps/openstmap/custom.html
@@ -66,20 +66,20 @@ TODO:
However some don't allow cross-origin use */
//var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast, TOO SLOW
//var TILELAYER = 'http://a.tile.stamen.com/toner/{z}/{x}/{y}.png'; // black and white
- var TILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
- var PREVIEWTILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+ var TILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+ var PREVIEWTILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
// Create map and try and set the location to where the browser thinks we are
var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true});
// Tiles used for Bangle.js itself
var bangleTileLayer = L.tileLayer(TILELAYER, {
maxZoom: 18,
- attribution: 'Map data © OpenStreetMap contributors'
+ attribution: 'Map data © OpenStreetMap contributors'
});
// Tiles used for the may the user sees (faster)
var previewTileLayer = L.tileLayer(PREVIEWTILELAYER, {
maxZoom: 18,
- attribution: 'Map data © OpenStreetMap contributors'
+ attribution: 'Map data © OpenStreetMap contributors'
});
// Could optionally overlay trails: https://wiki.openstreetmap.org/wiki/Tiles
diff --git a/apps/powersave/README.md b/apps/powersave/README.md
new file mode 100644
index 000000000..5be5e32b5
--- /dev/null
+++ b/apps/powersave/README.md
@@ -0,0 +1,27 @@
+# Power Saver
+
+Save your watch's battery power by halting foreground app execution while the screen is off.
+
+## Features
+- Stops foreground app processes
+- Background processes still run
+- Clears screen
+- Decreases accelerometer polls
+- Foreground app is returned to when screen is turned back on (app state is not preserved)
+
+## Controls
+- Automatically activates when screen times out, timing can be adjusted using normal timeout settings
+- Deactivates when screen is turned back on
+
+## Warnings
+- Due to an Espruino bug, this does not take affect immediately when installed. Switch apps for these features to take affect.
+- This is not compatible with apps that need to run in the foreground even while the screen is off, such as most stopwatch apps and some health trackers.
+- If you check your watch super often (like multiple times per minute), this may end of costing you more power than it saves since the app you are using will have to restart everytime you check it.
+
+## Requests
+
+[Contact information is on my website](https://kyleplo.com/#contact)
+
+## Creator
+
+[kyleplo](https://kyleplo.com)
\ No newline at end of file
diff --git a/apps/powersave/boot.js b/apps/powersave/boot.js
new file mode 100644
index 000000000..d170e9d59
--- /dev/null
+++ b/apps/powersave/boot.js
@@ -0,0 +1,15 @@
+var Storage = Storage || require("Storage");
+Bangle.on("lock", locked => {
+ if(locked){
+ g.clear().reset();
+ Bangle.setLCDBrightness(0);
+ Bangle.setPollInterval(1000);
+ load("powersave.screen.js");
+ }else{
+ load(Storage.read("resumeaftersleep") || JSON.parse(Storage.read("setting.json")).clock);
+ }
+});
+E.on("init", () => {
+ if(__FILE__ && __FILE__ !== "powersave.screen.js")
+ Storage.write("resumeaftersleep", __FILE__);
+});
\ No newline at end of file
diff --git a/apps/powersave/metadata.json b/apps/powersave/metadata.json
new file mode 100644
index 000000000..50603b2c2
--- /dev/null
+++ b/apps/powersave/metadata.json
@@ -0,0 +1,15 @@
+{
+ "id": "powersave",
+ "name": "Power Save",
+ "version": "0.01",
+ "description": "Halts foreground app execution while screen is off while still allowing background processes.",
+ "readme": "README.md",
+ "icon": "powersave.png",
+ "type": "bootloader",
+ "tags": "tool",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"powersave.boot.js","url":"boot.js"},
+ {"name":"powersave.screen.js","url":"boot.js"}
+ ]
+}
\ No newline at end of file
diff --git a/apps/powersave/powersave.png b/apps/powersave/powersave.png
new file mode 100644
index 000000000..fa0399b73
Binary files /dev/null and b/apps/powersave/powersave.png differ
diff --git a/apps/powersave/screen.js b/apps/powersave/screen.js
new file mode 100644
index 000000000..f987f0bbb
--- /dev/null
+++ b/apps/powersave/screen.js
@@ -0,0 +1,7 @@
+var Storage = Storage || require("Storage");
+g.clear();
+Bangle.setLCDBrightness(0);
+Bangle.setPollInterval(1000);
+if(!Bangle.isLocked()){
+ load(Storage.read("resumeaftersleep") || JSON.parse(Storage.read("setting.json")).clock);
+}
\ No newline at end of file
diff --git a/apps/slash/ChangeLog b/apps/slash/ChangeLog
index f3fae1785..734df26dd 100644
--- a/apps/slash/ChangeLog
+++ b/apps/slash/ChangeLog
@@ -1 +1,2 @@
0.01: First version for upload
+0.02: Fix for leftover date artifacts on display.
diff --git a/apps/slash/app.js b/apps/slash/app.js
index f548bcaf7..c594e5916 100644
--- a/apps/slash/app.js
+++ b/apps/slash/app.js
@@ -57,6 +57,10 @@ function draw() {
var minutes = ("0"+m).substr(-2);
g.reset();
+ // If midnight clear display to remove day / date artifacts
+ if (h == 0 && m == 0)
+ g.clear();
+
// Convert to 12hr time mode
if (is12Hour && h > 12) {
h = h - 12;
diff --git a/apps/slash/metadata.json b/apps/slash/metadata.json
index 6bdb4cd53..b08aa7c44 100644
--- a/apps/slash/metadata.json
+++ b/apps/slash/metadata.json
@@ -4,7 +4,7 @@
"shortName":"Slash",
"icon": "slash.png",
"screenshots": [{"url":"screenshot.png"}],
- "version":"0.01",
+ "version":"0.02",
"description": "Slash Watch based on Pebble watch face by Nikki.",
"tags": "clock",
"type": "clock",
diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog
index bf128e2fb..572aaa91e 100644
--- a/apps/smpltmr/ChangeLog
+++ b/apps/smpltmr/ChangeLog
@@ -1,2 +1,3 @@
0.01: Release
-0.02: Rewrite with new interface
\ No newline at end of file
+0.02: Rewrite with new interface
+0.03: Added clock infos to expose timer functionality to clocks.
\ No newline at end of file
diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js
new file mode 100644
index 000000000..dfc70aab9
--- /dev/null
+++ b/apps/smpltmr/clkinfo.js
@@ -0,0 +1,97 @@
+(function() {
+ const TIMER_IDX = "smpltmr";
+
+ function isAlarmEnabled(){
+ try{
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ if(alarmObj===undefined || !alarmObj.on){
+ return false;
+ }
+
+ return true;
+
+ } catch(ex){ }
+ return false;
+ }
+
+ function getAlarmMinutes(){
+ if(!isAlarmEnabled()){
+ return -1;
+ }
+
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
+ }
+
+ function getAlarmMinutesText(){
+ var min = getAlarmMinutes();
+ if(min < 0){
+ return "OFF";
+ }
+
+ return "T-" + String(min);
+ }
+
+ function increaseAlarm(t){
+ try{
+ var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, {
+ timer : (minutes+t)*60*1000,
+ });
+ alarm.reload();
+ } catch(ex){ }
+ }
+
+ function decreaseAlarm(t){
+ try{
+ var minutes = getAlarmMinutes();
+ minutes -= t;
+
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, undefined);
+
+ if(minutes > 0){
+ alarm.setAlarm(TIMER_IDX, {
+ timer : minutes*60*1000,
+ });
+ }
+
+ alarm.reload();
+ } catch(ex){ }
+ }
+
+ var img = atob("GBiBAeAAB+AAB/v/3/v/3/v/3/v/3/v/n/n/H/z+P/48//85//+b//+b//8p//4E//yCP/kBH/oAn/oAX/oAX/oAX/oAX+AAB+AABw==")
+ var smpltmrItems = {
+ name: "Timer",
+ img: img,
+ items: [
+ {
+ name: "Timer",
+ get: () => ({ text: getAlarmMinutesText() + (isAlarmEnabled() ? " min" : ""), img: null}),
+ show: function() { smpltmrItems.items[0].emit("redraw"); },
+ hide: function () {},
+ run: function() { }
+ },
+ ]
+ };
+
+ var offsets = [+1,+5,-1,-5];
+ offsets.forEach((o, i) => {
+ smpltmrItems.items = smpltmrItems.items.concat({
+ name: String(o),
+ get: () => ({ text: getAlarmMinutesText() + " (" + (o > 0 ? "+" : "") + o + ")", img: null}),
+ show: function() { smpltmrItems.items[i+1].emit("redraw"); },
+ hide: function () {},
+ run: function() {
+ if(o > 0) increaseAlarm(o);
+ else decreaseAlarm(Math.abs(o));
+ this.show();
+ }
+ });
+ });
+
+ return smpltmrItems;
+})
\ No newline at end of file
diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json
index cb1ef6eab..4a219fad2 100644
--- a/apps/smpltmr/metadata.json
+++ b/apps/smpltmr/metadata.json
@@ -2,7 +2,7 @@
"id": "smpltmr",
"name": "Simple Timer",
"shortName": "Simple Timer",
- "version": "0.02",
+ "version": "0.03",
"description": "A very simple app to start a timer.",
"icon": "app.png",
"tags": "tool,alarm,timer",
@@ -12,6 +12,7 @@
"readme": "README.md",
"storage": [
{"name":"smpltmr.app.js","url":"app.js"},
+ {"name":"smpltmr.clkinfo.js","url":"clkinfo.js"},
{"name":"smpltmr.img","url":"app-icon.js","evaluate":true}
]
}
diff --git a/apps/swscroll/ChangeLog b/apps/swscroll/ChangeLog
new file mode 100644
index 000000000..c650baf72
--- /dev/null
+++ b/apps/swscroll/ChangeLog
@@ -0,0 +1 @@
+0.01: Inital release.
diff --git a/apps/swscroll/README.md b/apps/swscroll/README.md
new file mode 100644
index 000000000..f97d59b71
--- /dev/null
+++ b/apps/swscroll/README.md
@@ -0,0 +1,9 @@
+This first release seems servicable in testing so far.
+
+To get the standard menu scrolling back, just remove this app from your Bangle.
+
+TODO:
+- Maybe have how much of "trailing space" there are after the last entry should be dynamic in size, now it's always 8 pixels which corresponds to if there are a widget field and a menu title present.
+- I want to change the size of menu entries to be a little bigger vertically.
+
+Drag List Down icon by Icons8
diff --git a/apps/swscroll/app.png b/apps/swscroll/app.png
new file mode 100644
index 000000000..7abd582c2
Binary files /dev/null and b/apps/swscroll/app.png differ
diff --git a/apps/swscroll/boot.js b/apps/swscroll/boot.js
new file mode 100644
index 000000000..fc5650cad
--- /dev/null
+++ b/apps/swscroll/boot.js
@@ -0,0 +1,100 @@
+E.showScroller = (function(options) {
+ /* options = {
+ h = height
+ c = # of items
+ scroll = initial scroll position
+ scrollMin = minimum scroll amount (can be negative)
+ draw = function(idx, rect)
+ select = function(idx)
+ }
+
+ returns {
+ draw = draw all
+ drawItem(idx) = draw specific item
+ }
+ */
+if (!options) return Bangle.setUI(); // remove existing handlers
+
+var menuShowing = false;
+var R = Bangle.appRect;
+var Y = Bangle.appRect.y;
+var n = Math.ceil(R.h/options.h);
+var menuScrollMin = 0|options.scrollMin;
+var menuScrollMax = options.h*options.c - R.h;
+if (menuScrollMax {
+ g.reset().clearRect(R.x,R.y,R.x2,R.y2);
+ g.setClipRect(R.x,R.y,R.x2,R.y2);
+ var a = YtoIdx(R.y);
+ var b = Math.min(YtoIdx(R.y2),options.c-1);
+ for (var i=a;i<=b;i++)
+ options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h});
+ g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
+}, drawItem : i => {
+ var y = idxToY(i);
+ g.reset().setClipRect(R.x,y,R.x2,y+options.h);
+ options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
+ g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
+}};
+var rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither)
+s.draw(); // draw the full scroller
+g.flip(); // force an update now to make this snappier
+Bangle.setUI({
+ mode : "custom",
+ back : options.back,
+ swipe : (hor,ver)=>{
+ pixels = 120;
+ var dy = ver*pixels;
+ if (s.scroll - dy > menuScrollMax)
+ dy = s.scroll - menuScrollMax-8; // Makes it so the last 'page' has the same position as previous pages. This should be done dynamically (change the static 8 to be a variable) so the offset is correct even when no widget field or title field is present.
+ if (s.scroll - dy < menuScrollMin)
+ dy = s.scroll - menuScrollMin;
+ s.scroll -= dy;
+ var oldScroll = rScroll;
+ rScroll = s.scroll &~1;
+ dy = oldScroll-rScroll;
+ if (!dy || options.c<=3) return; //options.c<=3 should maybe be dynamic, so 3 would be replaced by a variable dependent on R=Bangle.appRect. It's here so we don't try to scroll if all entries fit in the app rectangle.
+ g.reset().setClipRect(R.x,R.y,R.x2,R.y2);
+ g.scroll(0,dy);
+ var d = ver*pixels;
+ if (d < 0) {
+ g.setClipRect(R.x,R.y2-(1-d),R.x2,R.y2);
+ let i = YtoIdx(R.y2-(1-d));
+ let y = idxToY(i);
+ //print(i, options.c, options.c-i); //debugging info
+ while (y < R.y2 - (options.h*((options.c-i)<=0)) ) { //- (options.h*((options.c-i)<=0)) makes sure we don't go beyond the menu entries in the menu object "options". This has to do with "dy = s.scroll - menuScrollMax-8" above.
+ options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
+ i++;
+ y += options.h;
+ }
+ } else { // d>0
+ g.setClipRect(R.x,R.y,R.x2,R.y+d);
+ let i = YtoIdx(R.y+d);
+ let y = idxToY(i);
+ //print(i, options.c, options.c-i); //debugging info
+ while (y > R.y-options.h) {
+ options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
+ y -= options.h;
+ i--;
+ }
+ }
+ g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
+ }, touch : (_,e)=>{
+ if (e.y=0) && i {
+ const move = 20 * 60 * 1000; // 20 minutes
+ const look = 20 * 1000; // 20 seconds
+
+ const buzz = _ => {
+ const date = new Date();
+ const day = date.getDay();
+ const hour = date.getHours();
+ // buzz at work
+ if (day >= 1 && day <= 5 &&
+ hour >= 8 && hour <= 17) {
+ Bangle.buzz().then(_ => {
+ setTimeout(Bangle.buzz, look);
+ });
+ }
+ };
+
+ setInterval(buzz, move); // buzz to stand / sit
+})();
diff --git a/apps/widtwenties/metadata.json b/apps/twenties/metadata.json
similarity index 52%
rename from apps/widtwenties/metadata.json
rename to apps/twenties/metadata.json
index 2e51457ac..b1dfe2134 100644
--- a/apps/widtwenties/metadata.json
+++ b/apps/twenties/metadata.json
@@ -1,13 +1,13 @@
{
- "id": "widtwenties",
+ "id": "twenties",
"name": "Twenties",
"shortName": "twenties",
- "version": "0.02",
+ "version": "0.04",
"description": "Buzzes every 20m to stand / sit and look 20ft away for 20s.",
- "icon": "widget.png",
- "type": "widget",
- "tags": "widget,tools",
+ "icon": "app.png",
+ "type": "bootloader",
+ "tags": "alarm,tool",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
- "storage": [{ "name": "widtwenties.wid.js", "url": "widget.js" }]
+ "storage": [{ "name": "twenties.boot.js", "url": "boot.js" }]
}
diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog
index 49e23e1d6..da28d8d5a 100644
--- a/apps/weather/ChangeLog
+++ b/apps/weather/ChangeLog
@@ -13,3 +13,4 @@
0.14: Use weather condition code for icon selection
0.15: Fix widget icon
0.16: Don't mark app as clock
+0.17: Added clkinfo for clocks.
\ No newline at end of file
diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js
new file mode 100644
index 000000000..8e502b7fc
--- /dev/null
+++ b/apps/weather/clkinfo.js
@@ -0,0 +1,43 @@
+(function() {
+ var weather = {
+ temp: "?",
+ hum: "?",
+ wind: "?",
+ };
+
+ var weatherJson = storage.readJSON('weather.json');
+ if(weatherJson !== undefined && weatherJson.weather !== undefined){
+ weather = weatherJson.weather;
+ weather.temp = locale.temp(weather.temp-273.15);
+ weather.hum = weather.hum + "%";
+ weather.wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
+ weather.wind = Math.round(weather.wind[1]) + "kph";
+ }
+
+ var weatherItems = {
+ name: "Weather",
+ img: atob("GBiBAf+///u5//n7//8f/9wHP8gDf/gB//AB/7AH/5AcP/AQH/DwD/uAD84AD/4AA/wAAfAAAfAAAfAAAfgAA/////+bP/+zf/+zfw=="),
+ items: [
+ {
+ name: "temperature",
+ get: () => ({ text: weather.temp, img: atob("GBiBAf/D//+B//8Y//88//88//88//88//88//8k//8k//8k//8k//8k//8k//4kf/5mf/zDP/yBP/yBP/zDP/5mf/48f/8A///D/w==")}),
+ show: function() { weatherItems.items[0].emit("redraw"); },
+ hide: function () {}
+ },
+ {
+ name: "humidity",
+ get: () => ({ text: weather.hum, img: atob("GBiBAf/7///z///x///g///g///Af//Af/3Af/nA//jg//B/v/B/H+A/H8A+D8AeB8AcB4AYA8AYA8AYA+A4A/B4A//4A//8B///Dw==")}),
+ show: function() { weatherItems.items[1].emit("redraw"); },
+ hide: function () {}
+ },
+ {
+ name: "wind",
+ get: () => ({ text: weather.wind, img: atob("GBiBAf4f//wP//nn//Pn//Pzg//nAf/meIAOfAAefP///P//+fAAAfAAB////////wAAP4AAH///z///z//nz//nz//zj//wH//8Pw==")}),
+ show: function() { weatherItems.items[2].emit("redraw"); },
+ hide: function () {}
+ },
+ ]
+ };
+
+ return weatherItems;
+})
\ No newline at end of file
diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json
index 25037de3d..125041ec4 100644
--- a/apps/weather/metadata.json
+++ b/apps/weather/metadata.json
@@ -1,7 +1,7 @@
{
"id": "weather",
"name": "Weather",
- "version": "0.16",
+ "version": "0.17",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
@@ -13,7 +13,8 @@
{"name":"weather.wid.js","url":"widget.js"},
{"name":"weather","url":"lib.js"},
{"name":"weather.img","url":"icon.js","evaluate":true},
- {"name":"weather.settings.js","url":"settings.js"}
+ {"name":"weather.settings.js","url":"settings.js"},
+ {"name":"weather.clkinfo.js","url":"clkinfo.js"}
],
"data": [{"name":"weather.json"}]
}
diff --git a/apps/widagps/metadata.json b/apps/widagps/metadata.json
index 8d3d37aab..ee1eb9f08 100644
--- a/apps/widagps/metadata.json
+++ b/apps/widagps/metadata.json
@@ -1,10 +1,10 @@
{ "id": "widagps",
- "name": "AGPS Widget",
+ "name": "AGPS Widget (automatic download)",
"shortName":"AGPS Widget",
"icon": "widget.png",
"type": "widget",
"version":"0.01",
- "description": "Load AGPS data in the background **using Gadgetbridge**",
+ "description": "Once installed, this widget allows your Bangle.js 2 to load AGPS data in the background **via Gadgetbridge on an Android phone** so it is always up to date. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"readme": "README.md",
"tags": "widget,agps,http",
"supports": ["BANGLEJS2"],
diff --git a/apps/widbt_notify/ChangeLog b/apps/widbt_notify/ChangeLog
index 8f1dab908..b9ecd7b7d 100644
--- a/apps/widbt_notify/ChangeLog
+++ b/apps/widbt_notify/ChangeLog
@@ -11,4 +11,5 @@
0.12: Prevent repeated execution of `draw()` from the current app.
0.13: Added "connection restored" notification. Fixed restoring of the watchface.
0.14: Added configuration option
-0.15: Added option to hide widget when connected
\ No newline at end of file
+0.15: Added option to hide widget when connected
+0.16: Simplify code, add option to disable displaying a message
\ No newline at end of file
diff --git a/apps/widbt_notify/metadata.json b/apps/widbt_notify/metadata.json
index 6def70c64..626ffcb8b 100644
--- a/apps/widbt_notify/metadata.json
+++ b/apps/widbt_notify/metadata.json
@@ -1,8 +1,8 @@
{
"id": "widbt_notify",
"name": "Bluetooth Widget with Notification",
- "version": "0.15",
- "description": "Show the current Bluetooth connection status in the top right of the watch. Optional buzz and/or and hide if disconnected",
+ "version": "0.16",
+ "description": "Show the current Bluetooth connection status with some optional features: show message, buzz on connect/loss, hide always/if connected.",
"icon": "widget.png",
"type": "widget",
"tags": "widget,bluetooth",
diff --git a/apps/widbt_notify/settings.js b/apps/widbt_notify/settings.js
index 1e0d5036b..5c67fed7b 100644
--- a/apps/widbt_notify/settings.js
+++ b/apps/widbt_notify/settings.js
@@ -1,69 +1,57 @@
(function(back) {
- var FILE = "widbt_notify.json";
+
+ var filename = "widbt_notify.json";
+
+ // set Storage and load settings
+ var storage = require("Storage");
var settings = Object.assign({
- secondsOnUnlock: false,
- }, require('Storage').readJSON(FILE, true) || {});
+ showWidget: true,
+ buzzOnConnect: true,
+ buzzOnLoss: true,
+ hideConnected: true,
+ showMessage: true,
+ nextBuzz: 30000
+ }, storage.readJSON(filename, true) || {});
- function writeSettings() {
- require('Storage').writeJSON(FILE, settings);
- }
-
- // Helper method which uses int-based menu item for set of string values
- function stringItems(startvalue, writer, values) {
+ // setup boolean menu entries
+ function boolEntry(key) {
return {
- value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
- format: v => values[v],
- min: 0,
- max: values.length - 1,
- wrap: true,
- step: 1,
+ value: settings[key],
onchange: v => {
- writer(values[v]);
- writeSettings();
+ // change the value of key
+ settings[key] = v;
+ // write to storage
+ storage.writeJSON(filename, settings);
}
};
}
- // Helper method which breaks string set settings down to local settings object
- function stringInSettings(name, values) {
- return stringItems(settings[name], v => settings[name] = v, values);
- }
-
- var mainmenu = {
+ // setup menu
+ var menu = {
"": {
"title": "Bluetooth Widget WN"
},
"< Back": () => back(),
- "Show Widget": {
- value: (settings.showWidget !== undefined ? settings.showWidget : true),
+ "Show Widget": boolEntry("showWidget"),
+ "Buzz on connect": boolEntry("buzzOnConnect"),
+ "Buzz on loss": boolEntry("buzzOnLoss"),
+ "Hide connected": boolEntry("hideConnected"),
+ "Show Message": boolEntry("showMessage"),
+ "Next Buzz": {
+ value: settings.nextBuzz,
+ step: 1000,
+ min: 1000,
+ max: 120000,
+ wrap: true,
+ format: v => (v / 1000) + "s",
onchange: v => {
- settings.showWidget = v;
- writeSettings();
+ settings.nextBuzz = v;
+ storage.writeJSON(filename, settings);
}
- },
- "Buzz on Connect": {
- value: (settings.buzzOnConnect !== undefined ? settings.buzzOnConnect : true),
- onchange: v => {
- settings.buzzOnConnect = v;
- writeSettings();
- }
- },
- "Buzz on loss": {
- value: (settings.buzzOnLoss !== undefined ? settings.buzzOnLoss : true),
- onchange: v => {
- settings.buzzOnLoss = v;
- writeSettings();
- }
- },
- "Hide connected": {
- value: (settings.hideConnected !== undefined ? settings.hideConnected : false),
- onchange: v => {
- settings.hideConnected = v;
- writeSettings();
- }
- }
+ }
};
- E.showMenu(mainmenu);
+ // draw main menu
+ E.showMenu(menu);
-});
+})
\ No newline at end of file
diff --git a/apps/widbt_notify/widget.js b/apps/widbt_notify/widget.js
index de2baa3cf..1b192412a 100644
--- a/apps/widbt_notify/widget.js
+++ b/apps/widbt_notify/widget.js
@@ -1,110 +1,90 @@
-WIDGETS.bluetooth_notify = {
+(function() {
+ // load settings
+ var settings = Object.assign({
+ showWidget: true,
+ buzzOnConnect: true,
+ buzzOnLoss: true,
+ hideConnected: true,
+ showMessage: true,
+ nextBuzz: 30000
+ }, require("Storage").readJSON("widbt_notify.json", true) || {});
+
+ // setup widget with to hide if connected and option set
+ var widWidth = settings.hideConnected && NRF.getSecurityStatus().connected ? 0 : 15;
+
+ // write widget with loaded settings
+ WIDGETS.bluetooth_notify = Object.assign(settings, {
+
+ // set area and width
area: "tr",
- width: 15,
+ width: widWidth,
+
+ // setup warning status
warningEnabled: 1,
- // ------------ Settings -------- very lame - need to improve
- readshowWidget: function() {
- var showWidget;
- const SETTINGSFILE = "widbt_notify.json";
- function def (value, def) {return value !== undefined ? value : def;}
- var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
- showWidget = def(settings.showWidget, true);
- return showWidget;
- },
-
- readBuzzOnConnect: function() {
- var buzzOnConnect;
- const SETTINGSFILE = "widbt_notify.json";
- function def (value, def) {return value !== undefined ? value : def;}
- var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
- buzzOnConnect = def(settings.buzzOnConnect, true);
- return buzzOnConnect;
- },
-
- readBuzzOnLoss: function() {
- var buzzOnLoss;
- const SETTINGSFILE = "widbt_notify.json";
- function def (value, def) {return value !== undefined ? value : def;}
- var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
- buzzOnLoss = def(settings.buzzOnLoss, true);
- return buzzOnLoss;
- },
-
- readHideConnected: function() {
- var hideConnected;
- const SETTINGSFILE = "widbt_notify.json";
- function def (value, def) {return value !== undefined ? value : def;}
- var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
- hideConnected = def(settings.hideConnected, true);
- return hideConnected;
- },
-
-
- // ------------ Settings --------
-
draw: function() {
- if (WIDGETS.bluetooth_notify.readshowWidget()){
- g.reset();
- if (NRF.getSecurityStatus().connected) {
- if (!WIDGETS.bluetooth_notify.readHideConnected()) {
- g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
- g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
- }
- } else {
- // g.setColor(g.theme.dark ? "#666" : "#999");
- g.setColor("#f00"); // red is easier to distinguish from blue
- g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
- }
+ if (this.showWidget) {
+ g.reset();
+ if (NRF.getSecurityStatus().connected) {
+ if (!this.hideConnected) {
+ g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
+ g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
+ }
+ } else {
+ // g.setColor(g.theme.dark ? "#666" : "#999");
+ g.setColor("#f00"); // red is easier to distinguish from blue
+ g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
}
+ }
},
-
- redrawCurrentApp: function(){
- if(typeof(draw)=='function'){
- g.clear();
- draw();
- Bangle.loadWidgets();
- Bangle.drawWidgets();
- }else{
- load(); // fallback. This might reset some variables
+
+ redrawCurrentApp: function() {
+ if (typeof(draw) == 'function') {
+ g.clear();
+ draw();
+ Bangle.loadWidgets();
+ Bangle.drawWidgets();
+ } else {
+ load(); // fallback. This might reset some variables
+ }
+ },
+
+ onNRF: function(connect) {
+ // setup widget with and reload widgets to show/hide if hideConnected is enabled
+ if (this.hideConnected) {
+ this.width = connect ? 0 : 15; // ensures correct redraw
+ Bangle.drawWidgets();
+ } else {
+ // redraw widget
+ this.draw();
+ }
+
+ if (this.warningEnabled) {
+ if (this.showMessage) {
+ E.showMessage( /*LANG*/ 'Connection\n' + (connect ? /*LANG*/ 'restored.' : /*LANG*/ 'lost.'), 'Bluetooth');
+ setTimeout(() => {
+ WIDGETS.bluetooth_notify.redrawCurrentApp();
+ }, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
}
- },
-
- connect: function() {
- if(WIDGETS.bluetooth_notify.warningEnabled == 1){
- E.showMessage(/*LANG*/'Connection\nrestored.', 'Bluetooth');
- setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
-
- WIDGETS.bluetooth_notify.warningEnabled = 0;
- setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds.
-
- var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
- if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnConnect()){
- Bangle.buzz(700, 1); // buzz on connection resume
- }
- }
- WIDGETS.bluetooth_notify.draw();
+ this.warningEnabled = 0;
+ setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', this.nextBuzz); // don't buzz for the next X seconds.
- },
-
- disconnect: function() {
- if(WIDGETS.bluetooth_notify.warningEnabled == 1){
- E.showMessage(/*LANG*/ 'Connection\nlost.', 'Bluetooth');
- setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
-
- WIDGETS.bluetooth_notify.warningEnabled = 0;
- setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds.
-
- var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
- if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnLoss()){
- Bangle.buzz(700, 1); // buzz on connection loss
- }
- }
-
- WIDGETS.bluetooth_notify.draw();
+ var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
+ if (!quiet && (connect ? this.buzzOnConnect : this.buzzOnLoss)) {
+ Bangle.buzz(700, 1); // buzz on connection resume or loss
+ }
+ }
}
-};
-NRF.on('connect', WIDGETS.bluetooth_notify.connect);
-NRF.on('disconnect', WIDGETS.bluetooth_notify.disconnect);
+ });
+
+ // clear variables
+ settings = undefined;
+ widWidth = undefined;
+
+ // setup bluetooth connection events
+ NRF.on('connect', (addr) => WIDGETS.bluetooth_notify.onNRF(addr));
+ NRF.on('disconnect', () => WIDGETS.bluetooth_notify.onNRF());
+
+})()
\ No newline at end of file
diff --git a/apps/widtwenties/ChangeLog b/apps/widtwenties/ChangeLog
deleted file mode 100644
index 87935d810..000000000
--- a/apps/widtwenties/ChangeLog
+++ /dev/null
@@ -1,2 +0,0 @@
-0.01: New Widget!
-0.02: Fix calling null on draw
\ No newline at end of file
diff --git a/apps/widtwenties/README.md b/apps/widtwenties/README.md
deleted file mode 100644
index 1dea18b8e..000000000
--- a/apps/widtwenties/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Twenties
-
-Follow the [20-20-20 rule](https://www.aoa.org/AOA/Images/Patients/Eye%20Conditions/20-20-20-rule.pdf) with discrete reminders. Your BangleJS will buzz every 20 minutes for you to look away from your screen, and then buzz 20 seconds later to look back. Additionally, alternate between standing and sitting every 20 minutes to be standing for [more than 30 minutes](https://uwaterloo.ca/kinesiology-health-sciences/how-long-should-you-stand-rather-sit-your-work-station) per hour.
-
-## Usage
-
-Download this widget and, as long as your watch-face supports widgets, it will automatically run in the background.
-
-## Features
-
-Vibrate to remind you to stand up and look away for healthy living.
-
-## Creator
-
-[@splch](https://github.com/splch/)
diff --git a/apps/widtwenties/widget.js b/apps/widtwenties/widget.js
deleted file mode 100644
index 58bc622eb..000000000
--- a/apps/widtwenties/widget.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// WIDGETS = {}; // <-- for development only
-
-/* run widgets in their own function scope so
-they don't interfere with currently-running apps */
-(() => {
- const move = 20 * 60 * 1000; // 20 minutes
- const look = 20 * 1000; // 20 seconds
-
- buzz = _ => {
- Bangle.buzz().then(_ => {
- setTimeout(Bangle.buzz, look);
- });
- };
-
- // add widget
- WIDGETS.twenties = {
- buzz: buzz,
- draw: _ => { return null; },
- };
-
- setInterval(WIDGETS.twenties.buzz, move); // buzz to stand / sit
-})();
-
-// Bangle.drawWidgets(); // <-- for development only
\ No newline at end of file
diff --git a/modules/clock_info.js b/modules/clock_info.js
index 2ee7fc2f7..6a810371a 100644
--- a/modules/clock_info.js
+++ b/modules/clock_info.js
@@ -2,9 +2,14 @@ var exports = {};
/* Module that allows for loading of clock 'info' displays
that can be scrolled through on the clock face.
-`load()` returns an array of menu items:
+`load()` returns an array of menu objects, where each object contains a list of menu items:
+* 'name' : text to display and identify menu object (e.g. weather)
+* 'img' : a 24x24px image
+* 'items' : menu items such as temperature, humidity, wind etc.
-* 'item.name' : friendly name
+Note that each item is an object with:
+
+* 'item.name' : friendly name to identify an item (e.g. temperature)
* 'item.get' : function that resolves with:
{
'text' : the text to display for this item
@@ -13,21 +18,25 @@ that can be scrolled through on the clock face.
* 'item.show' : called when item should be shown. Enables updates. Call BEFORE 'get'
* 'item.hide' : called when item should be hidden. Disables updates.
* .on('redraw', ...) : event that is called when 'get' should be called again (only after 'item.show')
-* 'item.run' : (optional) called if the info screen is tapped - can perform some action
+* 'item.run' : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user.
See the bottom of this file for example usage...
example.clkinfo.js :
(function() {
- return [
- { name : "Example",
- get : () => ({ text : "Bangle.js",
- img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
- show : () => {},
- hide : () => {}
- }
- ];
+ return {
+ name: "Bangle",
+ img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
+ items: [
+ { name : "Item1",
+ get : () => ({ text : "TextOfItem1",
+ img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
+ show : () => {},
+ hide : () => {}
+ }
+ ]
+ };
}) // must not have a semi-colon!
*/
@@ -38,65 +47,72 @@ exports.load = function() {
var hrm = "--";
var alt = "--";
// callbacks (needed for easy removal of listeners)
- function batteryUpdateHandler() { items[0].emit("redraw"); }
- function stepUpdateHandler() { items[1].emit("redraw"); }
- function hrmUpdateHandler() { items[2].emit("redraw"); }
+ function batteryUpdateHandler() { bangleItems[0].emit("redraw"); }
+ function stepUpdateHandler() { bangleItems[1].emit("redraw"); }
+ function hrmUpdateHandler() { bangleItems[2].emit("redraw"); }
function altUpdateHandler() {
Bangle.getPressure().then(data=>{
if (!data) return;
alt = Math.round(data.altitude) + "m";
- items[3].emit("redraw");
+ bangleItems[3].emit("redraw");
});
}
- // actual items
- var items = [
+ // actual menu
+ var menu = [{
+ name: "Bangle",
+ img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="),
+ items: [
{ name : "Battery",
get : () => ({
text : E.getBattery() + "%",
img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") }),
- show : function() {
- this.interval = setInterval(()=>this.emit('redraw'), 60000);
- Bangle.on("charging", batteryUpdateHandler);
- },
- hide : function() {
- clearInterval(this.interval);
- delete this.interval;
- Bangle.removeListener("charging", batteryUpdateHandler);
- },
+ show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); },
+ hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); },
},
{ name : "Steps", get : () => ({
text : Bangle.getHealthStatus("day").steps,
img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }),
- show : function() { Bangle.on("step", stepUpdateHandler); },
+ show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
hide : function() { Bangle.removeListener("step", stepUpdateHandler); },
},
{ name : "HRM", get : () => ({
text : Math.round(Bangle.getHealthStatus("last").bpm) + " bpm",
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }),
- show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); },
+ show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); },
hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; },
}
- ];
- if (Bangle.getPressure) // Altimeter may not exist
- items.push({ name : "Altitude", get : () => ({
+ ],
+ }];
+ var bangleItems = menu[0].items;
+
+ if (Bangle.getPressure){ // Altimeter may not exist
+ bangleItems.push({ name : "Altitude", get : () => ({
text : alt,
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }),
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
- hide : function() { clearInterval(this.interval); delete this.interval; },
+ hide : function() { clearInterval(this.interval); delete this.interval; },
});
- // now load extra data from a third party files
+ }
+
+ // In case there exists already a menu object b with the same name as the next
+ // object a, we append the items. Otherwise we add the new object a to the list.
require("Storage").list(/clkinfo.js$/).forEach(fn => {
- items = items.concat(eval(require("Storage").read(fn))());
+ var a = eval(require("Storage").read(fn))();
+ var b = menu.find(x => x.name === a.name)
+ if(b) b.items = b.items.concat(a.items);
+ else menu = menu.concat(a);
});
+
// return it all!
- return items;
+ return menu;
};
// Code for testing
/*
g.clear();
-var items = exports.load(); // or require("clock_info").load()
+var menu = exports.load(); // or require("clock_info").load()
+var itemsFirstMenu = menu[0].items;
items.forEach((itm,i) => {
var y = i*24;
console.log("Starting", itm.name);