diff --git a/.eslintignore b/.eslintignore
index 1e3abd9ff..a82960313 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,8 @@
# Needs to be ignored because it uses ESM export/import
apps/gipy/pkg/gps.js
+apps/gipy/pkg/gps.d.ts
+apps/gipy/pkg/gps_bg.wasm.d.ts
# Needs to be ignored because it includes broken JS
apps/health/chart.min.js
diff --git a/.eslintrc.js b/.eslintrc.js
index e79f87a5d..b7590a77e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -112,6 +112,7 @@ module.exports = {
"getSerial": "readonly",
"getTime": "readonly",
"global": "readonly",
+ "globalThis": "readonly",
"HIGH": "readonly",
"I2C1": "readonly",
"Infinity": "readonly",
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e2fbf5609..a20c7ed7c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,3 +5,5 @@ updates:
directory: "/"
schedule:
interval: "daily"
+ reviewers:
+ - "gfwilliams"
diff --git a/README.md b/README.md
index d595c7df1..0bc511450 100644
--- a/README.md
+++ b/README.md
@@ -230,7 +230,7 @@ and which gives information about the app for the Launcher.
"src":"-myappid", // source file
"type":"widget/clock/app/bootloader/...", // optional, default "app"
// see 'type' in 'metadata.json format' below for more options/info
- "version":"1.23",
+ "version":"0.01",
// added by BangleApps loader on upload based on metadata.json
"files:"file1,file2,file3",
// added by BangleApps loader on upload - lists all files
@@ -249,7 +249,7 @@ and which gives information about the app for the Launcher.
{ "id": "appid", // 7 character app id
"name": "Readable name", // readable name
"shortName": "Short name", // short name for launcher
- "version": "0v01", // the version of this app
+ "version": "0.01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
"screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
@@ -270,7 +270,8 @@ and which gives information about the app for the Launcher.
// 'notify' - provides 'notify' library for showing notifications
// 'locale' - provides 'locale' library for language-specific date/distance/etc
// (a version of 'locale' is included in the firmware)
- "tags": "", // comma separated tag list for searching
+ // 'defaultconfig' - a set of apps that will can be installed and will wipe out all previously installed apps
+ "tags": "", // comma separated tag list for searching (don't include uppercase or spaces)
// common types are:
// 'clock' - it's a clock
// 'widget' - it is (or provides) a widget
@@ -289,6 +290,7 @@ and which gives information about the app for the Launcher.
"dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
"provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
+ "provides_features" : ["welcome"] // optional, this app provides some feature, used to ensure two aren't installed at once. Currently just 'welcome'
"default" : true, // set if an app is the default implementer of something (a widget/module/etc)
"readme": "README.md", // if supplied, a link to a markdown-style text file
// that contains more information about this app (usage, etc)
diff --git a/android.html b/android.html
index a0bc6075a..1dc015468 100644
--- a/android.html
+++ b/android.html
@@ -85,6 +85,7 @@
Online
Clock Info
Health
+ Fonts
Favourites
-
+
Install
@@ -203,7 +212,7 @@
-
+
@@ -416,7 +425,7 @@ if (el) el.addEventListener("click", event=>{
if (webrtc) showWebRTCID(webrtc.peerId);
else {
webrtc = webrtcInit({
- bridge:true,
+ bridge:true,
onStatus : function(s) {
showToast(s);
},
@@ -432,7 +441,7 @@ if (el) el.addEventListener("click", event=>{
onPortDisconnect : function(serialPort) {
},
onPortWrite : function(data, cb) {
- Puck.write(data, cb);
+ Puck.write(data, cb);
}
});
connection.on("data", function(d) {
diff --git a/apps/93dub/ChangeLog b/apps/93dub/ChangeLog
index 712a52a37..9a07b38ad 100644
--- a/apps/93dub/ChangeLog
+++ b/apps/93dub/ChangeLog
@@ -5,3 +5,4 @@
0.05: Display time, even on Thursday
0.06: Fix light theme issue, where widgets would end up on a light strip
0.07: Minor code improvements
+0.08: Support Fast Loading
diff --git a/apps/93dub/app.js b/apps/93dub/app.js
index c9f670993..dbf8ec907 100644
--- a/apps/93dub/app.js
+++ b/apps/93dub/app.js
@@ -1,146 +1,162 @@
-// get 12 hour status, code from barclock
-const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
+{
+ // get 12 hour status, code from barclock
+ const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
-// define background
-var imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
+ // define background
+ const imgBg = require("heatshrink").decompress(atob("2GwgJC/AH4A/AH4A/AH4A/AH4A/ACcGAhAV/Cp3gvdug+Gj0AgeABYMBAQMIggVEg/w/9/h/Gn8As3ACpk559zznmseAs0B13nq/Rie+uodCIIUZw9hzFmv+AgcCmco7MRilow1ACpN8gFhwMilFRCoMowgVEIIVhIINhwFg4GiCpfw/dhx/mn4uBCoXRhWktAVFTIVhw9mj8YseDkUnqPEoeuugVEAAlgSgICBACAVC8AUQCQQVSAEsD/4ASeYgA/ACkHNiK5Cj4VR/AVBng+RCQVwCqMOAQPhIKOHgEB44VR8YVBx4VR+eAgOfCqPxwEDCqX5CoKvS/PAgc/YqQVU/gV/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/Cv4V/CsMfCqP4CoOfCqP54EBx4VR+OAgPPCqPzwEA44VR4cAgHhCqMHCoNwAQIAPjwCBngVRvgCBV6XwCoMHCqPAHyIA/AEigEf4IAOkAEDoAPJWAtA+PHv+Al6uPCofAGAgALoHz51/8AVT+IVS+4VPpMR73woH27n/8Eh8+ZmadIqsoyGICofAkMUktJFZAVBzgVBv34YgMhi8RkIVJnGQIIN8/H34FB8kJiIVIkVEyGQkF8/Pj4GBkhBKCoOexEQvHx8fBgMXzMxTJkICoXCVx8AggDGABsD/4AB/AVQAH4APA"));
-// define fonts
-// reg number first char 48 28 by 41
-var fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
-// tiny font for percentage first char 48 6 by 8
-//var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
-// date font first char 48 12 by 15
-var fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ // define fonts
+ // reg number first char 48 28 by 41
+ const fontNum = atob("AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//w//j4//A/+P4/8A/4/4AAAAD/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/4AAAAP/wAAAAf/gAAAA//AAAAB/+AAAAD/8AAAAH/wAAAAH/H/gH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wB/4AP/4H/4A//4f/4D//5//4P//h//4//+B//4AAAAAAAAAAAAAAAAAf/+AAAB//4gAAD//jgAAD/+PgABj/4/gAHj/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f88AAfx/8wAAfH/8AAAcf/8AAAR//4AAAH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA4AAAAAD4AAYAAP4AD8AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAHgAH/H/GH/H8f/gf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAP//AAAAP//AAAAP//AAAAP/8AAAAP/2AAAAP/eAAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAB/7x/4AH/7H/4Af/4f/4B//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wAAAD//wAAAj//gAADj/+AAAPj/5gAA/j/ngAD/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8AA8f8fwAAx/8fAAAH/8cAAAf/8QAAA//8AAAA//8AAAAAAAAAAAAAA//8D//g//8P/+I//8//44//0//j4//Y/+P4/94/4/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/AAPH/H8AAMf/HwAAB//HAAAH//EAAAH//AAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAGAAAAAAOAAAAAAeAAAAAA+AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB+AAAAAD8AAAAAH4AAAAAPwAAAAAfgAAAAA/AAAAAB8AAAAADx/4B/4HH/4H/4Mf/4f/4R//5//4H//h//4f/+B//4AAAAAAAAAAAAAD//wP/+D//w//4j//z//jj//T/+Pj/9j/4/j/3j/j/gAfgAP/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/AA/AAf8f+8f8fx/+x/8fH/+H/8cf/+f/8R//4f/8H//gf/8AAAAAAAAAAAAAA//8AAAA//8AAAI//8AAA4//0AAD4//YAAP4/94AA/4AH4AD/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/4APwAP/wAfgAf/gA/AA//AB+AB/+AD8AD/8AH4AH/wAPwAH/H/vH/H8f/sf/Hx//h//HH//n//Ef/+H//B//4H//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ // tiny font for percentage first char 48 6 by 8
+ //var fontTiny = atob("AH6BgYF+ACFB/wEBAGGDhYlxAEKBkZFuAAx0hP8EAPqRkZGOAH6RkZFOAICHmKDAAG6RkZFuAHKJiYl+AAAAAAAAAAAAAAAA");
+ // date font first char 48 12 by 15
+ const fontDate = atob("AAAAAfv149wAeADwAeADwAeADvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAPHn9/AAAAAAP0A9wweGDwweGDwweGDvAL8AAAAAAAAAAAgwOGDwweGDwweGDvHp98AAAAA/gB6AAwAGAAwAGAAwAGAPHj9/AAAAAfgF6BwweGDwweGDwweGDgHoB+AAAAAfv169wweGDwweGDwweGDgHoB+AAAAAAAAAAgAGAAwAGAAwAGAAvHh9/AAAAAfv169wweGDwweGDwweGDvHr9+AAAAAfgF6BwweGDwweGDwweGDvHr9+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
-// define days of the week images
-var imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
-var imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
-var imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
-var imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
-var imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
-var imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
-var imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
+ // define days of the week images
+ const imgMon = E.toArrayBuffer(atob("Ig8BgHwfD5AvB8HD8z8wMPzPzMQzM/M/DMz8z8c7f7f7z////3Oz+3+PzPzPw/M/M/D8z8z8PzPzPw/vB8/n/8H3/A=="));
+ const imgTue = E.toArrayBuffer(atob("Ig8BwDv9wDAOfmgf/5+Z///n5n/5+fmf/n5+Z//fv9oH////Af37/b/+fn5n/5+fmf/n5+Z/+fn5n/5/g+gfn+D8AA=="));
+ const imgWed = E.toArrayBuffer(atob("Ig8Bf7gHgM/NA9Az8z/z8PzP/Pw/M/8/D8z/z8c7QPf7z+A//3O3/3+MzP/PwzM/8/D8z/z8PzP/PxAtA9A4B4B4DA=="));
+ const imgThu = E.toArrayBuffer(atob("Ig8BgHf7f6Ac/M/P/z8z8//PzPzz8/M/PPz8z8+/QLf7/+A///v3+3+8/PzPzz8/M/PPz8z88/PzPzz8/vB/P3/8HA=="));
+ const imgFri = E.toArrayBuffer(atob("Ig8B/wDwP7+geg/P5/5+c/n/n5z+f+fnP5/5+c/oHoF7/AfAf/7/7/+/n/k/z+f+R/P5/5j8/n/nHz+/++PP7//8+A=="));
+ const imgSat = E.toArrayBuffer(atob("Ig8B4DwDwDgOgXAJ/5+f/n/n5/+f+fn55/5+fnoHoF/fAfAf//+b/f3/5n5+f/mfn5/+Z+fn//n5+eAef358B7//nA=="));
+ const imgSun = E.toArrayBuffer(atob("Ig8BwHf7D7Ac/MHD/z8wMP/PzMQ/8/M/D/z8z8QPf7f6A/////83+3+/zPzPz/M/M/P8z8z8//PzPwA/B8/oD8H3/A=="));
-// define icons
-var imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
-//var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
-var img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
-var imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
+ // define icons
+ const imgSep = E.toArrayBuffer(atob("BhsBAAAAAA///////////////AAAAAAA"));
+ //var imgPercent = E.toArrayBuffer(atob("BwcBuq7ffbqugA=="));
+ const img24hr = E.toArrayBuffer(atob("EwgBj7vO53na73tcDtu9uDev7vA93g=="));
+ const imgPM = E.toArrayBuffer(atob("EwgB+HOfdnPu1X3ar4dV9+q+/bfftg=="));
-//vars
-var separator = true;
-var is24hr = !is12Hour;
-var leadingZero = true;
+ //vars
+ let separator = true;
+ let is24hr = !is12Hour;
+ let leadingZero = true;
-//the following 2 sections are used from waveclk to schedule minutely updates
-// timeout used to update every minute
-var drawTimeout;
+ //the following 2 sections are used from waveclk to schedule minutely updates
+ // timeout used to update every minute
+ let drawTimeout;
-// schedule a draw for the next minute
-function queueDraw() {
- if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = setTimeout(function() {
- drawTimeout = undefined;
- draw();
- }, 60000 - (Date.now() % 60000));
-}
-
-function drawBackground() {
- g.setBgColor(0,0,0);
- g.setColor(1,1,1);
- g.clear();
- g.drawImage(imgBg,0,0);
- g.reset();
-}
-
-function draw(){
- drawBackground();
- var date = new Date();
- var h = date.getHours(), m = date.getMinutes();
- var d = date.getDate(), w = date.getDay();
- g.reset();
- g.setBgColor(0,0,0);
- g.setColor(1,1,1);
-
- //draw 24 hr indicator and 12 hr specific behavior
- if (is24hr){
- g.drawImage(img24hr,32, 65);
- if (leadingZero){
- h = ("0"+h).substr(-2);
- }
- } else if (h > 12) {
- g.drawImage(imgPM,40, 70);
- h = h - 12;
- if (leadingZero){
- h = ("0"+h).substr(-2);
- } else {
- h = " " + h;
- }
- } else if (h === 0) {
- // display 12:00 instead of 00:00 for 12 hr mode
- h = "12";
- }
-
- //draw separator
- if (separator){
- g.drawImage(imgSep, 85,98);}
-
- //draw day of week
- var imgW = null;
- if (w == 0) {imgW = imgSun;}
- if (w == 1) {imgW = imgMon;}
- if (w == 2) {imgW = imgTue;}
- if (w == 3) {imgW = imgWed;}
- if (w == 4) {imgW = imgThu;}
- if (w == 5) {imgW = imgFri;}
- if (w == 6) {imgW = imgSat;}
- g.drawImage(imgW, 85, 63);
-
-
- // draw nums
- // draw time
- g.setColor(0,0,0);
- g.setBgColor(1,1,1);
- g.setFontCustom(fontNum, 48, 28, 41);
- if (h<10) {
- if (leadingZero) {
- h = ("0"+h).substr(-2);
- } else {
- h = " " + h;
- }
- }
- g.drawString(h, 25, 90, true);
- g.drawString(("0"+m).substr(-2), 92, 90, true);
- // draw date
- g.setFontCustom(fontDate, 48, 12, 15);
- g.drawString(("0"+d).substr(-2), 123,63, true);
-
- // widget redraw
- Bangle.drawWidgets();
- queueDraw();
-}
-
-/**
- * This watch is mostly dark, it does not make sense to respect the
- * light theme as you end up with a white strip at the top for the
- * widgets and black watch. So set the colours to the dark theme.
- *
- */
-g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
-draw();
-
-//the following section is also from waveclk
-Bangle.on('lcdPower',on=>{
- if (on) {
- draw(); // draw immediately, queue redraw
- } else { // stop draw timer
+ // schedule a draw for the next minute
+ let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = undefined;
- }
-});
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+ };
-Bangle.setUI("clock");
-Bangle.loadWidgets();
-Bangle.drawWidgets();
+ let drawBackground = function() {
+ g.setBgColor(0, 0, 0);
+ g.setColor(1, 1, 1);
+ g.clear();
+ g.drawImage(imgBg, 0, 0);
+ g.reset();
+ };
+
+ let draw = function() {
+ drawBackground();
+ let date = new Date();
+ let h = date.getHours(),
+ m = date.getMinutes();
+ let d = date.getDate(),
+ w = date.getDay();
+ g.reset();
+ g.setBgColor(0, 0, 0);
+ g.setColor(1, 1, 1);
+
+ //draw 24 hr indicator and 12 hr specific behavior
+ if (is24hr){
+ g.drawImage(img24hr,32, 65);
+ if (leadingZero){
+ h = ("0"+h).substr(-2);
+ }
+ } else if (h > 12) {
+ g.drawImage(imgPM,40, 70);
+ h = h - 12;
+ if (leadingZero){
+ h = ("0"+h).substr(-2);
+ } else {
+ h = " " + h;
+ }
+ } else if (h === 0) {
+ // display 12:00 instead of 00:00 for 12 hr mode
+ h = "12";
+ }
+
+ //draw separator
+ if (separator) {
+ g.drawImage(imgSep, 85, 98);
+ }
+
+ //draw day of week
+ let imgW = null;
+ if (w == 0) {imgW = imgSun;}
+ if (w == 1) {imgW = imgMon;}
+ if (w == 2) {imgW = imgTue;}
+ if (w == 3) {imgW = imgWed;}
+ if (w == 4) {imgW = imgThu;}
+ if (w == 5) {imgW = imgFri;}
+ if (w == 6) {imgW = imgSat;}
+ g.drawImage(imgW, 85, 63);
+
+
+ // draw nums
+ // draw time
+ g.setColor(0, 0, 0);
+ g.setBgColor(1, 1, 1);
+ g.setFontCustom(fontNum, 48, 28, 41);
+ if (h < 10) {
+ if (leadingZero) {
+ h = ("0" + h).substr(-2);
+ } else {
+ h = " " + h;
+ }
+ }
+ g.drawString(h, 25, 90, true);
+ g.drawString(("0" + m).substr(-2), 92, 90, true);
+ // draw date
+ g.setFontCustom(fontDate, 48, 12, 15);
+ g.drawString(("0" + d).substr(-2), 123, 63, true);
+
+ // widget redraw
+ Bangle.drawWidgets();
+ queueDraw();
+ };
+
+ /**
+ * This watch is mostly dark, it does not make sense to respect the
+ * light theme as you end up with a white strip at the top for the
+ * widgets and black watch. So set the colours to the dark theme.
+ *
+ */
+ g.setTheme({
+ bg: "#000",
+ fg: "#fff",
+ dark: true
+ }).clear();
+ draw();
+
+ //the following section is also from waveclk
+ let onLCDPower = on => {
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+ };
+ Bangle.on('lcdPower', onLCDPower);
+
+ Bangle.setUI({
+ mode: "clock",
+ remove: function() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ Bangle.removeListener('lcdPower', onLCDPower);
+ }
+ });
+ Bangle.loadWidgets();
+ Bangle.drawWidgets();
+}
diff --git a/apps/93dub/metadata.json b/apps/93dub/metadata.json
index 4b0b7bd21..97b771902 100644
--- a/apps/93dub/metadata.json
+++ b/apps/93dub/metadata.json
@@ -3,7 +3,7 @@
"shortName":"93 Dub",
"icon": "93dub.png",
"screenshots": [{"url":"screenshot.png"}],
- "version": "0.07",
+ "version": "0.08",
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
"tags": "clock",
"type": "clock",
diff --git a/apps/UI4swatch/metadata.json b/apps/UI4swatch/metadata.json
index bcb0e6c51..5b4abf406 100644
--- a/apps/UI4swatch/metadata.json
+++ b/apps/UI4swatch/metadata.json
@@ -5,7 +5,7 @@
"version": "0.02",
"description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
"icon": "app.png",
- "tags": "Color,input,buttons,touch,UI",
+ "tags": "color,input,buttons,touch,ui",
"supports": ["BANGLEJS"],
"readme": "README.md",
"screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}],
diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json
index 5a3f17639..1185c7202 100644
--- a/apps/Uke/metadata.json
+++ b/apps/Uke/metadata.json
@@ -4,8 +4,8 @@
"version": "0.04",
"description": "Wrist mounted ukulele chords",
"icon": "app.png",
- "tags": "uke, chords",
- "supports" : ["BANGLEJS2"],
+ "tags": "uke,chords",
+ "supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"Uke.app.js","url":"app.js"},
diff --git a/apps/_example_clock/app.js b/apps/_example_clock/app.js
index d1f997136..a5d114b3a 100644
--- a/apps/_example_clock/app.js
+++ b/apps/_example_clock/app.js
@@ -24,10 +24,10 @@
var dateStr = require("locale").date(date);
// draw time
g.setFontAlign(0,0).setFont("Vector",48);
- g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
+ g.clearRect(0,y-20,g.getWidth(),y+25); // clear the background
g.drawString(timeStr,x,y);
// draw date
- y += 35;
+ y += 30;
g.setFontAlign(0,0).setFont("6x8");
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
g.drawString(dateStr,x,y);
@@ -41,6 +41,8 @@
// Show launcher when middle button pressed
Bangle.setUI({mode:"clock", remove:function() {
// free any memory we allocated to allow fast loading
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
}});
// Load widgets
Bangle.loadWidgets();
diff --git a/apps/a_dndtoggle/settings.js b/apps/a_dndtoggle/settings.js
index 5316525b3..483af8c97 100644
--- a/apps/a_dndtoggle/settings.js
+++ b/apps/a_dndtoggle/settings.js
@@ -29,5 +29,4 @@
}
E.showMenu(buildMainMenu());
- });
-
\ No newline at end of file
+ })
diff --git a/apps/activepedom/settings.js b/apps/activepedom/settings.js
index 3b64d8735..16799f0db 100644
--- a/apps/activepedom/settings.js
+++ b/apps/activepedom/settings.js
@@ -109,4 +109,4 @@
},
};
E.showMenu(menu);
-});
+})
diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog
index d1b95a702..4d2e28096 100644
--- a/apps/agenda/ChangeLog
+++ b/apps/agenda/ChangeLog
@@ -15,3 +15,7 @@
0.13: Show day of the week in date
0.14: Fixed "Today" and "Yesterday" wrongly displayed for allDay events on some time zones
0.15: Minor code improvements
+0.16: Correct date for all day events in negative timezones, improve locale display
+0.17: Fixed "Today" and "Tomorrow" labels displaying in non-current weeks
+0.18: Correct date in clockinfo for all-day events in negative timezones
+0.19: Change clockinfo title truncation to preserve images
diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js
index f9ea6a35d..07c48e751 100644
--- a/apps/agenda/agenda.clkinfo.js
+++ b/apps/agenda/agenda.clkinfo.js
@@ -64,8 +64,12 @@
agenda.forEach((entry, i) => {
- var title = entry.title.slice(0,12);
- var date = new Date(entry.timestamp*1000);
+ var title = g.setFont("6x8").wrapString(entry.title,100)[0];
+ // All day events are always in UTC and always start at 00:00:00, so we
+ // need to "undo" the timezone offsetting to make sure that the day is
+ // correct.
+ var offset = entry.allDay ? new Date().getTimezoneOffset() * 60 : 0
+ var date = new Date((entry.timestamp+offset)*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
var color = "#"+(0x1000000+Number(entry.color)).toString(16).padStart(6,"0");
diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js
index 4f3a91537..f8fffc643 100644
--- a/apps/agenda/agenda.js
+++ b/apps/agenda/agenda.js
@@ -30,20 +30,26 @@ var settings = require("Storage").readJSON("agenda.settings.json",true)||{};
CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
-function getDate(timestamp) {
- return new Date(timestamp*1000);
+function getDate(timestamp, allDay) {
+ // All day events are always in UTC and always start at 00:00:00, so we
+ // need to "undo" the timezone offsetting to make sure that the day is
+ // correct.
+ var offset = allDay ? new Date().getTimezoneOffset() * 60 : 0
+ return new Date((timestamp+offset)*1000);
}
+
function formatDay(date) {
- let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
+ let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/,*\s*\d\d\d\d/,"");
if (!settings.useToday) {
return formattedDate;
}
const today = new Date(Date.now());
- if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
+ if (date.getDate() == today.getDate())
return /*LANG*/"Today ";
else {
- const tomorrow = new Date(Date.now() + 86400 * 1000);
- if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
+ var tomorrow = new Date();
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ if (date.getDate() == tomorrow.getDate()) {
return /*LANG*/"Tomorrow ";
}
return formattedDate;
@@ -57,8 +63,9 @@ function formatDateLong(date, includeDay, allDay) {
}
return shortTime;
}
+
function formatDateShort(date, allDay) {
- return formatDay(date)+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
+ return formatDay(date)+(allDay?"":" "+Locale.time(date,1)+Locale.meridian(date));
}
var lines = [];
@@ -69,16 +76,19 @@ function showEvent(ev) {
//var lines = [];
if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10);
var titleCnt = lines.length;
- var start = getDate(ev.timestamp);
- var end = getDate((+ev.timestamp) + (+ev.durationInSeconds));
+ var start = getDate(ev.timestamp, ev.allDay);
+ // All day events end at the midnight boundary of the following day. Here, we
+ // subtract one second for all day events so the days display correctly.
+ const allDayEndCorrection = ev.allDay ? 1 : 0;
+ var end = getDate((+ev.timestamp) + (+ev.durationInSeconds) - allDayEndCorrection, ev.allDay);
var includeDay = true;
if (titleCnt) lines.push(""); // add blank line after title
if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth())
includeDay = false;
- if(includeDay && ev.allDay) {
- //single day all day (average to avoid getting previous day)
+ if(!includeDay && ev.allDay) {
+ //single day all day
lines = lines.concat(
- g.wrapString(formatDateLong(new Date((start+end)/2), includeDay, ev.allDay), g.getWidth()-10));
+ g.wrapString(formatDateLong(start, includeDay, ev.allDay), g.getWidth()-10));
} else if(includeDay || ev.allDay) {
lines = lines.concat(
/*LANG*/"Start"+":",
@@ -137,7 +147,7 @@ function showList() {
if (!ev) return;
var isPast = false;
var x = r.x+2, title = ev.title;
- var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
+ var body = formatDateShort(getDate(ev.timestamp, ev.allDay),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json
index 41b7ed893..bfe9531be 100644
--- a/apps/agenda/metadata.json
+++ b/apps/agenda/metadata.json
@@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
- "version": "0.15",
+ "version": "0.19",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
diff --git a/apps/agpsdata/settings.js b/apps/agpsdata/settings.js
index 64fa25330..95b06fe55 100644
--- a/apps/agpsdata/settings.js
+++ b/apps/agpsdata/settings.js
@@ -68,4 +68,4 @@ function buildMainMenu() {
}
E.showMenu(buildMainMenu());
-});
+})
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index a3a5dfc1c..d2a1dd107 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -51,3 +51,9 @@
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
0.47: Fix wrap around when snoozed through midnight
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
+0.49: fix uncaught error if no scroller (Bangle 1). Would happen when trying
+ to select an alarm in the main menu.
+0.50: Bangle.js 2: Long touch of alarm in main menu toggle it on/off. Touching the icon on
+ the right will do the same.
+0.51: Fix long-touch to enable alarm/timer not updating time (fix #3804)
+0.52: Allow deletion of alarms once they sound (like timers and events)
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
index 77aa61d2c..d7b64b3c2 100644
--- a/apps/alarm/README.md
+++ b/apps/alarm/README.md
@@ -20,6 +20,8 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- `Disable All` → Disable _all_ enabled alarms & timers
- `Delete All` → Delete _all_ alarms & timers
+On Bangle.js 2 it's possible to toggle alarms, timers and events from the main menu. This is done by clicking the indicator icons of corresponding entries. Or long pressing anywhere on them.
+
## Creator
- [Gordon Williams](https://github.com/gfwilliams)
@@ -29,6 +31,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
+- [thyttan](https://github.com/thyttan) - Toggle alarms directly from main menu.
## Attributions
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 053505187..74e6ad6b7 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -87,14 +87,24 @@ function showMainMenu(scroll, group, scrollback) {
};
const getGroups = settings.showGroup && !group;
const groups = getGroups ? {} : undefined;
- var showAlarm;
+ const getIcon = (e)=>{return e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff);};
alarms.forEach((e, index) => {
- showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
+ const showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
if(showAlarm) {
- menu[trimLabel(getLabel(e),40)] = {
- value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
- onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller.scroll, group)
+ const label = trimLabel(getLabel(e),40);
+ menu[label] = {
+ value: e.on,
+ onchange: (v, touch) => {
+ if (touch && (2==touch.type || 145getIcon(e)
};
} else if (getGroups) {
groups[e.group] = undefined;
@@ -102,7 +112,7 @@ function showMainMenu(scroll, group, scrollback) {
});
if (!group) {
- Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
+ Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller?scroller.scroll:undefined));
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
}
@@ -283,7 +293,6 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
- delete menu[/*LANG*/"Delete After Expiration"];
}
if (!isNew) {
@@ -318,6 +327,14 @@ function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
}
}
+function prepareForSave(alarm, alarmIndex) {
+ if (alarm.timer) {
+ prepareTimerForSave(alarm, alarmIndex, require("time_utils").decodeTime(alarm.timer));
+ } else {
+ prepareAlarmForSave(alarm, alarmIndex, require("time_utils").decodeTime(alarm.t));
+ }
+}
+
function saveAndReload() {
// Before saving revert the dow to the standard format (alarms only!)
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
@@ -564,13 +581,7 @@ function enableAll(on) {
if (confirm) {
alarms.forEach((alarm, i) => {
alarm.on = on;
- if (on) {
- if (alarm.timer) {
- prepareTimerForSave(alarm, i, require("time_utils").decodeTime(alarm.timer));
- } else {
- prepareAlarmForSave(alarm, i, require("time_utils").decodeTime(alarm.t));
- }
- }
+ if (on) prepareForSave(alarm, i);
});
saveAndReload();
showMainMenu();
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index 78cd4bd4e..ae87be6b2 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.48",
+ "version": "0.52",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",
diff --git a/apps/alarm/settings.js b/apps/alarm/settings.js
index 765e5a5fa..2843fbdb1 100644
--- a/apps/alarm/settings.js
+++ b/apps/alarm/settings.js
@@ -48,4 +48,4 @@
};
E.showMenu(appMenu);
-});
+})
diff --git a/apps/alpinenav/README.md b/apps/alpinenav/README.md
index d18cdfd6d..823d6c9f8 100644
--- a/apps/alpinenav/README.md
+++ b/apps/alpinenav/README.md
@@ -2,13 +2,26 @@ Alpine Navigator
================
App that performs GPS monitoring to track and display position relative to a given origin in realtime.
-
+
+
+ [compass 5]
+
+ altitude
+[start 1] [current 2]
+
+ distance
+[from start 3] [track 4]
+
+
+[btn1 -- screen lock]
+[btn2 -- remove points]
+[btn3 -- pause]
Functions
---------
-Note if you've not used GPS yet I suggest using one of the GPS apps to get your first fix and confirm as I've found that helps initially.
+Note if you've not used GPS yet, I suggest using one of the GPS apps to get your first fix and confirm, as I've found that helps initially.
-The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. all your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit but I've kept it fairly conservative here as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
+The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. All your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit, but I've kept it fairly conservative here, as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
1. altitude at origin, this is displayed left of the centre.
2. current altitude, displayed centre right
@@ -16,12 +29,12 @@ The GPS and magnetometer will be turned on and after a few moments, when the wat
4. distance travelled, bottom right (meters)
5. compass heading, at the top
-For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time because the waypoints will be reduced when it reaches a set threshold so you may see the path smooth out slightly at intervals.
+For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time, because the waypoints will be reduced when it reaches a set threshold, so you may see the path smooth out slightly at intervals.
-If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring but often just moving your wrist a bit is enough.
+If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring, but often just moving your wrist a bit is enough.
The buttons do the following:
-BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! soft and hard reset will both still work.
+BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! Soft and hard reset will both still work.
BTN2: this removes all waypoints aside from the origin and your current location; sometimes during smaller journeys and walks, the GPS can give sporadic differences in locations because of the error margins of GPS and this can add noise to your route.
BTN3: this will pause the GPS and magnetometer, useful for saving power for situations where you don't necessarily need to track parts of your route e.g. you're going indoors/shelter for some time. You'll know it's paused because the compass won't update it's reading and all the metrics will be blacked out on the screen.
diff --git a/apps/altimeter/ChangeLog b/apps/altimeter/ChangeLog
index 905152382..77f64d029 100644
--- a/apps/altimeter/ChangeLog
+++ b/apps/altimeter/ChangeLog
@@ -2,3 +2,4 @@
0.02: Actually upload correct code
0.03: Display sea-level pressure, too, and allow calibration
0.04: Switch to using system code for pressure calibration
+0.05: Prompt before resetting calibration (stops long-press of button resetting calibration)
\ No newline at end of file
diff --git a/apps/altimeter/app.js b/apps/altimeter/app.js
index 6e44161da..222ed2d21 100644
--- a/apps/altimeter/app.js
+++ b/apps/altimeter/app.js
@@ -7,6 +7,7 @@ var R = Bangle.appRect;
var y = R.y + R.h/2;
var MEDIANLENGTH = 20;
var avr = [];
+var updateDisplay = true;
function fmt(t) {
if ((t > -100) && (t < 1000))
@@ -19,48 +20,57 @@ function fmt(t) {
Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude);
- let median = avr.slice().sort();
+ if (!updateDisplay) return;
+ let median = avr.slice().sort(), value;
g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
if (median.length>10) {
var mid = median.length>>1;
- var value = E.sum(median.slice(mid-4,mid+5)) / 9;
+ value = E.sum(median.slice(mid-4,mid+5)) / 9;
} else {
- var value = median[median.length>>1];
+ value = median[median.length>>1];
}
- t = fmt(value);
+ var t = fmt(value);
g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
let o = Bangle.getOptions();
let sea = o.seaLevelPressure;
t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
- if (0) {
+ /*if (0) {
print("alt raw:", value.toFixed(1));
print("temperature:", e.temperature);
print("pressure:", e.pressure);
print("sea pressure:", sea);
- }
+ }*/
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t, 10, R.y+R.h - 35);
});
function setPressure(m, a) {
- o = Bangle.getOptions();
- print(o);
+ var o = Bangle.getOptions();
+ //print(o);
o.seaLevelPressure = o.seaLevelPressure * m + a;
Bangle.setOptions(o);
avr = [];
}
-print(g.getFonts());
-g.reset();
-g.setFont("Vector:15");
-g.setFontAlign(0,0);
-g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
-g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
-g.flip();
-g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2);
-Bangle.setUI("updown", btn=> {
- if (!btn) setPressure(0, 1013.25);
- if (btn<0) setPressure(1, 1);
- if (btn>0) setPressure(1, -1);
-});
+function start() {
+ g.reset();
+ g.setFont("Vector:15");
+ g.setFontAlign(0,0);
+ g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
+ g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
+ g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2);
+ updateDisplay = true;
+ Bangle.setUI("updown", btn => {
+ if (!btn) {
+ updateDisplay = false;
+ E.showPrompt(/*LANG*/"Set calibration to default?",{title:/*LANG*/"Altitude"}).then(function(reset) {
+ start();
+ if (reset) setPressure(0, 1013.25);
+ });
+ }
+ if (btn<0) setPressure(1, 1);
+ if (btn>0) setPressure(1, -1);
+ });
+}
+start();
\ No newline at end of file
diff --git a/apps/altimeter/metadata.json b/apps/altimeter/metadata.json
index ff5eb9935..2c8bf06ec 100644
--- a/apps/altimeter/metadata.json
+++ b/apps/altimeter/metadata.json
@@ -1,6 +1,6 @@
{ "id": "altimeter",
"name": "Altimeter",
- "version":"0.04",
+ "version":"0.05",
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
"icon": "app.png",
"tags": "tool,outdoors",
diff --git a/apps/andark/ChangeLog b/apps/andark/ChangeLog
index 7d7f9567b..5541288e8 100644
--- a/apps/andark/ChangeLog
+++ b/apps/andark/ChangeLog
@@ -5,3 +5,7 @@
0.05: Fix support for dark theme + support widgets +
add settings for widgets, order of drawing and hour hand length
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled
+0.07: Enable fast loading and queue updates to the second
+0.08: Restore redraw on charging event + fixup for safer fast-loading
+0.09: Add setting to show the weekday and not the year + add setting to hide the battery +
+ changed to follow system them with setting for dark theme
diff --git a/apps/andark/README.md b/apps/andark/README.md
index 9034677c2..04737a7a2 100644
--- a/apps/andark/README.md
+++ b/apps/andark/README.md
@@ -7,10 +7,19 @@
* battery percentage (showing charge status with color)
* turned off or swipeable widgets (choose in settings)
-
+
+
+*Default settings*
+
+
+
+*Following system theme, with weekday shown and battery hidden*
## Settings
* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available
* date and battery can be printed both below hands (as if hands were physical) and above (more readable)
* hour hand can be made slighly shorter to improve readability when minute hand is behind a number
+* show the weekday and not the year
+* hide the battery percentage; the font for the date is increased since there is more space
+* dark theme (enabled by default); disable to follow system theme
diff --git a/apps/andark/andark_screen_light_weekday_nobatt.png b/apps/andark/andark_screen_light_weekday_nobatt.png
new file mode 100644
index 000000000..72fa233b7
Binary files /dev/null and b/apps/andark/andark_screen_light_weekday_nobatt.png differ
diff --git a/apps/andark/app.js b/apps/andark/app.js
index e6b5204f0..12c836182 100644
--- a/apps/andark/app.js
+++ b/apps/andark/app.js
@@ -1,19 +1,29 @@
+{
const defaultSettings = {
loadWidgets : false,
textAboveHands : false,
- shortHrHand : false
+ shortHrHand : false,
+ weekdayNoYear : false,
+ noBattery : false,
+ darkTheme : true
};
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
+const origTheme = g.theme;
+if (settings.darkTheme) {
+ g.setTheme({bg: "#000"});
+ g.setTheme({fg: "#FFF"});
+}
+
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
const zahlpos=(function() {
let z=[];
let sk=1;
for(let i=-10;i<50;i+=5){
- let win=i*2*Math.PI/60;
- let xsk =c.x+2+Math.cos(win)*(c.x-10),
- ysk =c.y+2+Math.sin(win)*(c.x-10);
+ let win=i*2*Math.PI/60;
+ let xsk =c.x+2+Math.cos(win)*(c.x-10),
+ ysk =c.y+2+Math.sin(win)*(c.x-10);
if(sk==3){xsk-=10;}
if(sk==6){ysk-=10;}
if(sk==9){xsk+=10;}
@@ -25,20 +35,17 @@ const zahlpos=(function() {
return z;
})();
-let unlock = false;
-
-function zeiger(len,dia,tim){
+const zeiger = function(len,dia,tim) {
const x=c.x+ Math.cos(tim)*len/2,
y=c.y + Math.sin(tim)*len/2,
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
return pol;
+};
-}
-
-function drawHands(d) {
+const drawHands = function(d) {
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
- g.setColor(1,1,1);
+ g.setColor(g.theme.fg);
if(h>12){
h=h-12;
@@ -61,34 +68,70 @@ function drawHands(d) {
g.fillPoly(sekz,true);
}
g.fillCircle(c.x,c.y,4);
-}
+};
-function drawText(d) {
- g.setFont("Vector",10);
- g.setBgColor(0,0,0);
- g.setColor(1,1,1);
- let dateStr = require("locale").date(d);
- g.drawString(dateStr, c.x, c.y+20, true);
- let batStr = Math.round(E.getBattery()/5)*5+"%";
- if (Bangle.isCharging()) {
- g.setBgColor(1,0,0);
+const drawText = function(d) {
+//g.setFont("Vector",10);
+ g.setBgColor(g.theme.bg);
+ g.setColor(g.theme.fg);
+ const dateStr = settings.weekdayNoYear
+ ? require("locale").dow(d, 1)+" "+d.getDate()+" "+require("locale").month(d, 1)
+ : require("locale").date(d);
+ const batStr = Math.round(E.getBattery()/5)*5+"%";
+ if (settings.noBattery) {
+ g.setFont("Vector",13);
+ g.drawString(dateStr, c.x, c.y+25, true);
+ } else {
+ g.setFont("Vector",10);
+ g.drawString(dateStr, c.x, c.y+20, true);
+ if (Bangle.isCharging()) {
+ g.setBgColor(1,0,0);
+ }
+ g.drawString(batStr, c.x, c.y+40, true);
}
- g.drawString(batStr, c.x, c.y+40, true);
-}
+};
-function drawNumbers() {
+const drawNumbers = function() {
//draws the numbers on the screen
g.setFont("Vector",20);
- g.setColor(1,1,1);
- g.setBgColor(0,0,0);
+ g.setColor(g.theme.fg);
+ g.setBgColor(g.theme.bg);
for(let i = 0;i<12;i++){
- g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
+ g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
}
-}
+};
-function draw(){
+let drawTimeout;
+let queueMillis = 1000;
+let unlock = true;
+
+const updateState = function() {
+ if (Bangle.isLCDOn()) {
+ if (!Bangle.isLocked()) {
+ queueMillis = 1000;
+ unlock = true;
+ } else {
+ queueMillis = 60000;
+ unlock = false;
+ }
+ draw();
+ } else {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+};
+
+const queueDraw = function() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, queueMillis - (Date.now() % queueMillis));
+};
+
+const draw = function() {
// draw black rectangle in the middle to clear screen from scale and hands
- g.setColor(0,0,0);
+ g.setColor(g.theme.bg);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
// prepare for drawing the text
g.setFontAlign(0,0);
@@ -100,12 +143,13 @@ function draw(){
} else {
drawText(d); drawHands(d);
}
-}
+ queueDraw();
+};
//draws the scale once the app is startet
-function drawScale(){
+const drawScale = function() {
// clear the screen
- g.setBgColor(0,0,0);
+ g.setBgColor(g.theme.bg);
g.clear();
// draw the ticks of the scale
for(let i=-14;i<47;i++){
@@ -113,40 +157,40 @@ function drawScale(){
let d=2;
if(i%5==0){d=5;}
g.fillPoly(zeiger(300,d,win),true);
- g.setColor(0,0,0);
+ g.setColor(g.theme.bg);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
- g.setColor(1,1,1);
+ g.setColor(g.theme.fg);
}
-}
+};
//// main running sequence ////
// Show launcher when middle button pressed, and widgets that we're clock
-Bangle.setUI("clock");
+Bangle.setUI({
+ mode: "clock",
+ remove: function() {
+ if (settings.darkTheme) g.setTheme(origTheme);
+ Bangle.removeListener('lcdPower', updateState);
+ Bangle.removeListener('lock', updateState);
+ Bangle.removeListener('charging', draw);
+ // We clear drawTimout after removing all listeners, because they can add one again
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ require("widget_utils").show();
+ }
+});
// Load widgets if needed, and make them show swipeable
if (settings.loadWidgets) {
Bangle.loadWidgets();
require("widget_utils").swipeOn();
} else if (global.WIDGETS) require("widget_utils").hide();
-// Clear the screen once, at startup
-drawScale();
-draw();
-
-let secondInterval = setInterval(draw, 1000);
// Stop updates when LCD is off, restart when on
-Bangle.on('lcdPower',on=>{
- if (secondInterval) clearInterval(secondInterval);
- secondInterval = undefined;
- if (on) {
- secondInterval = setInterval(draw, 1000);
- draw(); // draw immediately
- }
-});
-Bangle.on('lock',on=>{
- unlock = !on;
- if (secondInterval) clearInterval(secondInterval);
- secondInterval = setInterval(draw, unlock ? 1000 : 60000);
- draw(); // draw immediately
-});
-Bangle.on('charging',on=>{draw();});
+Bangle.on('lcdPower', updateState);
+Bangle.on('lock', updateState);
+Bangle.on('charging', draw); // Immediately redraw when charger (dis)connected
+
+updateState();
+drawScale();
+draw();
+}
diff --git a/apps/andark/metadata.json b/apps/andark/metadata.json
index fc8872f4b..66b22b2aa 100644
--- a/apps/andark/metadata.json
+++ b/apps/andark/metadata.json
@@ -1,13 +1,14 @@
{ "id": "andark",
"name": "Analog Dark",
"shortName":"AnDark",
- "version":"0.06",
+ "version":"0.09",
"description": "analog clock face without disturbing widgets",
"icon": "andark_icon.png",
"type": "clock",
"tags": "clock",
+ "allow_emulator": true,
"supports" : ["BANGLEJS2"],
- "screenshots": [{"url":"andark_screen.png"}],
+ "screenshots": [{"url":"andark_screen.png"},{"url":"andark_screen_light_weekday_nobatt.png"}],
"readme": "README.md",
"storage": [
{"name":"andark.app.js","url":"app.js"},
diff --git a/apps/andark/settings.js b/apps/andark/settings.js
index 708913705..d1cba0e30 100644
--- a/apps/andark/settings.js
+++ b/apps/andark/settings.js
@@ -2,7 +2,10 @@
const defaultSettings = {
loadWidgets : false,
textAboveHands : false,
- shortHrHand : false
+ shortHrHand : false,
+ weekdayNoYear : false,
+ noBattery : false,
+ darkTheme : true
}
let settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
@@ -22,7 +25,19 @@
value : !!settings.shortHrHand,
onchange : v => { settings.shortHrHand=v; save();}
},
+ /*LANG*/'Show weekday not year': {
+ value : !!settings.weekdayNoYear,
+ onchange : v => { settings.weekdayNoYear=v; save();}
+ },
+ /*LANG*/'Hide the battery': {
+ value : !!settings.noBattery,
+ onchange : v => { settings.noBattery=v; save();}
+ },
+ /*LANG*/'Dark theme': {
+ value : !!settings.darkTheme,
+ onchange : v => { settings.darkTheme=v; save();}
+ },
};
E.showMenu(appMenu);
-});
+})
diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog
index 11c78588a..84d917a8c 100644
--- a/apps/android/ChangeLog
+++ b/apps/android/ChangeLog
@@ -36,4 +36,14 @@
0.34: Implement API for activity tracks fetching (Recorder app logs).
0.35: Implement API to enable/disable acceleration data tracking.
0.36: Move from wrapper function to {} and let - faster execution at boot
- Allow `calendar-` to take an array of items to remove
\ No newline at end of file
+ Allow `calendar-` to take an array of items to remove
+0.37: Support Gadgetbridge canned responses
+0.38: Don't rewrite settings file on every boot!
+0.39: Move GB message handling into a library to reduce boot time from 40ms->13ms
+0.40: Ensure we send health 'activity' message to gadgetbridge (added 2v26)
+0.41: When using `actfetch`, fetch historical activity type too
+0.42: Add handling for android STREAM_MUSIC volume level info, emitting on
+ arrival. (Needs Gadgetbridge nightly (either flavour) for now, or stable
+ version 85 when it's out)
+0.43: Ensure listRecs doesn't list old-style recorded tracks (Otherwise Gadgetbridge fails parsing the filename)
+0.44: Pass HTTP request timeout to Gadgetbridge
diff --git a/apps/android/README.md b/apps/android/README.md
index f322e6a4e..a7a539e38 100644
--- a/apps/android/README.md
+++ b/apps/android/README.md
@@ -49,11 +49,21 @@ The boot code also provides some useful functions:
* `body` the body of the HTTP request
* `headers` an object of headers, eg `{HeaderOne : "headercontents"}`
+`Bangle.http` returns a promise which contains:
+
+```JS
+{
+ t:"http",
+ id: // the ID of this HTTP request
+ resp: "...." // a string containing the response
+}
+```
+
eg:
-```
+```JS
Bangle.http("https://pur3.co.uk/hello.txt").then(data=>{
- console.log("Got ",data);
+ console.log("Got ",data.resp);
});
```
diff --git a/apps/android/boot.js b/apps/android/boot.js
index 729ed2b47..37550cdd0 100644
--- a/apps/android/boot.js
+++ b/apps/android/boot.js
@@ -1,350 +1,24 @@
/* global GB */
{
- let gbSend = function(message) {
- Bluetooth.println("");
- Bluetooth.println(JSON.stringify(message));
- }
- let lastMsg; // for music messages - may not be needed now...
- let actInterval; // Realtime activity reporting interval when `act` is true
- let actHRMHandler; // For Realtime activity reporting
- let gpsState = {}; // keep information on GPS via Gadgetbridge
-
- // this settings var is deleted after this executes to save memory
- let settings = require("Storage").readJSON("android.settings.json",1)||{};
- //default alarm settings
- if (settings.rp == undefined) settings.rp = true;
- if (settings.as == undefined) settings.as = true;
- if (settings.vibrate == undefined) settings.vibrate = "..";
- require('Storage').writeJSON("android.settings.json", settings);
+ // settings var is deleted after this executes to save memory
+ let settings = Object.assign({rp:true,as:true,vibrate:".."},
+ require("Storage").readJSON("android.settings.json",1)||{}
+ );
let _GB = global.GB;
- let fetchRecInterval;
- global.GB = (event) => {
+ global.GB = e => {
// feed a copy to other handlers if there were any
- if (_GB) setTimeout(_GB,0,Object.assign({},event));
-
-
- /* TODO: Call handling, fitness */
- var HANDLERS = {
- // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
- "notify" : function() {
- Object.assign(event,{t:"add",positive:true, negative:true});
- // Detect a weird GadgetBridge bug and fix it
- // For some reason SMS messages send two GB notifications, with different sets of info
- if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
- // Mutate the other message
- event.id = lastMsg.id;
- }
- lastMsg = event;
- require("messages").pushMessage(event);
- },
- // {t:"notify~",id:int, title:string} // modified
- "notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
- // {t:"notify-",id:int} // remove
- "notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
- // {t:"find", n:bool} // find my phone
- "find" : function() {
- if (Bangle.findDeviceInterval) {
- clearInterval(Bangle.findDeviceInterval);
- delete Bangle.findDeviceInterval;
- }
- if (event.n) // Ignore quiet mode: we always want to find our watch
- Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
- },
- // {t:"musicstate", state:"play/pause",position,shuffle,repeat}
- "musicstate" : function() {
- require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
- },
- // {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
- "musicinfo" : function() {
- require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
- },
- // {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
- "call" : function() {
- Object.assign(event, {
- t:event.cmd=="incoming"?"add":"remove",
- id:"call", src:"Phone",
- positive:true, negative:true,
- title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
- require("messages").pushMessage(event);
- },
- // {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
- "alarm" : function() {
- //wipe existing GB alarms
- var sched;
- try { sched = require("sched"); } catch (e) {}
- if (!sched) return; // alarms may not be installed
- var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
- for (var i = 0; i < gbalarms.length; i++)
- sched.setAlarm(gbalarms[i].id, undefined);
- var alarms = sched.getAlarms();
- var time = new Date();
- var currentTime = time.getHours() * 3600000 +
- time.getMinutes() * 60000 +
- time.getSeconds() * 1000;
- for (var j = 0; j < event.d.length; j++) {
- // prevents all alarms from going off at once??
- var dow = event.d[j].rep;
- var rp = false;
- if (!dow) {
- dow = 127; //if no DOW selected, set alarm to all DOW
- } else {
- rp = true;
- }
- var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
- var a = require("sched").newDefaultAlarm();
- a.id = "gb"+j;
- a.appid = "gbalarms";
- a.on = event.d[j].on !== undefined ? event.d[j].on : true;
- a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
- a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
- a.rp = rp;
- a.last = last;
- alarms.push(a);
- }
- sched.setAlarms(alarms);
- sched.reload();
- },
- //TODO perhaps move those in a library (like messages), used also for viewing events?
- //add and remove events based on activity on phone (pebble-like)
- // {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
- "calendar" : function() {
- var cal = require("Storage").readJSON("android.calendar.json",true);
- if (!cal || !Array.isArray(cal)) cal = [];
- var i = cal.findIndex(e=>e.id==event.id);
- if(i<0)
- cal.push(event);
- else
- cal[i] = event;
- require("Storage").writeJSON("android.calendar.json", cal);
- },
- // {t:"calendar-", id:int}
- "calendar-" : function() {
- var cal = require("Storage").readJSON("android.calendar.json",true);
- //if any of those happen we are out of sync!
- if (!cal || !Array.isArray(cal)) cal = [];
- if (Array.isArray(event.id))
- cal = cal.filter(e=>!event.id.includes(e.id));
- else
- cal = cal.filter(e=>e.id!=event.id);
- require("Storage").writeJSON("android.calendar.json", cal);
- },
- //triggered by GB, send all ids
- // { t:"force_calendar_sync_start" }
- "force_calendar_sync_start" : function() {
- var cal = require("Storage").readJSON("android.calendar.json",true);
- if (!cal || !Array.isArray(cal)) cal = [];
- gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
- },
- // {t:"http",resp:"......",[id:"..."]}
- "http":function() {
- //get the promise and call the promise resolve
- if (Bangle.httpRequest === undefined) return;
- var request=Bangle.httpRequest[event.id];
- if (request === undefined) return; //already timedout or wrong id
- delete Bangle.httpRequest[event.id];
- clearTimeout(request.t); //t = timeout variable
- if(event.err!==undefined) //if is error
- request.j(event.err); //r = reJect function
- else
- request.r(event); //r = resolve function
- },
- // {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
- "gps": function() {
- if (!settings.overwriteGps) return;
- // modify event for using it as Bangle GPS event
- delete event.t;
- if (!isFinite(event.satellites)) event.satellites = NaN;
- if (!isFinite(event.course)) event.course = NaN;
- event.fix = 1;
- if (event.long!==undefined) { // for earlier Gadgetbridge implementations
- event.lon = event.long;
- delete event.long;
- }
- if (event.time){
- event.time = new Date(event.time);
- }
-
- if (!gpsState.lastGPSEvent) {
- // this is the first event, save time of arrival and deactivate internal GPS
- Bangle.moveGPSPower(0);
- } else {
- // this is the second event, store the intervall for expecting the next GPS event
- gpsState.interval = Date.now() - gpsState.lastGPSEvent;
- }
- gpsState.lastGPSEvent = Date.now();
- // in any case, cleanup the GPS state in case no new events arrive
- if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS);
- gpsState.timeoutGPS = setTimeout(()=>{
- // reset state
- gpsState.lastGPSEvent = undefined;
- gpsState.timeoutGPS = undefined;
- gpsState.interval = undefined;
- // did not get an expected GPS event but have GPS clients, switch back to internal GPS
- if (Bangle.isGPSOn()) Bangle.moveGPSPower(1);
- }, (gpsState.interval || 10000) + 1000);
- Bangle.emit('GPS', event);
- },
- // {t:"is_gps_active"}
- "is_gps_active": function() {
- gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
- },
- // {t:"act", hrm:bool, stp:bool, int:int}
- "act": function() {
- if (actInterval) clearInterval(actInterval);
- actInterval = undefined;
- if (actHRMHandler)
- actHRMHandler = undefined;
- Bangle.setHRMPower(event.hrm,"androidact");
- if (!(event.hrm || event.stp)) return;
- if (!isFinite(event.int)) event.int=1;
- var lastSteps = Bangle.getStepCount();
- var lastBPM = 0;
- actHRMHandler = function(e) {
- lastBPM = e.bpm;
- };
- Bangle.on('HRM',actHRMHandler);
- actInterval = setInterval(function() {
- var steps = Bangle.getStepCount();
- gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
- lastSteps = steps;
- }, event.int*1000);
- },
- // {t:"actfetch", ts:long}
- "actfetch": function() {
- gbSend({t: "actfetch", state: "start"});
- var actCount = 0;
- var actCb = function(r) {
- // The health lib saves the samples at the start of the 10-minute block
- // However, GB expects them at the end of the block, so let's offset them
- // here to keep a consistent API in the health lib
- var sampleTs = r.date.getTime() + 600000;
- if (sampleTs >= event.ts) {
- gbSend({
- t: "act",
- ts: sampleTs,
- stp: r.steps,
- hrm: r.bpm,
- mov: r.movement
- });
- actCount++;
- }
- }
- if (event.ts != 0) {
- require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
- } else {
- require("health").readFullDatabase(actCb);
- }
- gbSend({t: "actfetch", state: "end", count: actCount});
- },
- //{t:"listRecs", id:"20230616a"}
- "listRecs": function() {
- let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21));
- if (event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
- let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
- if (-1 == firstNonsyncedIdx) {
- recs = []
- } else {
- recs = recs.slice(firstNonsyncedIdx);
- }
- }
- gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
- },
- //{t:"fetchRec", id:"20230616a"}
- "fetchRec": function() {
- // TODO: Decide on what names keys should have.
- if (fetchRecInterval) {
- clearInterval(fetchRecInterval);
- fetchRecInterval = undefined;
- }
- if (event.id=="stop") {
- return
- } else {
- let log = require("Storage").open("recorder.log"+event.id+".csv","r");
- let lines = "init";// = log.readLine();
- let pkgcnt = 0;
- gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
- let sendlines = ()=>{
- lines = log.readLine();
- for (var i = 0; i < 3; i++) {
- let line = log.readLine();
- if (line) lines += line;
- }
- pkgcnt++;
- gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
- if (!lines && fetchRecInterval) {
- clearInterval(fetchRecInterval);
- fetchRecInterval = undefined;
- }
- }
- fetchRecInterval = setInterval(sendlines, 50)
- }
- },
- "nav": function() {
- event.id="nav";
- if (event.instr) {
- event.t="add";
- event.src="maps"; // for the icon
- event.title="Navigation";
- if (require("messages").getMessages().find(m=>m.id=="nav"))
- event.t = "modify";
- } else {
- event.t="remove";
- }
- require("messages").pushMessage(event);
- },
- "cards" : function() {
- // we receive all, just override what we have
- if (Array.isArray(event.d))
- require("Storage").writeJSON("android.cards.json", event.d);
- },
- "accelsender": function () {
- require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
- load();
- }
- };
- var h = HANDLERS[event.t];
- if (h) h(); else console.log("GB Unknown",event);
+ if (_GB) setTimeout(_GB,0,Object.assign({},e));
+ Bangle.emit("GB",e);
+ require("android").gbHandler(e);
};
// HTTP request handling - see the readme
- // options = {id,timeout,xpath}
- Bangle.http = (url,options)=>{
- options = options||{};
- if (!NRF.getSecurityStatus().connected)
- return Promise.reject(/*LANG*/"Not connected to Bluetooth");
- if (Bangle.httpRequest === undefined)
- Bangle.httpRequest={};
- if (options.id === undefined) {
- // try and create a unique ID
- do {
- options.id = Math.random().toString().substr(2);
- } while( Bangle.httpRequest[options.id]!==undefined);
- }
- //send the request
- var req = {t: "http", url:url, id:options.id};
- if (options.xpath) req.xpath = options.xpath;
- if (options.return) req.return = options.return; // for xpath
- if (options.method) req.method = options.method;
- if (options.body) req.body = options.body;
- if (options.headers) req.headers = options.headers;
- gbSend(req);
- //create the promise
- var promise = new Promise(function(resolve,reject) {
- //save the resolve function in the dictionary and create a timeout (30 seconds default)
- Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
- //if after "timeoutMillisec" it still hasn't answered -> reject
- delete Bangle.httpRequest[options.id];
- reject("Timeout");
- },options.timeout||30000)};
- });
- return promise;
- };
-
+ Bangle.http = (url,options)=>require("android").httpHandler(url,options);
// Battery monitor
- let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
+ let sendBattery = function() { require("android").gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
Bangle.on("charging", sendBattery);
NRF.on("connect", () => setTimeout(function() {
sendBattery();
- gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
+ require("android").gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
}, 2000));
NRF.on("disconnect", () => {
@@ -358,81 +32,24 @@
setInterval(sendBattery, 10*60*1000);
// Health tracking - if 'realtime' data is sent with 'rt:1', but let's still send our activity log every 10 mins
Bangle.on('health', h=>{
- gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement });
+ require("android").gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement, act: h.activity }); // h.activity added in 2v26
});
// Music control
Bangle.musicControl = cmd => {
// play/pause/next/previous/volumeup/volumedown
- gbSend({ t: "music", n:cmd });
+ require("android").gbSend({ t: "music", n:cmd });
};
// Message response
Bangle.messageResponse = (msg,response) => {
- if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
- if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
+ if (msg.id=="call") return require("android").gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
+ if (isFinite(msg.id)) return require("android").gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
// error/warn here?
};
Bangle.messageIgnore = msg => {
- if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id });
+ if (isFinite(msg.id)) return require("android").gbSend({ t: "notify", n:"MUTE", id: msg.id });
};
// GPS overwrite logic
- if (settings.overwriteGps) { // if the overwrite option is set..
- const origSetGPSPower = Bangle.setGPSPower;
- Bangle.moveGPSPower = (state) => {
- if (Bangle.isGPSOn()){
- let orig = Bangle._PWR.GPS;
- delete Bangle._PWR.GPS;
- origSetGPSPower(state);
- Bangle._PWR.GPS = orig;
- }
- };
-
- // work around Serial1 for GPS not working when connected to something
- let serialTimeout;
- let wrap = function(f){
- return (s)=>{
- if (serialTimeout) clearTimeout(serialTimeout);
- origSetGPSPower(1, "androidgpsserial");
- f(s);
- serialTimeout = setTimeout(()=>{
- serialTimeout = undefined;
- origSetGPSPower(0, "androidgpsserial");
- }, 10000);
- };
- };
- Serial1.println = wrap(Serial1.println);
- Serial1.write = wrap(Serial1.write);
-
- // replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
- Bangle.setGPSPower = ((isOn, appID) => {
- let pwr;
- if (!this.lastGPSEvent){
- // use internal GPS power function if no gps event has arrived from GadgetBridge
- pwr = origSetGPSPower(isOn, appID);
- } else {
- // we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
- if (!Bangle._PWR) Bangle._PWR={};
- if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
- if (!appID) appID="?";
- if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
- if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
- pwr = Bangle._PWR.GPS.length>0;
- // stop internal GPS, no clients left
- if (!pwr) origSetGPSPower(0);
- }
- // always update Gadgetbridge on current power state
- gbSend({ t: "gps_power", status: pwr });
- return pwr;
- }).bind(gpsState);
- // allow checking for GPS via GadgetBridge
- Bangle.isGPSOn = () => {
- return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
- };
- // stop GPS on boot if not activated
- setTimeout(()=>{
- if (!Bangle.isGPSOn()) gbSend({ t: "gps_power", status: false });
- },3000);
- }
-
+ if (settings.overwriteGps) require("android").overwriteGPS();
// remove settings object so it's not taking up RAM
delete settings;
}
diff --git a/apps/android/lib.js b/apps/android/lib.js
new file mode 100644
index 000000000..157c00544
--- /dev/null
+++ b/apps/android/lib.js
@@ -0,0 +1,394 @@
+exports.gbSend = function(message) {
+ Bluetooth.println("");
+ Bluetooth.println(JSON.stringify(message));
+}
+let lastMsg, // for music messages - may not be needed now...
+ gpsState = {}, // keep information on GPS via Gadgetbridge
+ settings = Object.assign({rp:true,as:true,vibrate:".."},
+ require("Storage").readJSON("android.settings.json",1)||{}
+ );
+
+exports.gbHandler = (event) => {
+ var HANDLERS = {
+ // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
+ "notify" : function() {
+ print("notify",event);
+ Object.assign(event,{t:"add",positive:true, negative:true});
+ // Detect a weird GadgetBridge bug and fix it
+ // For some reason SMS messages send two GB notifications, with different sets of info
+ if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
+ // Mutate the other message
+ event.id = lastMsg.id;
+ }
+ lastMsg = event;
+ require("messages").pushMessage(event);
+ },
+ // {t:"notify~",id:int, title:string} // modified
+ "notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
+ // {t:"notify-",id:int} // remove
+ "notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
+ // {t:"find", n:bool} // find my phone
+ "find" : function() {
+ if (Bangle.findDeviceInterval) {
+ clearInterval(Bangle.findDeviceInterval);
+ delete Bangle.findDeviceInterval;
+ }
+ if (event.n) // Ignore quiet mode: we always want to find our watch
+ Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
+ },
+ // {t:"musicstate", state:"play/pause",position,shuffle,repeat}
+ "musicstate" : function() {
+ require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
+ },
+ // {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
+ "musicinfo" : function() {
+ require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
+ },
+ // {t:"audio", v:(percentage of max volume for android STREAM_MUSIC)}
+ "audio" : function() {
+ Bangle.emit("musicVolume", event.v);
+ },
+ // {"t":"call","cmd":"incoming/end/start/outgoing","name":"Bob","number":"12421312"})
+ "call" : function() {
+ Object.assign(event, {
+ t:event.cmd=="incoming"?"add":"remove",
+ id:"call", src:"Phone",
+ positive:true, negative:true,
+ title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
+ require("messages").pushMessage(event);
+ },
+ "canned_responses_sync" : function() {
+ require("Storage").writeJSON("replies.json", event.d);
+ },
+ // {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
+ "alarm" : function() {
+ //wipe existing GB alarms
+ var sched;
+ try { sched = require("sched"); } catch (e) {}
+ if (!sched) return; // alarms may not be installed
+ var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
+ for (var i = 0; i < gbalarms.length; i++)
+ sched.setAlarm(gbalarms[i].id, undefined);
+ var alarms = sched.getAlarms();
+ var time = new Date();
+ var currentTime = time.getHours() * 3600000 +
+ time.getMinutes() * 60000 +
+ time.getSeconds() * 1000;
+ for (var j = 0; j < event.d.length; j++) {
+ // prevents all alarms from going off at once??
+ var dow = event.d[j].rep;
+ var rp = false;
+ if (!dow) {
+ dow = 127; //if no DOW selected, set alarm to all DOW
+ } else {
+ rp = true;
+ }
+ var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
+ var a = require("sched").newDefaultAlarm();
+ a.id = "gb"+j;
+ a.appid = "gbalarms";
+ a.on = event.d[j].on !== undefined ? event.d[j].on : true;
+ a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
+ a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
+ a.rp = rp;
+ a.last = last;
+ alarms.push(a);
+ }
+ sched.setAlarms(alarms);
+ sched.reload();
+ },
+ //TODO perhaps move those in a library (like messages), used also for viewing events?
+ //add and remove events based on activity on phone (pebble-like)
+ // {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
+ "calendar" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ if (!cal || !Array.isArray(cal)) cal = [];
+ var i = cal.findIndex(e=>e.id==event.id);
+ if(i<0)
+ cal.push(event);
+ else
+ cal[i] = event;
+ require("Storage").writeJSON("android.calendar.json", cal);
+ },
+ // {t:"calendar-", id:int}
+ "calendar-" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ //if any of those happen we are out of sync!
+ if (!cal || !Array.isArray(cal)) cal = [];
+ if (Array.isArray(event.id))
+ cal = cal.filter(e=>!event.id.includes(e.id));
+ else
+ cal = cal.filter(e=>e.id!=event.id);
+ require("Storage").writeJSON("android.calendar.json", cal);
+ },
+ //triggered by GB, send all ids
+ // { t:"force_calendar_sync_start" }
+ "force_calendar_sync_start" : function() {
+ var cal = require("Storage").readJSON("android.calendar.json",true);
+ if (!cal || !Array.isArray(cal)) cal = [];
+ exports.gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
+ },
+ // {t:"http",resp:"......",[id:"..."]}
+ "http":function() {
+ //get the promise and call the promise resolve
+ if (Bangle.httpRequest === undefined) return;
+ var request=Bangle.httpRequest[event.id];
+ if (request === undefined) return; //already timedout or wrong id
+ delete Bangle.httpRequest[event.id];
+ clearTimeout(request.t); //t = timeout variable
+ if(event.err!==undefined) //if is error
+ request.j(event.err); //r = reJect function
+ else
+ request.r(event); //r = resolve function
+ },
+ // {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
+ "gps": function() {
+ if (!settings.overwriteGps) return;
+ // modify event for using it as Bangle GPS event
+ delete event.t;
+ if (!isFinite(event.satellites)) event.satellites = NaN;
+ if (!isFinite(event.course)) event.course = NaN;
+ event.fix = 1;
+ if (event.long!==undefined) { // for earlier Gadgetbridge implementations
+ event.lon = event.long;
+ delete event.long;
+ }
+ if (event.time){
+ event.time = new Date(event.time);
+ }
+
+ if (!gpsState.lastGPSEvent) {
+ // this is the first event, save time of arrival and deactivate internal GPS
+ Bangle.moveGPSPower(0);
+ } else {
+ // this is the second event, store the intervall for expecting the next GPS event
+ gpsState.interval = Date.now() - gpsState.lastGPSEvent;
+ }
+ gpsState.lastGPSEvent = Date.now();
+ // in any case, cleanup the GPS state in case no new events arrive
+ if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS);
+ gpsState.timeoutGPS = setTimeout(()=>{
+ // reset state
+ gpsState.lastGPSEvent = undefined;
+ gpsState.timeoutGPS = undefined;
+ gpsState.interval = undefined;
+ // did not get an expected GPS event but have GPS clients, switch back to internal GPS
+ if (Bangle.isGPSOn()) Bangle.moveGPSPower(1);
+ }, (gpsState.interval || 10000) + 1000);
+ Bangle.emit('GPS', event);
+ },
+ // {t:"is_gps_active"}
+ "is_gps_active": function() {
+ exports.gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
+ },
+ // {t:"act", hrm:bool, stp:bool, int:int}
+ "act": function() {
+ if (exports.actInterval) clearInterval(exports.actInterval);
+ exports.actInterval = undefined;
+ if (exports.actHRMHandler)
+ exports.actHRMHandler = undefined;
+ Bangle.setHRMPower(event.hrm,"androidact");
+ if (!(event.hrm || event.stp)) return;
+ if (!isFinite(event.int)) event.int=1;
+ var lastSteps = Bangle.getStepCount();
+ var lastBPM = 0;
+ exports.actHRMHandler = function(e) {
+ lastBPM = e.bpm;
+ };
+ Bangle.on('HRM',exports.actHRMHandler);
+ exports.actInterval = setInterval(function() {
+ var steps = Bangle.getStepCount();
+ exports.gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
+ lastSteps = steps;
+ }, event.int*1000);
+ },
+ // {t:"actfetch", ts:long}
+ "actfetch": function() {
+ exports.gbSend({t: "actfetch", state: "start"});
+ var actCount = 0;
+ var actCb = function(r) {
+ // The health lib saves the samples at the start of the 10-minute block
+ // However, GB expects them at the end of the block, so let's offset them
+ // here to keep a consistent API in the health lib
+ var sampleTs = r.date.getTime() + 600000;
+ if (sampleTs >= event.ts) {
+ exports.gbSend({
+ t: "act",
+ ts: sampleTs,
+ stp: r.steps,
+ hrm: r.bpm,
+ mov: r.movement,
+ act: r.activity
+ });
+ actCount++;
+ }
+ }
+ if (event.ts != 0) {
+ require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
+ } else {
+ require("health").readFullDatabase(actCb);
+ }
+ exports.gbSend({t: "actfetch", state: "end", count: actCount});
+ },
+ //{t:"listRecs", id:"20230616a"}
+ "listRecs": function() {
+ let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21)).filter(s => s.length>7 /*ignore 'old' tracks without date*/);
+ if (event.id && event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
+ let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
+ if (-1 == firstNonsyncedIdx) {
+ recs = []
+ } else {
+ recs = recs.slice(firstNonsyncedIdx);
+ }
+ }
+ exports.gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
+ },
+ //{t:"fetchRec", id:"20230616a"}
+ "fetchRec": function() {
+ // TODO: Decide on what names keys should have.
+ if (exports.fetchRecInterval) {
+ clearInterval(exports.fetchRecInterval);
+ exports.fetchRecInterval = undefined;
+ }
+ if (event.id=="stop") {
+ return;
+ } else {
+ let log = require("Storage").open("recorder.log"+event.id+".csv","r");
+ let lines = "init";// = log.readLine();
+ let pkgcnt = 0;
+ exports.gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
+ let sendlines = ()=>{
+ lines = log.readLine();
+ for (var i = 0; i < 3; i++) {
+ let line = log.readLine();
+ if (line) lines += line;
+ }
+ pkgcnt++;
+ exports.gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
+ if (!lines && exports.fetchRecInterval) {
+ clearInterval(exports.fetchRecInterval);
+ exports.fetchRecInterval = undefined;
+ }
+ };
+ exports.fetchRecInterval = setInterval(sendlines, 50);
+ }
+ },
+ "nav": function() {
+ event.id="nav";
+ if (event.instr) {
+ event.t="add";
+ event.src="maps"; // for the icon
+ event.title="Navigation";
+ if (require("messages").getMessages().find(m=>m.id=="nav"))
+ event.t = "modify";
+ } else {
+ event.t="remove";
+ }
+ require("messages").pushMessage(event);
+ },
+ "cards" : function() {
+ // we receive all, just override what we have
+ if (Array.isArray(event.d))
+ require("Storage").writeJSON("android.cards.json", event.d);
+ },
+ "accelsender": function () {
+ require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
+ load();
+ }
+ };
+ var h = HANDLERS[event.t];
+ if (h) h(); else console.log("GB Unknown",event);
+};
+
+// HTTP request handling - see the readme
+// options = {id,timeout,xpath}
+exports.httpHandler = (url,options) => {
+ options = options||{};
+ if (!NRF.getSecurityStatus().connected)
+ return Promise.reject(/*LANG*/"Not connected to Bluetooth");
+ if (Bangle.httpRequest === undefined)
+ Bangle.httpRequest={};
+ if (options.id === undefined) {
+ // try and create a unique ID
+ do {
+ options.id = Math.random().toString().substr(2);
+ } while( Bangle.httpRequest[options.id]!==undefined);
+ }
+ //send the request
+ var req = {t: "http", url:url, id:options.id};
+ if (options.xpath) req.xpath = options.xpath;
+ if (options.return) req.return = options.return; // for xpath
+ if (options.method) req.method = options.method;
+ if (options.body) req.body = options.body;
+ if (options.headers) req.headers = options.headers;
+ req.timeout = options.timeout || 30000;
+ exports.gbSend(req);
+ //create the promise
+ var promise = new Promise(function(resolve,reject) {
+ //save the resolve function in the dictionary and create a timeout (30 seconds default)
+ Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
+ //if after "timeoutMillisec" it still hasn't answered -> reject
+ delete Bangle.httpRequest[options.id];
+ reject("Timeout");
+ },req.timeout+500)};
+ });
+ return promise;
+};
+
+exports.overwriteGPS = () => { // if the overwrite option is set, call this on init..
+ const origSetGPSPower = Bangle.setGPSPower;
+ Bangle.moveGPSPower = (state) => {
+ if (Bangle.isGPSOn()){
+ let orig = Bangle._PWR.GPS;
+ delete Bangle._PWR.GPS;
+ origSetGPSPower(state);
+ Bangle._PWR.GPS = orig;
+ }
+ };
+
+ // work around Serial1 for GPS not working when connected to something
+ let serialTimeout;
+ let wrap = function(f){
+ return (s)=>{
+ if (serialTimeout) clearTimeout(serialTimeout);
+ origSetGPSPower(1, "androidgpsserial");
+ f(s);
+ serialTimeout = setTimeout(()=>{
+ serialTimeout = undefined;
+ origSetGPSPower(0, "androidgpsserial");
+ }, 10000);
+ };
+ };
+ Serial1.println = wrap(Serial1.println);
+ Serial1.write = wrap(Serial1.write);
+
+ // replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
+ Bangle.setGPSPower = ((isOn, appID) => {
+ let pwr;
+ if (!this.lastGPSEvent){
+ // use internal GPS power function if no gps event has arrived from GadgetBridge
+ pwr = origSetGPSPower(isOn, appID);
+ } else {
+ // we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
+ if (!Bangle._PWR) Bangle._PWR={};
+ if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
+ if (!appID) appID="?";
+ if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
+ if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
+ pwr = Bangle._PWR.GPS.length>0;
+ // stop internal GPS, no clients left
+ if (!pwr) origSetGPSPower(0);
+ }
+ // always update Gadgetbridge on current power state
+ require("android").gbSend({ t: "gps_power", status: pwr });
+ return pwr;
+ }).bind(gpsState);
+ // allow checking for GPS via GadgetBridge
+ Bangle.isGPSOn = () => {
+ return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
+ };
+ // stop GPS on boot if not activated
+ setTimeout(()=>{
+ if (!Bangle.isGPSOn()) require("android").gbSend({ t: "gps_power", status: false });
+ },3000);
+};
diff --git a/apps/android/metadata.json b/apps/android/metadata.json
index 7768efb6c..c92678e23 100644
--- a/apps/android/metadata.json
+++ b/apps/android/metadata.json
@@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
- "version": "0.36",
+ "version": "0.44",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",
@@ -13,7 +13,8 @@
{"name":"android.app.js","url":"app.js"},
{"name":"android.settings.js","url":"settings.js"},
{"name":"android.img","url":"app-icon.js","evaluate":true},
- {"name":"android.boot.js","url":"boot.js"}
+ {"name":"android.boot.js","url":"boot.js"},
+ {"name":"android","url":"lib.js"}
],
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
"sortorder": -8
diff --git a/apps/antonclkplus/settings.js b/apps/antonclkplus/settings.js
index 4448c00ed..70851e983 100644
--- a/apps/antonclkplus/settings.js
+++ b/apps/antonclkplus/settings.js
@@ -94,4 +94,4 @@
E.showMenu(mainmenu);
-});
+})
diff --git a/apps/assistedgps/ChangeLog b/apps/assistedgps/ChangeLog
index 13f928f18..89b1c80f8 100644
--- a/apps/assistedgps/ChangeLog
+++ b/apps/assistedgps/ChangeLog
@@ -3,4 +3,5 @@
0.03: Select GNSS systems to use for Bangle.js 2
0.04: Now turns GPS off after upload
0.05: Fix regression in 0.04 that caused AGPS data not to get loaded
-0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
\ No newline at end of file
+0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
+0.07: Bangle.js 2 now gets estimated time + lat/lon from the browser (~3x faster fix)
\ No newline at end of file
diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html
index 994f6d053..39290c2e6 100644
--- a/apps/assistedgps/custom.html
+++ b/apps/assistedgps/custom.html
@@ -49,7 +49,7 @@
BDS+GLONASS
- GPS+BDS+GLONASS
+ GPS+BDS+GLONASS
@@ -60,6 +60,7 @@