diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html
index 7d567d34f..d7cd59f1a 100644
--- a/apps/authentiwatch/interface.html
+++ b/apps/authentiwatch/interface.html
@@ -12,8 +12,8 @@ body.select div.select,body.export div.export{display:block}
body.select div.export,body.export div.select{display:none}
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#showqr,body.export div#tokens{display:block}
#tokens th,#tokens td{padding:5px}
-#tokens tr:nth-child(odd){background-color:#ccc}
-#tokens tr:nth-child(even){background-color:#eee}
+#tokens tr:nth-child(odd){background-color:#f1f1fc}
+#tokens tr:nth-child(even){background-color:#fff}
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
#advbtn,#scan,#tokenqr table{text-align:center}
#edittoken tbody#adv{display:none}
@@ -226,15 +226,18 @@ function editToken(id) {
markup += selectMarkup('algorithm', otpAlgos, tokens[id].algorithm);
markup += '';
markup += '
';
- markup += 'Advanced ';
+ markup += 'Advanced ';
markup += ' ';
- markup += 'Cancel Edit ';
- markup += 'Save Changes ';
+ markup += 'Cancel Edit ';
+ markup += ' ';
+ markup += 'Save Changes ';
+ markup += ' ';
if (tokens[id].isnew) {
- markup += 'Scan QR ';
+ markup += 'Scan QR ';
} else {
- markup += 'Show QR ';
- markup += 'Forget Token ';
+ markup += 'Show QR ';
+ markup += ' ';
+ markup += 'Forget Token ';
}
document.getElementById('edit').innerHTML = markup;
document.body.className = 'editing';
@@ -304,9 +307,23 @@ function updateTokens() {
return ' ';
};
const tokenButton = function(fn, id, label, dir) {
- return '' + label + ' ';
+ return '' + label + ' ';
};
- var markup = '';
+ var markup = '';
+ markup += '';
+ markup += 'Add Token ';
+ markup += ' ';
+ markup += 'Save to watch ';
+ markup += ' ';
+ markup += 'Import ';
+ markup += ' ';
+ markup += 'Export ';
+ markup += '
';
+ markup += 'Cancel ';
+ markup += ' ';
+ markup += 'Show QR ';
+ markup += '
';
+ markup += '';
markup += tokenSelect('all');
markup += ' Token Order ';
/* any tokens marked new are cancelled new additions and must be removed */
@@ -331,15 +348,6 @@ function updateTokens() {
markup += '';
}
markup += '
';
- markup += '';
- markup += 'Add Token ';
- markup += 'Save to watch ';
- markup += 'Import ';
- markup += 'Export ';
- markup += '
';
- markup += 'Cancel ';
- markup += 'Show QR ';
- markup += '
';
document.getElementById('tokens').innerHTML = markup;
document.body.className = 'select';
}
@@ -604,7 +612,7 @@ function qrBack() {
@@ -613,7 +621,7 @@ function qrBack() {
diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog
index beba4ed95..cc8bb6306 100644
--- a/apps/calendar/ChangeLog
+++ b/apps/calendar/ChangeLog
@@ -4,3 +4,4 @@
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates
+0.07: Fix off-by-one-error on previous month
diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js
index 62702e349..3f4315811 100644
--- a/apps/calendar/calendar.js
+++ b/apps/calendar/calendar.js
@@ -171,7 +171,7 @@ function drawCalendar(date) {
let days = [];
let nextMonthDay = 1;
let thisMonthDay = 51;
- let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm;
+ let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
for (let i = 0; i < colN * (rowN - 1) + 1; i++) {
if (i < dowNorm) {
days.push(prevMonthDay);
diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json
index 5531c03c3..62d2513ae 100644
--- a/apps/calendar/metadata.json
+++ b/apps/calendar/metadata.json
@@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
- "version": "0.06",
+ "version": "0.07",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog
index e263227e5..91d2bb0bf 100644
--- a/apps/messages/ChangeLog
+++ b/apps/messages/ChangeLog
@@ -39,4 +39,4 @@
0.24: Remove left-over debug statement
0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550)
0.26: Setting to auto-open music
-0.27: Option to auto-unlock the watch when a new message arrives
+0.27: Add 'mark all read' option to popup menu (fix #1624)
diff --git a/apps/messages/app.js b/apps/messages/app.js
index 403f9b5d8..655fc7122 100644
--- a/apps/messages/app.js
+++ b/apps/messages/app.js
@@ -302,6 +302,11 @@ function showMessageSettings(msg) {
saveMessages();
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
},
+ /*LANG*/"Mark all read" : () => {
+ MESSAGES.forEach(msg => msg.new = false);
+ saveMessages();
+ checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
+ },
/*LANG*/"Delete all messages" : () => {
E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => {
if (isYes) {
diff --git a/apps/quicklaunch/ChangeLog b/apps/quicklaunch/ChangeLog
new file mode 100644
index 000000000..ec66c5568
--- /dev/null
+++ b/apps/quicklaunch/ChangeLog
@@ -0,0 +1 @@
+0.01: Initial version
diff --git a/apps/quicklaunch/app-icon.js b/apps/quicklaunch/app-icon.js
new file mode 100644
index 000000000..14ae94823
--- /dev/null
+++ b/apps/quicklaunch/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("kMigILIgPAAYMD/ADBwcGhkAwM5wcA/+2//Av/Rn/giFoyFggkUrFggEKlAkCiApCx+AAYNGoADBkU4AYMQj4DBvEICANkAoIPBgE2B4MAiMAH4MAwECAYNALYUgBIISCHYMYAoQWBAIMEgAYBAIMBwEDDQNgDwUf/4eBg4DCAA4"))
diff --git a/apps/quicklaunch/app.js b/apps/quicklaunch/app.js
new file mode 100644
index 000000000..f2b749e3e
--- /dev/null
+++ b/apps/quicklaunch/app.js
@@ -0,0 +1,120 @@
+var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
+
+var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{name:a.name,type:a.type,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="launch" || app.type=="clock" || !app.type));
+
+apps.sort((a,b)=>{
+ var n=(0|a.sortorder)-(0|b.sortorder);
+ if (n) return n; // do sortorder first
+ if (a.nameb.name) return 1;
+ return 0;
+});
+
+function save(key, value) {
+ settings[key] = value;
+ require("Storage").write("quicklaunch.json",settings);
+}
+
+// Quick Launch menu
+function showMainMenu() {
+ var mainmenu = {
+ "" : { "title" : "Quick Launch" },
+ "< Back" : ()=>{load();}
+ };
+
+ //List all selected apps
+ mainmenu["Left: "+settings.leftapp.name] = function() { E.showMenu(leftmenu); };
+ mainmenu["Right: "+settings.rightapp.name] = function() { E.showMenu(rightmenu); };
+ mainmenu["Up: "+settings.upapp.name] = function() { E.showMenu(upmenu); };
+ mainmenu["Down: "+settings.downapp.name] = function() { E.showMenu(downmenu); };
+ mainmenu["Tap: "+settings.tapapp.name] = function() { E.showMenu(tapmenu); };
+
+ return E.showMenu(mainmenu);
+}
+
+//Left swipe menu
+var leftmenu = {
+ "" : { "title" : "Left Swipe" },
+ "< Back" : showMainMenu
+};
+
+leftmenu["(none)"] = function() {
+ save("leftapp", {"name":"(none)"});
+ showMainMenu();
+};
+apps.forEach((a)=>{
+ leftmenu[a.name] = function() {
+ save("leftapp", a);
+ showMainMenu();
+ };
+});
+
+//Right swipe menu
+var rightmenu = {
+ "" : { "title" : "Right Swipe" },
+ "< Back" : showMainMenu
+};
+
+rightmenu["(none)"] = function() {
+ save("rightapp", {"name":"(none)"});
+ showMainMenu();
+};
+apps.forEach((a)=>{
+ rightmenu[a.name] = function() {
+ save("rightapp", a);
+ showMainMenu();
+ };
+});
+
+//Up swipe menu
+var upmenu = {
+ "" : { "title" : "Up Swipe" },
+ "< Back" : showMainMenu
+};
+
+upmenu["(none)"] = function() {
+ save("upapp", {"name":"(none)"});
+ showMainMenu();
+};
+apps.forEach((a)=>{
+ upmenu[a.name] = function() {
+ save("upapp", a);
+ showMainMenu();
+ };
+});
+
+//Down swipe menu
+var downmenu = {
+ "" : { "title" : "Down Swipe" },
+ "< Back" : showMainMenu
+};
+
+downmenu["(none)"] = function() {
+ save("downapp", {"name":"(none)"});
+ showMainMenu();
+};
+apps.forEach((a)=>{
+ downmenu[a.name] = function() {
+ save("downapp", a);
+ showMainMenu();
+ };
+});
+
+//Tap menu
+var tapmenu = {
+ "" : { "title" : "Tap" },
+ "< Back" : showMainMenu
+};
+
+tapmenu["(none)"] = function() {
+ save("tapapp", {"name":"(none)"});
+ showMainMenu();
+};
+apps.forEach((a)=>{
+ tapmenu[a.name] = function() {
+ save("tapapp", a);
+ showMainMenu();
+ };
+});
+
+showMainMenu();
diff --git a/apps/quicklaunch/app.png b/apps/quicklaunch/app.png
new file mode 100644
index 000000000..3d1d0fdd2
Binary files /dev/null and b/apps/quicklaunch/app.png differ
diff --git a/apps/quicklaunch/boot.js b/apps/quicklaunch/boot.js
new file mode 100644
index 000000000..3670c4776
--- /dev/null
+++ b/apps/quicklaunch/boot.js
@@ -0,0 +1,67 @@
+(function() {
+ var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
+
+ //list all sources
+ var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{src:a.src};});
+
+ //populate empty app list
+
+ if (!settings.leftapp) {
+ settings["leftapp"] = {"name":"(none)"};
+ require("Storage").write("quicklaunch.json",settings);
+ }
+ if (!settings.rightapp) {
+ settings["rightapp"] = {"name":"(none)"};
+ require("Storage").write("quicklaunch.json",settings);
+ }
+ if (!settings.upapp) {
+ settings["upapp"] = {"name":"(none)"};
+ require("Storage").write("quicklaunch.json",settings);
+ }
+ if (!settings.downapp) {
+ settings["downapp"] = {"name":"(none)"};
+ require("Storage").write("quicklaunch.json",settings);
+ }
+ if (!settings.tapapp) {
+ settings["tapapp"] = {"name":"(none)"};
+ require("Storage").write("quicklaunch.json",settings);
+ }
+
+ //activate on clock faces
+ var sui = Bangle.setUI;
+ Bangle.setUI = function(mode, cb) {
+ sui(mode,cb);
+ if(!mode) return;
+ if ("object"==typeof mode) mode = mode.mode;
+ if (!mode.startsWith("clock")) return;
+
+ function tap() {
+ //tap, check if source exists, launch
+ if ((settings.tapapp.src) && apps.some(e => e.src === settings.tapapp.src)) load (settings.tapapp.src);
+ }
+
+ let drag;
+ let e;
+
+ Bangle.on("touch",tap);
+ Bangle.on("drag", e => {
+ if (!drag) { // start dragging
+ drag = {x: e.x, y: e.y};
+ } else if (!e.b) { // released
+ const dx = e.x-drag.x, dy = e.y-drag.y;
+ drag = null;
+ //horizontal swipes, check if source exists, launch
+ if (Math.abs(dx)>Math.abs(dy)+10) {
+ if ((settings.leftapp.src) && apps.some(e => e.src === settings.leftapp.src) && dx<0) load(settings.leftapp.src);
+ if ((settings.rightapp.src) && apps.some(e => e.src === settings.rightapp.src) && dx>0) load(settings.rightapp.src);
+ }
+ //vertical swipes, check if source exists, launch
+ else if (Math.abs(dy)>Math.abs(dx)+10) {
+ if ((settings.upapp.src) && apps.some(e => e.src === settings.upapp.src) && dy<0) load(settings.upapp.src);
+ if ((settings.downapp.src) && apps.some(e => e.src === settings.downapp.src) && dy>0) load(settings.downapp.src);
+ }
+ }
+ });
+
+ };
+})();
diff --git a/apps/quicklaunch/metadata.json b/apps/quicklaunch/metadata.json
new file mode 100644
index 000000000..6411d1a5f
--- /dev/null
+++ b/apps/quicklaunch/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "quicklaunch",
+ "name": "Quick Launch",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice.",
+ "tags": "tools, system",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"quicklaunch.app.js","url":"app.js"},
+ {"name":"quicklaunch.boot.js","url":"boot.js"},
+ {"name":"quicklaunch.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [{"name":"quicklaunch.json"}]
+}
diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog
index 46fdb7e7e..9f1a547b1 100644
--- a/apps/run/ChangeLog
+++ b/apps/run/ChangeLog
@@ -9,4 +9,5 @@
0.08: Added support for notifications from exstats. Support all stats from exstats
0.09: Fix broken start/stop if recording not enabled (fix #1561)
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)
-0.11: Notifications fixes
\ No newline at end of file
+0.11: Notifications fixes
+0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
\ No newline at end of file
diff --git a/apps/run/app.js b/apps/run/app.js
index fb8158e58..19dcd7e88 100644
--- a/apps/run/app.js
+++ b/apps/run/app.js
@@ -59,7 +59,7 @@ function onStartStop() {
layout.render();
})
);
- } else if (!settings.record && WIDGETS["recorder"]) {
+ } else {
prepPromises.push(
WIDGETS["recorder"].setRecording(false)
);
@@ -68,7 +68,7 @@ function onStartStop() {
if (!prepPromises.length) // fix for Promise.all bug in 2v12
prepPromises.push(Promise.resolve());
-
+
Promise.all(prepPromises)
.then(() => {
if (running) {
diff --git a/apps/run/metadata.json b/apps/run/metadata.json
index 09e5a3bed..b1b5617be 100644
--- a/apps/run/metadata.json
+++ b/apps/run/metadata.json
@@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
- "version":"0.11",
+ "version":"0.12",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",
diff --git a/apps/run/settings.js b/apps/run/settings.js
index 6a7d169c4..240df9f07 100644
--- a/apps/run/settings.js
+++ b/apps/run/settings.js
@@ -91,7 +91,7 @@
];
notificationsMenu[/*LANG*/"Dist Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
- min: 0, max: vibTimes.length,
+ min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.dist.notifications = vibTimes[v];
@@ -101,7 +101,7 @@
}
notificationsMenu[/*LANG*/"Step Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
- min: 0, max: vibTimes.length,
+ min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.step.notifications = vibTimes[v];
@@ -111,7 +111,7 @@
}
notificationsMenu[/*LANG*/"Time Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
- min: 0, max: vibTimes.length,
+ min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.time.notifications = vibTimes[v];
diff --git a/apps/todolist/README.md b/apps/todolist/README.md
index 27c7cfb63..0e1beb74a 100644
--- a/apps/todolist/README.md
+++ b/apps/todolist/README.md
@@ -2,39 +2,63 @@ Todo List
========
This is a simple Todo List application.
+The content is loaded from a JSON file.
+A task can be marked as completed or uncompleted.

-The content is loaded from a JSON file.
-You can mark a task as completed.
+Once installed, the list can be modified via the `Download data from app` icon in the [Bangle.js App Store](https://banglejs.com/apps/) (TodoList app).
+
+
+
JSON file content example:
```javascript
[
{
- name: "Pro",
- children: [
+ "name": "Pro",
+ "children": [
{
- name: "Read doc",
- done: true,
- children: [],
+ "name": "Read doc",
+ "done": true,
+ "children": []
}
- ],
+ ]
},
{
- name: "Pers",
- children: [
+ "name": "Pers",
+ "children": [
{
- name: "Grocery",
- children: [
- { name: "Milk", done: false, children: [] },
- { name: "Eggs", done: false, children: [] },
- { name: "Cheese", done: false, children: [] },
- ],
+ "name": "Grocery",
+ "children": [
+ {
+ "name": "Milk",
+ "done": false,
+ "children": []
+ },
+ {
+ "name": "Eggs",
+ "done": false,
+ "children": []
+ },
+ {
+ "name": "Cheese",
+ "done": false,
+ "children": []
+ }
+ ]
},
- { name: "Workout", done: false, children: [] },
- { name: "Learn Rust", done: false, children: [] },
- ],
- },
+ {
+ "name": "Workout",
+ "done": false,
+ "children": []
+ },
+ {
+ "name": "Learn Rust",
+ "done": false,
+ "children": []
+ }
+ ]
+ }
]
```
\ No newline at end of file
diff --git a/apps/todolist/interface.html b/apps/todolist/interface.html
new file mode 100644
index 000000000..5b9cb038e
--- /dev/null
+++ b/apps/todolist/interface.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+ Reload from watch
+ Upload to watch
+ Download
+
+
+
+
+
+
+
diff --git a/apps/todolist/metadata.json b/apps/todolist/metadata.json
index 0833a86bd..a8eb6118b 100644
--- a/apps/todolist/metadata.json
+++ b/apps/todolist/metadata.json
@@ -10,6 +10,7 @@
"tags": "tool,todo",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
+ "interface": "interface.html",
"storage": [
{ "name": "todolist.app.js", "url": "app.js" },
{ "name": "todolist.img", "url": "app-icon.js", "evaluate": true }
diff --git a/apps/todolist/screenshot4.png b/apps/todolist/screenshot4.png
new file mode 100644
index 000000000..43db1b0e6
Binary files /dev/null and b/apps/todolist/screenshot4.png differ
diff --git a/modules/exstats.js b/modules/exstats.js
index ec0a838a7..b22f3f5d3 100644
--- a/modules/exstats.js
+++ b/modules/exstats.js
@@ -135,7 +135,7 @@ Bangle.on("GPS", function(fix) {
if (stats["dist"]) stats["dist"].emit("changed",stats["dist"]);
var duration = Date.now() - state.startTime; // in ms
state.avrSpeed = state.distance * 1000 / duration; // meters/sec
- state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec
+ if (!isNaN(fix.speed)) state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec
if (stats["pacea"]) stats["pacea"].emit("changed",stats["pacea"]);
if (stats["pacec"]) stats["pacec"].emit("changed",stats["pacec"]);
if (stats["speed"]) stats["speed"].emit("changed",stats["speed"]);