diff --git a/apps/90sclk/ChangeLog b/apps/90sclk/ChangeLog
new file mode 100644
index 000000000..feb008f5f
--- /dev/null
+++ b/apps/90sclk/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New App!
+0.02: Fullscreen settings.
\ No newline at end of file
diff --git a/apps/90sclk/README.md b/apps/90sclk/README.md
new file mode 100644
index 000000000..c09c6fe23
--- /dev/null
+++ b/apps/90sclk/README.md
@@ -0,0 +1,13 @@
+# 90s Clock
+
+A watch face in 90s style:
+
+
+
+Fullscreen mode can be enabled in the settings:
+
+
+
+
+## Creator
+- [David Peer](https://github.com/peerdavid)
diff --git a/apps/90sclk/app-icon.js b/apps/90sclk/app-icon.js
new file mode 100644
index 000000000..28f75c4e6
--- /dev/null
+++ b/apps/90sclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgc8+fAgEgwAMDvPnz99BYdl2weHtu27ft2AGBiEcuEAhAPDg4jGgECIRMN23fthUNgP374vBAB3gAgc/gAXNjlx4EDxwJEpAjG/6IBjkBL4UAjVgBAJuCgPHBQMFEIkkyQjFhwEClgXBEYNBwkQJoibCBwNFBAUCEAVAQZAjC/8euPHDon//hKB//xEYMP//jBYP/+ARDNYM///+EYIgBj1B/8fCIUhEYQRB//FUIM/EZU4EYMkEYP/8VhEYUH/gRBWAUfI4MD+AjBoAsBwEH8EB/EDwE4HwYjCuEHWAOHgExEYKbBCIZNB8fAEYQHByE/EwPABAY+BgRHDBANyJQXHNwIjD8CSBj/+BwMSTwOOBYK2D/4CCNYZQB/iJBQwYjCCIcAgeBSoOAWYQjEVoIRCNAIjKAQKJBgAFC8ZoCWwJbDABMHGQPAAoMQB5EDx/4A4gqBZwIGCWwIABuBWC4EBZwPgv/AcwS/EAAcIU4IRBVQIRKEwIjBv0ARIUDCJIjD//x/ARK/5HC/+BCJkcI45uDgECUgQjCWAM4WwUBWYanEAA8cTARWBEYUC5RAHw1YgEOFQXADQPHIIkAhgICuARBh0A23blhHBagIKBsOGjNswhHDEYUUAoTUBhkxEYMwKwU503bvuwXILmCEYMYsumWYYjB85lDEYovBEYXm7fs25EBI4kYtOWNwIjD4+8NYsw4YjGz9/2hrEoOGjVBwE4NYdzNYSwBuEDEYcxaIUA8+atugGogjBiVgWAI"))
diff --git a/apps/90sclk/app.js b/apps/90sclk/app.js
new file mode 100644
index 000000000..6babbfec2
--- /dev/null
+++ b/apps/90sclk/app.js
@@ -0,0 +1,144 @@
+const SETTINGS_FILE = "90sclk.setting.json";
+const locale = require('locale');
+const storage = require('Storage');
+
+
+/*
+ * Load settings
+ */
+let settings = {
+ fullscreen: false,
+};
+
+let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+for (const key in saved_settings) {
+ settings[key] = saved_settings[key]
+}
+
+
+function getImg() {
+ return require("heatshrink").decompress(atob("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"));
+}
+
+Graphics.prototype.setFontTime = function(scale) {
+ // Actual height 54 (56 - 3)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAPwAAAAAAAA/gAAAAAAAF/AAAAAAAA/8AAAAAAAD/wAAAAAAAP/AAAAAAAA/wAAAAAAAB/AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAB+AAAAAAAAf4AAAAAAAH/wAAAAAAB//AAAAAAAf/+AAAAAAH//8AAAAAB///wAAAAAf///gAAAAH///+AAAAB////4AAAAf///+AAAAH////gAAAB////8AAAA/////AAAAP////wAAAD////8AAAA/////AAAAP////gAAAD////4AAAA////+AAAAD////gAAAAP///4AAAAAf//+AAAAAB///gAAAAAD//4AAAAAAP/+AAAAAAAf/gAAAAAAB/wAAAAAAAH8AAAAAAAAPAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAf/wAAAAAAH//gAAAAAD///gAAAAAf///AAAAAB///+AAAAgP///8AAAHg////wAAAfn////gAAD/t////AAAP/j///8AAA//D///4AAH//D///gAAf//H//+AAB///H//4AAH//+P//gAAP//+P/+AAA///8f/4AAD///8f/gAAP///8f+AAAf///8/wAAB////w/AAAD////A4AAAH///4BgAAAP///gAAAAAf//8AAAAAA///AAAAAAA//wAAAAAAA/+AAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAD+AAAAAAAAP4AAAAAAAB/gAAAAAAAP+AAAAAAAA/5//////4D/v//////gP+//////+A/7//////4D/v//////gP+//////+A/7//////4D/v//////gP+//////+Af7//////4A/v//////gA+//////+AA7///hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAD/4AAfwAAAf/gAD/AAAH/+AA/8AAA//4AH/wAAP//gA//AAD//+AH/8AA///4Af/wAP///gD//AD///+AP/8Af///4A//wH////gD//B////+AP/8f//7/4A//3///P/gD/////w/+AP////8D/4A/////AP/gD////4A/+AP///+AD/4Af///gAH/gB///4AAf/AD///AAB/8AH//wAAH/wAP/8AAAf/AAf/gAAB/4AA/4AAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAf/wD/+AAAB//AP/4AAAH/8A//gAAAf/wD/+AAAB//AP/4AAAH/8A//gAAAf/wB/+AAAAAAAAAB/4A//8A////////wH////////Af///////8A////////wD///+////AP///7///8A////P///wB///8f//+AH///h///4AP//+D///AAf//wH//4AA//+AP//AAB//wAf/4AAD/+AAf/AAAD/gAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAH//AAAAAAA//+AAAAAAP//8AAAAAB///4AAAAAP///wAAAAA////gAAAAH////AAAAA////8AAAAD////4AAAAf////j//gB////+//+AP///////4A////////gD///////+Af///////4B////////gH///////+Af///////gB//////AAAH8AAAHAAAAAAA//wAAAAAAD//4AAAAAAP//gAAAAAA//+AAAAAAD//4AAAAAAP//gAAAAAA//+AAAAAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH////AB/AAf///+AP/+B////4A//4H////AD//gf///8AP/+B////4A//4H////gD//gf/////+AAAAAAAP///wP//gA////A//+AD///8D//4AP///wP//gAf//+A//+AB///4D//4AD///gAB/gAH//8AAAAAAP//gAAAAAAf/8AAAAAAA//gAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAP/8AAAAAAB//8AAAAAAf//8AAAAAD///4AAAAAf///wAAAAD////wAAAAf////AAAAD////+AAAAP////8AAAB/////wAAAP/////gAAA/////+AAAH/////8AAAf/////wAAB//////gAAP/////+AAA//////4AAD//////gAAP/////+AAA//////4AAD//////gAAP//////AAA//////8AAAAf////wAAAAAAP//gAAAAB///+AAAAAH///4AAAAAf///gAAAAB///+AAAAAH///wAAAAAf///AAAAAA///8AAAAAD///gAAAAAH//+AAAAAAP//wAAAAAAf/+AAAAAAA//wAAAAAAB/+AAAAAAAB/gAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAH///AAAAAAf//8AAAAAB///wAAAAAH///AAAAAAf//8AAAAAB///wAAAAAH///AAAAAAf//8AAAAAA//AAAAAAAAAAAAAAAAwAAP//////A////////8D////////wP////////A////////8D////////wP////////A////////8D////////wP////////A////////8D////////wP////////A////0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAD/gAH//AAA//gA//+AAP//AH//8AB//+A///4AP//8H///wA///4f///AH///j///+Af///P///4D///9////wP////////A////////8D////////wP///3///+AAAAAAAAAAB/wAAAAAAAP///x4AAAA////P///4D///8////gH///z///+Af///H///4B///8f///gD///g///8AH//8D///wAf//wH//+AA//+AP//wAA//gA//+AAA/4AA//gAAAEAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAH/8AAAAAAB//8AAAAAAP//4AAAAAB///wAAAAAP///gAAAAB////AAAAAP///8AAAAA////4AAAAH////gAAAAf////AAAAD////8AAAAP////wAAAA/////AAAAD////8AAAAH////wAAAAAAAAAAAAAD///+AAAAAP///////AA///////8AD///////wAP///////AA///////8AB///////wAH//////+AAf//////4AA///////gAD//////8AAH//////wAAf/////+AAA//////4AAB//////AAAH/////4AAAP/////AAAAf////4AAAA////+AAAAB////wAAAAB///8AAAAAB///AAAAAAB//wAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AB8AAAAAH8AP4AAAAA/4B/wAAAAH/gO/AAAAAf+A/8AAAAB/4D/wAAAAH/AP/AAAAAP4AfwAAAAAfAA+AAAAAAQAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), 46, atob("FionHyIiJiIyJScyFA=="), 58+(scale<<8)+(1<<16));
+ return this;
+};
+
+
+Graphics.prototype.setFontDate = function(scale) {
+ // Actual height 28 (27 - 0)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///wf///B///8H///wf//+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAfwAAB+AAAAAAAAfwAAB/AAAH8AAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAADg8AAfH+AB//4A///gD//+AP//AA/w/gBPf+AB//4A///wB//8AH//AAfw8AAPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAD4AAAfwAAD/h4B/+HwH/4/Af//+AP5/4A/n/gB8f8ADg/gAAD8AAADgAAAAAAAAAAAAAAAAAAAAAAAAB8AAAP4CAB/gcAH+D4Af4/wB/n+AH9/wAHv+AAB/wAAf8AAD/gAA/54AH/PwAf5/gB+H+ADw/4AEB/gAAH8AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAP4AB9/wAP//gB///AP//8A///4D///gH//+Af3/wAfP/AAAf8AAB/gAAP/AAB/8AAP/wAAfOAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+AAAP4AAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBgAH///g////n////f///9/////8AB/fgAH8AAAPAAAAAAAAAAAAAAAAAAAAAAD9/AAf38AB/f///9////z///+H///wP//8AAAAAAAAAAAAAAAAAAAAAAAAFAAAA+AAAH8AAAfwAAB/AAAD8AAAEAAAAAAAAAAAAAAAAAAAAAAAAfAAAB8AAAHwAAAfAAA//wAD//AAP/8AA//wAD//AAAfAAAB8AAAHwAAAfAAAAAAAAAAAAAAAAAAAAAAABkAAAPwAAA/AAAB4AAACAAAAAAAAAAAAAAAAAAAAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAD4AAAPgAAA+AAAAAAAAAAAAAAAAAAAAAAACAAAAeAAAD4AAAPgAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAPAAAD+AAA/4AAP/wAD//AB//wAf/8AH//AB//wAf/8AA//AAD/wAAH8AAAeAAAAgAAAAAAAAAAAAAAAAAAAAAAAA8AAAP8AAD/4AEP/wAd//gD9//AP9/8A/9/wD/7/AP/78Af/7wA//CAB/8AAD/AAADwAAAAAAAAAAAAAAAAAAAABwAAAPAAAB8AAAH3//+ff//59///n3//+Pf//4d///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAD8B4AfwfgH/B+A/8P4P/w/j//D+//8P//vw//4/D/+B8H/gHwf8AfAfAA8AAAAAAAAAAAAAAfgAPx/AB/H8AH8fwAPwH/f/H///8f///x/+//H/7/8P/H/gf8P8A/APgAAAAAAAAAAAAAAAAAAAAAAAAD8AAA/8AAH/4AA//gAD//AAf/9/h///+H///4f///h//8AAD/gAAP+AAA/4AAD/gAAAAAAAAAAAAAAAAAAAAAAAAf/4PB//g/n/+D+f/4P5/gf/n/B/+f8H/5/wP/AAAf4AAA/AAAAAAAAAAAAAAAAAAAAAAAAAA8AAAP8AAD/8AAf/4AD//wAP//gB//+AH//8A///wD///AP//8A///wAAP/AAD/+AAH/4AAf/AAB/8AAD/gAAH+AAAPgAAAAAAAAAAAAAAAAAAAAeAAD/4AAP/gAA/+AAB/4AAAAAM+f///5////n///+f///5////n///+f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AP4H+B/w/8P/n/4////n///+f///4AAAAH/9/+P///4/+f/j/5/+H/D/wH4H+AAAHgAAAAAAAAAAAAAAAAAAAA/AAAP/AAB/+AAP/4AA//wAH//AAf/8AB//wAH///wf///B///8H///wP//+A///4B///gD//8AP//gAP/4AAf+AAAPAAAAAAAAAAAAAAAAAAAAAAGAwAA8HgAH4+AAfD4AA4HAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAB4+AAPn4AA+PgABwcAAAAAAAAAAAAAAAAAAAAABgAAAOAAAB8AAAf4AAD/wAAf/AAD/+AAfz8AA8HwADgfgAEA+AAABgAAAAAAAAAAAAAAAAAAAAHj4AAfPgAB8+AAHz4AAfPgAB8+AAHz4AAfPgAB8+AAHz4AAfPgAB8+AAHj4AAAAAAAAAAAAAAAAAAAAAAAAAAAYAAQDwADgfgAfD8AB/fgAD/8AAH/wAAP+AAAfwAAA+AAABwAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAf8AAD/wAAf/gAB/+AAH/4AgA//3B///cH//9wf//3A//8YD/+AAH/4AAP+AAAPgAAAAAAAAAAAAAAAAAD8AAA/8AAH/4AA//wAH8fgAfu+AAf98AOf7wA///ADn/8AO//wA///AD//YAP/8AA/gMAB//wAD//AAH/4AAP/AAAPwAAAAAAAAAAAAAAAAAAAAP///h///+P///5////n///+f//AB+D8AH///+f///4////h///+B///4AAAAAAAAAAAAAAAAAAAAAAAf///j////P///8////z////P///8////z///AP///+f///5////D/5/8H/H/gPwH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAA//gAH//AA//+AH//8A///4D///gf///B///8H///wf///B/gH8H+Afwf4B/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//8A////D///8P///w////B///8H///gP//+A///wB//+AD//wAD/8AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////n///+f///5////n///+f///5////n/fP+f9+/5/37/n/AP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAf///5////n///+f///5////n///+f///5/z8AH/PwAf8AAAAAAAAAAAAAAAAAAAAAAA/gAAP/wAD//wAf//gD///Af//+B///4P///w////H///8f///x////H///8f4H/x/gf/D+B/8AAHAAAAcAAABwAAAHAAAAAAAAAAAAAAAAAAAAAAABgH///gf//+B///4H///gf//+B///4AB+AAf/9+B///4H///gf//+B///4H///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8P///w////D///8P///w////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAfAAAB+AAAH4AAAfgAAB+f///5////n///+f///x///8AAAAAAAAAAAAAAAAAAAAAAAAAAANH///8f///x////H///8f///x////H//+AAH/8AA//4AH//wA///gD//+Af//8B///wH///Af//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX5////n///+f///5////v///+////5//7/gAAP+AAA/4AAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAvh///+P///5////n///+f///5/wAAD+AAAP8AAAfwAAD/AAAP4AAB////n///+f///5////j///+H///4AAAAAAAAAAAAAAAAAAAAAAAH///gf//+B///4H///gf//+B///4B//4AB//4B///4H///gf//+B///4H///gf//+B///4AAAAAAAAAAAAAAAD/AAA//gAH//AA//+AH//8Af//wD///gf//+B///8H///wf///B/z/8H//wAf///B///8H///wf///A///8D///gH//8AP//wAf/+AA//gAA/4AAAAAAAAAAAAAAAAAA/x////H///8f///x////H///8f///x//gAH/+AAf/4AA//gAB/8AAD/gAAH8AAAAAAAAAAAAAAAAAAAAAAAcAAAP8AAD/8AA//8AH//4Af//wD///AP//+B///4H///wf///B///8H//+Af///B///8H///wf///A///8D///gH//+Af//4A///gB//+AB//4AB/PgAAAYAAAAAAAAAAAAAAAAAAAP///5////n///+f///5////n///+P///4////D///+P///4////h/9/+D/gAAD4AAAAAAAAAAAAAAAAAAAAAAAAAADwAAA/wAAH/gAA//AfH/8D8f/4f5////n+P/+f4P/5/g//j8D/8DgH/wAAP8AAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAfAAAB8//8H///wf///B///8H///wf//AB8AAAHwAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAB///8H///8f///5////n///+AAAH5////n///+f///x////H///wf/gAAAAAAAAAAAAAAAAQAAAD8AAAP/AAA//wAH//8Af//+B////gf//+AP//4AP//gP//+P///5////n///gP//gA//AAD/gAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+D///8P///4////j///+PAAfwAAB/AAAH4AAAfw////j//++P//74///vj///8AAB7AAAAAAAAAAAAAAAAAAAAAAAAAAABgeAA+B/AP8P/n/w////j///+H///gH//4D///8P///4////h/4/8H8AfwPAAOAgAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAHwAAAfgAAB/AAAH8H/wf///B///8H///wf///B///8H/8cAfwAAB+AAAHwAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAABwAAAHAAD8cAD/xwD//HH//+f///5////n///+P//w4f/wDh/gAOHgAA4AAADgAAAOAAAAAAAAAAAAAA///+////////////////////////+AAA/wAAD/gAAP+AAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAA/AAAD/AAAf/AAB//gAD//gAD//gAD//gAD//gAD//gAD//AAD/4AAB/AAAB8AAABgAAAAAAAAAAAAAAAAAAOAAAe4AAB7gAAHuAAAe////7////v///+////7////v///YwAAAAAAAA="), 32, atob("HA4NFhEaFwoNDQsRCRALFBMPEBESEBgSExgKCRARERIXEhQVExEPGBMOERYRFhQaExwUExARFRYTFhMPFA4="), 28+(scale<<8)+(1<<16));
+ return this;
+}
+
+
+// timeout used to update every minute
+var 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 drawBorderString(str, x, y, b, fc){
+ g.setColor("#000");
+ g.drawString(str, x, y+b);
+ g.drawString(str, x, y-b);
+ g.drawString(str, x+b, y);
+ g.drawString(str, x-b, y);
+
+ g.setColor(fc);
+ g.drawString(str,x+1,y);
+}
+
+
+function getSteps() {
+ try{
+ if (WIDGETS.wpedom !== undefined) {
+ return WIDGETS.wpedom.getSteps();
+ } else if (WIDGETS.activepedom !== undefined) {
+ return WIDGETS.activepedom.getSteps();
+ }
+ } catch(ex) {
+ // In case we failed, we can only show 0 steps.
+ }
+
+ return 0;
+}
+
+
+function draw() {
+ // queue draw in one minute
+ queueDraw();
+
+ var x = g.getWidth()/2;
+ var y_offset = settings.fullscreen ? 0 : 10;
+ var y = g.getHeight()/2-20 + y_offset;
+
+ g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
+ g.drawImage(getImg(),0,0);
+
+ // Draw time
+ var date = new Date();
+ var timeStr = locale.time(date,1);
+ g.setFontAlign(0,0);
+ g.setFontTime();
+ drawBorderString(timeStr, x, y, 5, "#fff");
+
+ // Draw date
+ y += 50;
+ x = x - g.stringWidth(timeStr) / 2 + 5;
+ g.setFontDate();
+ g.setFontAlign(-1,0);
+ var dateStr = locale.dow(date, true).toUpperCase() + date.getDate();
+ var fc = Bangle.isLocked() ? "#0ff" :"#fff";
+ fc = E.getBattery() < 50 ? "#f00" : fc;
+ drawBorderString(dateStr, x, y, 3, fc);
+
+ // Draw steps
+ g.setFontAlign(1,1);
+ var steps = parseInt(getSteps() / 1000);
+ drawBorderString(steps, g.getWidth()-10, g.getHeight()-10, 3, "#f0f");
+
+ // Draw widgets if not fullscreen
+ if(settings.fullscreen){
+ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+ } else {
+ Bangle.drawWidgets();
+ }
+}
+
+Bangle.loadWidgets();
+
+// Clear the screen once, at startup
+g.setTheme({bg:"#000",fg:"#fff",dark:false}).clear();
+// draw immediately at first, queue update
+draw();
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+
+Bangle.on('lock', function(isLocked) {
+ print("LOCK");
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ draw();
+});
+
+
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
diff --git a/apps/90sclk/app.png b/apps/90sclk/app.png
new file mode 100644
index 000000000..29875b1dc
Binary files /dev/null and b/apps/90sclk/app.png differ
diff --git a/apps/90sclk/bg.png b/apps/90sclk/bg.png
new file mode 100644
index 000000000..4ebf755ad
Binary files /dev/null and b/apps/90sclk/bg.png differ
diff --git a/apps/90sclk/metadata.json b/apps/90sclk/metadata.json
new file mode 100644
index 000000000..fb2824a6f
--- /dev/null
+++ b/apps/90sclk/metadata.json
@@ -0,0 +1,18 @@
+{
+ "id": "90sclk",
+ "name": "90s Clock",
+ "version": "0.02",
+ "description": "A 90s style watch-face",
+ "readme": "README.md",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"90sclk.app.js","url":"app.js"},
+ {"name":"90sclk.img","url":"app-icon.js","evaluate":true},
+ {"name":"90sclk.settings.js","url":"settings.js"}
+ ]
+}
diff --git a/apps/90sclk/screenshot.png b/apps/90sclk/screenshot.png
new file mode 100644
index 000000000..182a85321
Binary files /dev/null and b/apps/90sclk/screenshot.png differ
diff --git a/apps/90sclk/screenshot_2.png b/apps/90sclk/screenshot_2.png
new file mode 100644
index 000000000..9646b1168
Binary files /dev/null and b/apps/90sclk/screenshot_2.png differ
diff --git a/apps/90sclk/settings.js b/apps/90sclk/settings.js
new file mode 100644
index 000000000..8f97cd317
--- /dev/null
+++ b/apps/90sclk/settings.js
@@ -0,0 +1,31 @@
+(function(back) {
+ const SETTINGS_FILE = "90sclk.setting.json";
+
+ // initialize with default settings...
+ const storage = require('Storage')
+ let settings = {
+ fullscreen: false,
+ };
+ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+ for (const key in saved_settings) {
+ settings[key] = saved_settings[key]
+ }
+
+ function save() {
+ storage.write(SETTINGS_FILE, settings)
+ }
+
+
+ E.showMenu({
+ '': { 'title': '90s Clock' },
+ '< Back': back,
+ 'Full Screen': {
+ value: settings.fullscreen,
+ format: () => (settings.fullscreen ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.fullscreen = !settings.fullscreen;
+ save();
+ },
+ }
+ });
+ })
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 4576237a5..fcafc386f 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -14,3 +14,7 @@
0.13: Alarm widget state now updates when setting/resetting an alarm
0.14: Order of 'back' menu item
0.15: Fix hour/minute wrapping code for new menu system
+0.16: Adding alarm library
+0.17: Moving alarm internals to 'sched' library
+0.18: Cope with >1 identical alarm at once (#1667)
+0.19: Ensure rescheduled alarms that already fired have 'last' reset
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
new file mode 100644
index 000000000..42131a5a6
--- /dev/null
+++ b/apps/alarm/README.md
@@ -0,0 +1,6 @@
+Default Alarm & Timer
+======================
+
+This allows you to add/modify any running timers.
+
+It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js
deleted file mode 100644
index a655dad1e..000000000
--- a/apps/alarm/alarm.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Chances are boot0.js got run already and scheduled *another*
-// 'load(alarm.js)' - so let's remove it first!
-clearInterval();
-
-function formatTime(t) {
- var hrs = 0|t;
- var mins = Math.round((t-hrs)*60);
- return hrs+":"+("0"+mins).substr(-2);
-}
-
-function getCurrentHr() {
- var time = new Date();
- return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
-}
-
-function showAlarm(alarm) {
- var msg = formatTime(alarm.hr);
- var buzzCount = 10;
- if (alarm.msg)
- msg += "\n"+alarm.msg;
- Bangle.loadWidgets();
- Bangle.drawWidgets();
- E.showPrompt(msg,{
- title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
- buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
- }).then(function(sleep) {
- buzzCount = 0;
- if (sleep) {
- if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
- alarm.hr += 10/60; // 10 minutes
- } else {
- alarm.last = (new Date()).getDate();
- if (alarm.ohr!==undefined) {
- alarm.hr = alarm.ohr;
- delete alarm.ohr;
- }
- if (!alarm.rp) alarm.on = false;
- }
- require("Storage").write("alarm.json",JSON.stringify(alarms));
- load();
- });
- function buzz() {
- if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
- Bangle.buzz(100).then(()=>{
- setTimeout(()=>{
- Bangle.buzz(100).then(function() {
- if (buzzCount--)
- setTimeout(buzz, 3000);
- else if(alarm.as) { // auto-snooze
- buzzCount = 10;
- setTimeout(buzz, 600000);
- }
- });
- },100);
- });
- }
- buzz();
-}
-
-// Check for alarms
-var day = (new Date()).getDate();
-var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
-var alarms = require("Storage").readJSON("alarm.json",1)||[];
-var active = alarms.filter(a=>a.on&&(a.hr
a.hr-b.hr);
- showAlarm(active[0]);
-} else {
- // otherwise just go back to default app
- setTimeout(load, 100);
-}
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 56184edf1..b9404358e 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -1,36 +1,43 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
-var alarms = require("Storage").readJSON("alarm.json",1)||[];
-/*alarms = [
- { on : true,
- hr : 6.5, // hours + minutes/60
- msg : "Eat chocolate",
- last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
- rp : true, // repeat
- as : false, // auto snooze
- timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
- }
-];*/
+var alarms = require("sched").getAlarms();
+// An array of alarm objects (see sched/README.md)
+
+// time in ms -> { hrs, mins }
+function decodeTime(t) {
+ t = 0|t; // sanitise
+ var hrs = 0|(t/3600000);
+ return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
+}
+
+// time in { hrs, mins } -> ms
+function encodeTime(o) {
+ return o.hrs*3600000 + o.mins*60000;
+}
function formatTime(t) {
- var hrs = 0|t;
- var mins = Math.round((t-hrs)*60);
- return hrs+":"+("0"+mins).substr(-2);
+ var o = decodeTime(t);
+ return o.hrs+":"+("0"+o.mins).substr(-2);
}
-function formatMins(t) {
- mins = (0|t)%60;
- hrs = 0|(t/60);
- return hrs+":"+("0"+mins).substr(-2);
-}
-
-function getCurrentHr() {
+function getCurrentTime() {
var time = new Date();
- return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
+ return (
+ time.getHours() * 3600000 +
+ time.getMinutes() * 60000 +
+ time.getSeconds() * 1000
+ );
+}
+
+function saveAndReload() {
+ require("sched").setAlarms(alarms);
+ require("sched").reload();
}
function showMainMenu() {
+ // Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
+ // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
'': { 'title': 'Alarm/Timer' },
/*LANG*/'< Back' : ()=>{load();},
@@ -38,140 +45,161 @@ function showMainMenu() {
/*LANG*/'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
+ var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer) {
- txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
+ type = /*LANG*/"Timer";
+ txt = " "+formatTime(alarm.timer);
} else {
- txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
- if (alarm.rp) txt += /*LANG*/" (repeat)";
+ type = /*LANG*/"Alarm";
+ txt = " "+formatTime(alarm.t);
}
- menu[txt] = function() {
- if (alarm.timer) editTimer(idx);
- else editAlarm(idx);
+ if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
+ // rename duplicate alarms
+ if (menu[type+txt]) {
+ var n = 2;
+ while (menu[type+" "+n+txt]) n++;
+ txt = type+" "+n+txt;
+ } else txt = type+txt;
+ // add to menu
+ menu[txt] = {
+ value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
+ onchange : function() {
+ if (alarm.timer) editTimer(idx, alarm);
+ else editAlarm(idx, alarm);
+ }
};
});
-
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
-function editAlarm(alarmIndex) {
+function editDOW(dow, onchange) {
+ const menu = {
+ '': { 'title': /*LANG*/'Days of Week' },
+ '< Back' : () => onchange(dow)
+ };
+ for (var i = 0; i < 7; i++) (i => {
+ var dayOfWeek = require("locale").dow({ getDay: () => i });
+ menu[dayOfWeek] = {
+ value: !!(dow&(1< v ? "Yes" : "No",
+ onchange: v => v ? dow |= 1< showMainMenu(),
/*LANG*/'Hours': {
- value: hrs, min : 0, max : 23, wrap : true,
- onchange: v => hrs=v
+ value: t.hrs, min : 0, max : 23, wrap : true,
+ onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
- value: mins, min : 0, max : 59, wrap : true,
- onchange: v => mins=v
+ value: t.mins, min : 0, max : 59, wrap : true,
+ onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
- value: en,
+ value: a.on,
format: v=>v?"On":"Off",
- onchange: v=>en=v
+ onchange: v=>a.on=v
},
/*LANG*/'Repeat': {
- value: en,
+ value: a.rp,
format: v=>v?"Yes":"No",
- onchange: v=>repeat=v
+ onchange: v=>a.rp=v
},
+ /*LANG*/'Days': {
+ value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)})
+ },
+ /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Auto snooze': {
- value: as,
+ value: a.as,
format: v=>v?"Yes":"No",
- onchange: v=>as=v
+ onchange: v=>a.as=v
}
};
- function getAlarm() {
- var hr = hrs+(mins/60);
- var day = 0;
- // If alarm is for tomorrow not today (eg, in the past), set day
- if (hr < getCurrentHr())
- day = (new Date()).getDate();
- // Save alarm
- return {
- on : en, hr : hr,
- last : day, rp : repeat, as: as
- };
- }
- menu[/*LANG*/"> Save"] = function() {
- if (newAlarm) alarms.push(getAlarm());
- else alarms[alarmIndex] = getAlarm();
- require("Storage").write("alarm.json",JSON.stringify(alarms));
+ menu[/*LANG*/"Save"] = function() {
+ a.t = encodeTime(t);
+ a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
+ if (newAlarm) alarms.push(a);
+ else alarms[alarmIndex] = a;
+ saveAndReload();
showMainMenu();
};
if (!newAlarm) {
- menu[/*LANG*/"> Delete"] = function() {
+ menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
- require("Storage").write("alarm.json",JSON.stringify(alarms));
+ saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
-function editTimer(alarmIndex) {
+function editTimer(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
- var hrs = 0;
- var mins = 5;
- var en = true;
- if (!newAlarm) {
- var a = alarms[alarmIndex];
- mins = (0|a.timer)%60;
- hrs = 0|(a.timer/60);
- en = a.on;
+ var a = {
+ timer : 5*60*1000, // 5 minutes
+ on : true,
+ rp : false,
+ as : false,
+ dow : 0b1111111,
+ last : 0,
+ vibrate : ".."
}
+ if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
+ if (alarm) Object.assign(a,alarm);
+ var t = decodeTime(a.timer);
+
const menu = {
'': { 'title': /*LANG*/'Timer' },
+ '< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
- value: hrs, min : 0, max : 23, wrap : true,
- onchange: v => hrs=v
+ value: t.hrs, min : 0, max : 23, wrap : true,
+ onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
- value: mins, min : 0, max : 59, wrap : true,
- onchange: v => mins=v
+ value: t.mins, min : 0, max : 59, wrap : true,
+ onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
- value: en,
- format: v=>v?/*LANG*/"On":/*LANG*/"Off",
- onchange: v=>en=v
- }
+ value: a.on,
+ format: v=>v?"On":"Off",
+ onchange: v=>a.on=v
+ },
+ /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
};
- function getTimer() {
- var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
- var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
- // Save alarm
- return {
- on : en,
- timer : (hrs*60)+mins,
- hr : hr,
- rp : false, as: false
- };
- }
- menu["> Save"] = function() {
- if (newAlarm) alarms.push(getTimer());
- else alarms[alarmIndex] = getTimer();
- require("Storage").write("alarm.json",JSON.stringify(alarms));
+ menu[/*LANG*/"Save"] = function() {
+ a.timer = encodeTime(t);
+ a.t = getCurrentTime() + a.timer;
+ a.last = 0;
+ if (newAlarm) alarms.push(a);
+ else alarms[alarmIndex] = a;
+ saveAndReload();
showMainMenu();
};
if (!newAlarm) {
- menu["> Delete"] = function() {
+ menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
- require("Storage").write("alarm.json",JSON.stringify(alarms));
+ saveAndReload();
showMainMenu();
};
}
diff --git a/apps/alarm/boot.js b/apps/alarm/boot.js
deleted file mode 100644
index 47dae5361..000000000
--- a/apps/alarm/boot.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// check for alarms
-(function() {
- var alarms = require('Storage').readJSON('alarm.json',1)||[];
- var time = new Date();
- var active = alarms.filter(a=>a.on);
- if (active.length) {
- active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
- var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
- if (!require('Storage').read("alarm.js")) {
- console.log("No alarm app!");
- require('Storage').write('alarm.json',"[]");
- } else {
- var t = 3600000*(active[0].hr-hr);
- if (active[0].last == time.getDate() || t < 0) t += 86400000;
- if (t<1000) t=1000;
- /* execute alarm at the correct time. We avoid execing immediately
- since this code will get called AGAIN when alarm.js is loaded. alarm.js
- will then clearInterval() to get rid of this call so it can proceed
- normally. */
- setTimeout(function() {
- load("alarm.js");
- },t);
- }
- }
-})();
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index d29298309..9636257ca 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -1,18 +1,17 @@
{
"id": "alarm",
- "name": "Default Alarm & Timer",
+ "name": "Alarm & Timer",
"shortName": "Alarms",
- "version": "0.15",
- "description": "Set and respond to alarms and timers",
+ "version": "0.19",
+ "description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "dependencies": {"scheduler":"type"},
"storage": [
{"name":"alarm.app.js","url":"app.js"},
- {"name":"alarm.boot.js","url":"boot.js"},
- {"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
{"name":"alarm.wid.js","url":"widget.js"}
- ],
- "data": [{"name":"alarm.json"}]
+ ]
}
diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js
index e8bb79fc7..052ac9ebd 100644
--- a/apps/alarm/widget.js
+++ b/apps/alarm/widget.js
@@ -1,7 +1,8 @@
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
- WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
+ // don't include library here as we're trying to use as little RAM as possible
+ WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();
diff --git a/apps/altimeter/ChangeLog b/apps/altimeter/ChangeLog
new file mode 100644
index 000000000..29388520e
--- /dev/null
+++ b/apps/altimeter/ChangeLog
@@ -0,0 +1,2 @@
+0.01: New App!
+0.02: Actually upload correct code
diff --git a/apps/altimeter/app-icon.js b/apps/altimeter/app-icon.js
new file mode 100644
index 000000000..1f8dfb637
--- /dev/null
+++ b/apps/altimeter/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///t9TmuV3+GJf4AN+ALVgf8BasP/4LVn//4ALUWgJUJBZUDBYJUIBZcP3/nKhEOt/WBZE5r+VKg0KgEVr9V3wLHqtaqt9sALElWAqoABt1QBZNeBYuq0ILCrVUBYulBYVWBYkCBYgABBZ8K1WVBYlABZegKQWqBQlVqALKqWoKQWpBYtWBZeqKRAAB1WABZZSHAANq0ALLKQ6qC1ALLKQ5UEAH4AG"))
diff --git a/apps/altimeter/app.js b/apps/altimeter/app.js
new file mode 100644
index 000000000..cac4e80fd
--- /dev/null
+++ b/apps/altimeter/app.js
@@ -0,0 +1,30 @@
+Bangle.setBarometerPower(true, "app");
+
+g.clear(1);
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+var zero = 0;
+var R = Bangle.appRect;
+var y = R.y + R.h/2;
+var MEDIANLENGTH = 20;
+var avr = [], median;
+var value = 0;
+
+Bangle.on('pressure', function(e) {
+ while (avr.length>MEDIANLENGTH) avr.pop();
+ avr.unshift(e.altitude);
+ median = avr.slice().sort();
+ g.reset().clearRect(0,y-30,g.getWidth()-10,y+30);
+ if (median.length>10) {
+ var mid = median.length>>1;
+ value = E.sum(median.slice(mid-4,mid+5)) / 9;
+ g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y);
+ }
+});
+
+g.reset();
+g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
+g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
+setWatch(function() {
+ zero = value;
+}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
diff --git a/apps/altimeter/app.png b/apps/altimeter/app.png
new file mode 100644
index 000000000..9c9d69077
Binary files /dev/null and b/apps/altimeter/app.png differ
diff --git a/apps/altimeter/metadata.json b/apps/altimeter/metadata.json
new file mode 100644
index 000000000..8bdbf3022
--- /dev/null
+++ b/apps/altimeter/metadata.json
@@ -0,0 +1,12 @@
+{ "id": "altimeter",
+ "name": "Altimeter",
+ "version":"0.02",
+ "description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
+ "icon": "app.png",
+ "tags": "tool,outdoors",
+ "supports" : ["BANGLEJS2"],
+ "storage": [
+ {"name":"altimeter.app.js","url":"app.js"},
+ {"name":"altimeter.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/bee/ChangeLog b/apps/bee/ChangeLog
new file mode 100644
index 000000000..cfb44c8e9
--- /dev/null
+++ b/apps/bee/ChangeLog
@@ -0,0 +1,3 @@
+0.01: New app!
+0.02: Fix bug with regenerating index, fix bug in word lookups
+0.03: Improve word search performance
diff --git a/apps/bee/README.md b/apps/bee/README.md
index c6461fb8e..3d0a4c14a 100644
--- a/apps/bee/README.md
+++ b/apps/bee/README.md
@@ -27,10 +27,7 @@ least once and yields an additional 7 points. Each game contains at least one pa
The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words', using the '2of12inf' word list).
The dictionary is fairly large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat
slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory).
-In order to make checking the validity of a guessed word faster an index file ('bee_lindex.json') is installed with
-the app that facilitates faster word lookups. This index file is specific to the dictionary file used. If one were to
-replace the dictionary file with a different version (e.g. a different language) the index file has to be regenerated. The easiest
-way to do so is to delete (via the Web IDE or the fileman app on the watch) the file 'bee_lindex.json' - it will be regenerated (and saved,
-i.e. it only happens once) on app startup automatically, a process that takes roughly 30 seconds.
+This file can be replaced with a custom dictionary, an ASCII file containing a newline-separated (single "\n", not DOS-style "\r\n") alphabetically
+sorted (sorting is important for the word lookup algorithm) list of words.

diff --git a/apps/bee/bee.app.js b/apps/bee/bee.app.js
index a12ca7820..878e9763c 100644
--- a/apps/bee/bee.app.js
+++ b/apps/bee/bee.app.js
@@ -1,7 +1,7 @@
const S = require("Storage");
+const words = S.read("bee.words");
var letters = [];
-var letterIdx = [];
var centers = [];
@@ -12,28 +12,17 @@ var score = 0;
var intervalID = -1;
-function prepareLetterIdx () {
+function biSearch(w, ws, start, end, count) {
"compile"
- var li = [0];
- if (S.read("bee_lindex.json")!==undefined) li = S.readJSON("bee_lindex.json"); // check for cached index
- else {
- for (var i=1; i<26; ++i) {
- var prefix = String.fromCharCode(97+i%26);
- console.log(prefix);
- li.push(S.read('bee.words').indexOf("\n"+prefix, li[i-1])+1);
- }
- li.push(S.read('bee.words').length);
- S.writeJSON("bee_lindex.json", li);
- }
- for (var i=0; i<26; ++i) letterIdx[i] = S.read("bee.words", li[i], li[i+1]-li[i]);
-}
-
-function findWord (w) {
- "compile"
- var ci = w.charCodeAt(0)-97;
- var f = letterIdx[ci].indexOf(w);
- if (f>=0 && letterIdx[ci][f+w.length]=="\n") return true;
- return false;
+ if (start>end-w.legnth || count--<=0) return ws.substr(start, end-start).indexOf("\n"+w+"\n");
+ var mid = (end+start)>>1;
+ if (ws[mid-1]==="\n") --mid;
+ else while (midws[mid+i+1]) return biSearch(w, ws, mid+1, end, count);
}
function isPangram(w) {
@@ -45,8 +34,9 @@ function isPangram(w) {
function checkWord (w) {
if (w.indexOf(String.fromCharCode(97+letters[0]))==-1) return false; // does it contain central letter?
if (foundWords.indexOf(w)>=0) return false; // already found
- if (findWord(w)) {
+ if (biSearch(w, words, 0, words.length, 20)>-1) {
foundWords.push(w);
+ foundWords.sort();
if (w.length==4) score++;
else score += w.length;
if (isPangram(w)) score += 7;
@@ -91,13 +81,12 @@ function pickLetters() {
var ltrs = "";
while (ltrs.length!==7) {
ltrs = [];
- var j = Math.floor(26*Math.random());
- var i = Math.floor((letterIdx[j].length-10)*Math.random());
- while (letterIdx[j][i]!="\n" && iIcons created by Flaticon
+
+## Creator
+- [David Peer](https://github.com/peerdavid)
diff --git a/apps/bwclk/app-icon.js b/apps/bwclk/app-icon.js
new file mode 100644
index 000000000..1df0fa6a5
--- /dev/null
+++ b/apps/bwclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))
diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js
new file mode 100644
index 000000000..d4e6c50ab
--- /dev/null
+++ b/apps/bwclk/app.js
@@ -0,0 +1,412 @@
+/*
+ * Includes
+ */
+const locale = require('locale');
+const storage = require('Storage');
+
+/*
+ * Statics
+ */
+const SETTINGS_FILE = "bwclk.setting.json";
+const TIMER_IDX = "bwclk";
+const W = g.getWidth();
+const H = g.getHeight();
+
+/*
+ * Settings
+ */
+let settings = {
+ fullscreen: false,
+ showLock: true,
+ showInfo: 0,
+};
+
+let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+for (const key in saved_settings) {
+ settings[key] = saved_settings[key]
+}
+
+
+/*
+ * Assets
+ */
+
+// Manrope font
+Graphics.prototype.setLargeFont = function(scale) {
+ // Actual height 49 (50 - 2)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAfwAAAAAAAAf/AAAAAAAAf/8AAAAAAAf//wAAAAAAP///AAAAAAP///8AAAAAP////wAAAAP////4AAAAP////8AAAAH////8AAAAH////8AAAAB////8AAAAAH///+AAAAAAf//+AAAAAAB//+AAAAAAAH/+AAAAAAAAf+AAAAAAAAB/AAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///gAAAAAD////4AAAAA/////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAA///////4AAD/4AAAH/wAAP+AAAAP/AAB/wAAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/gAAAAH+AAP8AAAAAf4AA/wAAAAB/gAD/AAAAAH+AAP8AAAAAf4AAf4AAAAB/gAB/gAAAAH+AAH+AAAAA/4AAf8AAAAH/AAB/4AAAA/8AAD/4AAAH/wAAP/8AAH/+AAAf//////4AAA///////AAAB//////4AAAD//////AAAAH/////4AAAAP////+AAAAAP////gAAAAAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAB/wAAAAAAAAH/AAAAAAAAA/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAD/gAAAAAAAAP+AAAAAAAAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAP///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAfwAAAH8AAAD/AAAB/wAAAf8AAAP/AAAD/wAAB/8AAAf/AAAP/wAAD/8AAB//AAAf/wAAH/8AAD//AAA//gAAf/8AAD/wAAB//wAAf+AAAP//AAB/wAAB//8AAH+AAAP//wAAf4AAB///AAD/AAAP/v8AAP8AAB/8/wAA/wAAP/j/AAD/AAB/8P8AAH+AAH/g/wAAf4AA/8D/AAB/wAH/gP8AAH/AA/+A/wAAf/AP/wD/AAA//D/+AP8AAD////wA/wAAH///+AD/AAAP///wAP8AAAf//+AA/wAAA///wAD/AAAB//+AAP8AAAB//gAA/wAAAB/4AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAD+AAAAHwAAAf4AAAAfwAAB/gAAAB/gAAH+AAAAP/AAAf4AAAA/8AAB/gAAAD/4AAH+ADAAH/wAAf4AeAAP/AAB/gD+AAP8AAH+Af+AA/4AAf4D/4AB/gAB/gP/AAH+AAH+B/8AAf4AAf4P/wAB/gAB/h//AAH+AAH+P/8AAf4AAf5//wAB/gAB/v//gAP+AAH+//+AA/4AAf//f8AH/AAB//5/8B/8AAH//D////gAAf/4P///+AAB//Af///wAAH/4A///+AAAf/AB///wAAB/4AD//+AAAH/AAH//gAAAP4AAD/4AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAA/+AAAAAAAAP/4AAAAAAAH//gAAAAAAB//+AAAAAAAf//4AAAAAAH///gAAAAAB///+AAAAAAf///4AAAAAH//9/gAAAAD///H+AAAAA///wf4AAAAP//8B/gAAAD///AH+AAAA///wAf4AAAH//8AB/gAAAf//AAH+AAAB//gAAf4AAAH/4AAB/gAAAf+AAAH+AAAB/gAf///8AAH4AB////wAAeAAH////AABgAAf///8AAAAAB////wAAAAAH////AAAAAAf///8AAAAAB////wAAAAAH////AAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAeAAAAAf//AB+AAAH///+AP8AAAf///4A/4AAB////gD/wAAH////Af/gAAf///8B/+AAB////wB/8AAH///+AB/wAAf4Af4AD/gAB/gB/AAP+AAH+AP8AAf4AAf4A/wAB/gAB/gD+AAH+AAH+AP4AAf4AAf4A/gAB/gAB/gD/AAH+AAH+AP8AAf4AAf4A/wAD/gAB/gD/gAf8AAH+AH/AD/wAAf4Af/Af+AAB/gB////4AAH+AD////AAAf4AH///8AAB/gAP///gAAH+AA///8AAAAAAA///AAAAAAAB//4AAAAAAAB/+AAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///gAAAAAB////4AAAAAf////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAAf//////4AAD/4D/wH/wAAP+AP8AP/AAB/wB/gAf8AAH/AH8AA/4AAf4A/wAB/gAB/gD/AAH+AAH8AP4AAf4AA/wA/gAB/gAD/AD+AAH+AAH8AP8AAf4AAf4A/wAB/gAB/gD/AAP+AAH+AP+AB/wAAf8Af8AP/AAA/4B/8B/8AAD/gH////gAAP8AP///8AAAfgAf///wAAA8AB///+AAADgAD///wAAAAAAD//+AAAAAAAH//gAAAAAAAH/4AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAAAH+AAAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAf4AAAAADgAB/gAAAAA+AAH+AAAAAf4AAf4AAAAH/gAB/gAAAD/+AAH+AAAA//4AAf4AAAf//gAB/gAAH//+AAH+AAD///wAAf4AA///8AAB/gAf//+AAAH+AH///gAAAf4D///wAAAB/g///8AAAAH+f//+AAAAAf////gAAAAB////wAAAAAH///8AAAAAAf//+AAAAAAB///gAAAAAAH//wAAAAAAAf/8AAAAAAAB/+AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAB/wB//wAAAAf/wf//gAAAD//z///AAAAf/////+AAAD//////8AAAf//////4AAD///////gAAP////w//AAB/+f/8Af8AAH/Af/gA/4AAf4A/8AD/gAB/gB/wAH+AAP8AH+AAf4AA/wAf4AB/gAD/AB/gAH+AAP8AH+AAf4AA/wAf4AB/gAB/gB/wAH+AAH+AP/AAf4AAf8A/+AD/gAB/8f/8Af8AAD////4H/wAAP//////+AAAf//////4AAA///////AAAD//////8AAAH//z///gAAAH/+H//4AAAAH/gH//AAAAAAAAH/wAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAP/wAAAAAAAD//wAAAAAAAf//wAAAAAAH///gABgAAA////AAPAAAD///+AB+AAAf///4AP4AAD////wB/wAAP/AP/AH/AAB/4Af+AP+AAH/AA/4A/4AAf4AB/gB/gAB/gAH+AH+AAP8AAP4Af4AA/wAA/gB/gAD/AAD+AH+AAP8AAP4Af4AA/4AB/gB/gAB/gAH+AH+AAH+AAfwA/4AAf8AD/AH/AAB/4Af4A/8AAD/4H/gP/wAAP//////+AAAf//////wAAA///////AAAB//////4AAAD//////AAAAH/////wAAAAH////+AAAAAH////AAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ExwqHCYlJyYoIicoFg=="), 64+(scale<<8)+(1<<16));
+ return this;
+};
+
+
+Graphics.prototype.setMediumFont = function(scale) {
+ // Actual height 41 (42 - 2)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
+ return this;
+};
+
+Graphics.prototype.setSmallFont = function(scale) {
+ // Actual height 28 (27 - 0)
+ this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//84D//zgP/+GAAAAAAAAAAAAAAAAAAAD4AAAPgAAA+AAAAAAAAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAg4AAHDgAAcOCABw54AHD/gAf/8AD/8AB//gAP8OAA9w4YCHD/gAcf+AB//gAf/gAP/uAA/w4ADnDgAAcOAABw4AAHAAAAcAAAAAAAAAAAAAAAIAA+A4AH8HwA/4PgHjgOAcHAcBwcBw/BwH78DgfvwOB8HA4HAOBw8A+HngB4P8ADgfgAAAYAAAAAAAAAAB4AAAf4AQB/gDgOHAeA4cDwDhweAOHDwA88eAB/nwAD88AAAHgAAA8AAAHn4AA8/wAHnvgA8cOAHhg4A8GDgHgcOA8B74BgD/AAAH4AAAAAAAAAAAAAAAAAMAAAH8AD8/4Af/3wB/8HgODwOA4HA4DgODgOAcOA4A44DwDzgHAH8AMAPwAQP+AAA/8AAAB4AAADAAAAAA+AAAD4AAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AD//+A/+/+H4AD98AAB3gAADIAAAAAAAAAAAAAIAAABwAAAXwAAHPwAB8P8D/gP//4AH/8AAAAAAAAAAAAAAAAAAAAAAAAGAAAA4gAAB/AAAH8AAD/AAAP8AAAH4AAAfwAADiAAAOAAAAAAAAAAAAAAGAAAAYAAABgAAAGAAAAYAAABgAAD/+AAP/4AABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAADkAAAPwAAA/AAAAAAAAAAAAAAAAAAAAAAAAABgAAAGAAAAYAAABgAAAGAAAAYAAABgAAAGAAAAYAAAAAAAAAAAAAAAAAAAAAAADgAAAOAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAA4AAA/gAA/+AA//AA//AAP/AAA/AAADAAAAAAAAAAAAAAAAAAA//gAP//gB///AHgA8A8AB4DgADgOAAOA4AA4DgADgPAAeAeADwB///AD//4AD/+AAAAAAAAAAAAAAAA4AAAHgAAAcAAADwAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAAAAAYAeADgD4AeAfAD4DwAfgOAD+A4Ae4DgDzgOAeOA4Dw4DweDgH/wOAP+A4AfwDgAAAAAAAAAAAAIAOAA4A4ADwDggHAOHgOA48A4DnwDgO/AOA7uA4D84HgPh/8A8H/gDgH8AAACAAAAAAAAAAAAAHgAAB+AAA/4AAP7gAD+OAA/g4AP4DgA+AOADAA4AAB/+AAH/4AAf/gAADgAAAOAAAAAAAAAAAAAAAAD4cAP/h4A/+HwDw4HgOHAOA4cA4DhwDgOHAOA4cA4Dh4HAOD58A4H/gAAP8AAAGAAAAAAAAAAAAAAAAD/+AAf/8AD//4AePDwDw4HgOHAOA4cA4DhwDgOHAOA4cB4Bw8PAHD/8AIH/gAAH4AAAAAAAAAADgAAAOAAAA4AAYDgAHgOAD+A4B/wDgf4AOP+AA7/AAD/gAAP4AAA8AAAAAAAAAAAAAAAAAAeH8AD+/4Af//wDz8HgOHgOA4OA4Dg4DgODgOA4eA4Dz8HgH//8AP7/gAeH8AAAAAAAAAAAAAAAA+AAAH+AgB/8HAHh4cA8Dg4DgODgOAcOA4Bw4DgODgPA4eAeHDwB///AD//4AD/+AAAAAAAAAAAAAAAAAAAAAAAAAAODgAA4OAADg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAABwA5AHAD8AcAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAB8AAAP4AAB5wAAPDgAB4HAAHAOAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAABjAAAGMAAAYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAEAAcA4AB4HAADw4AADnAAAH4AAAPAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAB8AAAHgAAA4AAADgDzgOA/OA4D84DgeAAPHwAAf+AAA/wAAB8AAAAAAAAAAAAAAAAAAD+AAB/+AAP/8AB4B4AOABwBwADgHB8OA4P4cDhxxwMGDDAwYMMDBgwwOHHHA4f4cDh/xwHAHCAcAMAA8AwAB8PAAD/4AAD/AAAAAAAAAAAAAACAAAB4AAB/gAA/8AAf+AAP/wAH/nAA/gcADwBwAPwHAA/4cAA/9wAAf/AAAP/AAAD/gAAB+AAAA4AAAAAAAAAAAAAAD///gP//+A///4DgcDgOBwOA4HA4DgcDgOBwOA4HA4Dg8DgPHwOAf/h4A///AB8f4AAAfAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AHgPAAOA4AAAAAAAAAAAAAAAP//+A///4D///gOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOA8AB4BwAHAHwB8AP//gAP/4AAP+AAAAAAAAAAAAAAAA///4D///gP//+A4HA4DgcDgOBwOA4HA4DgcDgOBwOA4HA4DgcDgOBgOA4AA4AAAAAAAAAAAAAAD///gP//+A///4DgcAAOBwAA4HAADgcAAOBwAA4HAADgcAAOAwAA4AAAAAAAAAf+AAD/+AA//+ADwB4AeADwDwAHgOAAOA4AA4DgADgOAAOA4AA4DgMDgPAweAcDBwB8MfADw/4AHD/AAAPwAAAAAAAAAAAAAAAP//+A///4D///gABwAAAHAAAAcAAABwAAAHAAAAcAAABwAAAHAAAAcAAABwAA///4D///gP//+AAAAAAAAAAAAAAAAAAAD///gP//+A///4AAAAAAAAAAAADgAAAPAAAA+AAAA4AAADgAAAOAAAA4AAAHgP//8A///wD//8AAAAAAAAAAAAAAAAAAAA///4D///gP//+AAHAAAA+AAAP8AAB54AAPDwAB4HgAPAPAB4AfAPAA+A4AA4DAABgAAACAAAAAAAAAAP//+A///4D///gAAAOAAAA4AAADgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAP//+A///4D///gD+AAAD+AAAB+AAAB/AAAB/AAAB/AAAB+AAAH4AAB+AAA/gAAP4AAD+AAA/AAAfwAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAP//+A///4D///gHwAAAPwAAAPgAAAfgAAAfAAAAfAAAA/AAAA+AAAB+AAAB8A///4D///gP//+AAAAAAAAAAAP+AAD/+AAf/8AD4D4AeADwBwAHAOAAOA4AA4DgADgOAAOA4AA4DgADgOAAOAcABwB4APAD4D4AH//AAP/4AAP+AAAAAAAAAAAP//+A///4D///gOAcAA4BwADgHAAOAcAA4BwADgHAAOAcAA4DgAD4eAAH/wAAP+AAAPgAAAAAAAA/4AAP/4AB//wAPgPgB4APAHAAcA4AA4DgADgOAAOA4AA4DgADgOAAOA4AO4BwA/AHgB8APgPwAf//gA//uAA/4QAAAAAAAAAA///4D///gP//+A4BwADgHAAOAcAA4BwADgHAAOAcAA4B8ADgP8APh/8Af/H4A/4HgA+AGAAAAAAAAAAAABgAHwHAA/g+AH/A8A8cBwDg4DgODgOA4OA4DgcDgOBwOA4HA4DwODgHg4cAPh/wAcH+AAwPwAAAAADgAAAOAAAA4AAADgAAAOAAAA4AAAD///gP//+A///4DgAAAOAAAA4AAADgAAAOAAAA4AAADgAAAAAAAAAAAAAAAAAP//AA///AD//+AAAB8AAABwAAADgAAAOAAAA4AAADgAAAOAAAA4AAAHgAAA8A///gD//8AP//gAAAAAAAAAAIAAAA8AAAD+AAAH/AAAD/wAAB/4AAA/8AAAf4AAAPgAAB+AAA/4AAf+AAP/AAH/gAD/wAAP4AAA4AAAAAAAAPAAAA/gAAD/4AAA/+AAAf/AAAH/gAAB+AAAf4AAf/AAf/AAP/gAD/gAAPwAAA/4AAA/+AAAf/AAAH/wAAB/gAAB+AAB/4AA/+AA/+AA/+AAD/AAAPAAAAgAAAAAAAAMAAGA4AA4D4APgHwB8APwfAAPn4AAf+AAAfwAAB/AAAf+AAD4+AA/B8AHwB8A+AD4DgADgMAAGAwAAADwAAAPwAAAPwAAAfgAAAfgAAAf/4AAf/gAH/+AB+AAAPwAAD8AAA/AAADwAAAMAAAAgAAAAAAAAMAACA4AA4DgAPgOAD+A4Af4DgH7gOB+OA4Pw4Dj8DgO/AOA/4A4D+ADgPgAOA4AA4DAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////gAAAOAAAA4AAADAAAAAAAAAAAAAAAAAAAAAAA4AAAD+AAAP/gAAH/4AAB/+AAAf+AAAH4AAABgAAAAAAAAADAAAAOAAAA4AAADgAAAP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAADgAAAcAAADgAAAcAAADgAAAcAAAB4AAADwAAADgAAAHAAAAOAAAAYAAAAAAAAAAAAAAAAAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAMAAAAwAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAHH8AA8/4AHzjgAcMOABxwYAHHBgAccOABxwwAHGHAAP/4AA//4AA//gAAAAAAAAAAAAAAAAAAA///4D///gP//+AA4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAAf/gADwPAAeAeABwA4AHADgAcAOABwA4AHADgAeAeAA8DwABwOAADAwAAAAAAAAAAAA/AAAP/AAD//AAPA8AB4B4AHADgAcAOABwA4AHADgAcAOAA4BwD///gP//+A///4AAAAAAAAAAAAAAAAPwAAD/wAAf/gAD2PAAeYeABxg4AHGDgAcYOABxg4AHGDgAeYeAA/jwAB+OAAD4wAABgAAAAAAAAAAABgAAAGAAAB//+Af//4D///gPcAAA5gAADGAAAMYAAAAAAAAAPwAAD/wMA//w4DwPHgeAePBwA4cHADhwcAOHBwA4cHADhwOAcPB///4H///Af//wAAAAAAAAAAAAAAAAAAD///gP//+AA//4ADgAAAcAAABwAAAHAAAAcAAABwAAAHgAAAP/+AAf/4AA//gAAAAAAAAAAAAAAMf/+A5//4Dn//gAAAAAAAAAAAAAAAAAAHAAAAfn///+f//+5///wAAAAAAAAAAAAAAAAAAP//+A///4D///gAAcAAAD8AAAf4AADzwAAeHgAHwPAAeAeABgA4AEABgAAAAAAAAAD///gP//+A///4AAAAAAAAAAAAAAAAAAAAf/+AB//4AH//gAOAAABwAAAHAAAAcAAABwAAAHgAAAP/+AA//4AB//gAOAAABwAAAHAAAAcAAABwAAAHgAAAf/+AA//4AA//gAAAAAAAAAAAAAAAf/+AB//4AD//gAOAAABwAAAHAAAAcAAABwAAAHAAAAeAAAA//4AB//gAD/+AAAAAAAAAAAAAAAAD8AAA/8AAH/4AA8DwAHgHgAcAOABwA4AHADgAcAOABwA4AHgHgAPh8AAf/gAA/8AAA/AAAAAAAAAAAAAAAAB///8H///wf///A4BwAHADgAcAOABwA4AHADgAcAOAB4B4ADwPAAP/8AAf/AAAf4AAAAAAAAAAAAPwAAD/wAA//wADwPAAeAeABwA4AHADgAcAOABwA4AHADgAOAcAB///8H///wf///AAAAAAAAAAAAAAAAAAAH//gAf/+AB//4ADwAAAcAAABwAAAHAAAAcAAAAAAAAAAMAAHw4AA/jwAH+HgAcYOABxw4AHHDgAcMOABw44AHjjgAPH+AA8fwAAw+AAAAAABgAAAGAAAAcAAAf//wB///AH//+ABgA4AGADgAYAOABgA4AAAAAAAAAAAAAAAH/AAAf/wAB//wAAB/AAAAeAAAA4AAADgAAAOAAAA4AAADgAAAcAB//4AH//gAf/+AAAAAAAAAAAAAAABwAAAH4AAAf8AAAP8AAAH+AAAD+AAAD4AAA/gAAf8AAP+AAH/AAAfgAABwAAAAAAAAAAAABwAAAH8AAAf+AAAP/gAAD/gAAB+AAAf4AAP8AAP+AAB/AAAH4AAAf8AAAP+AAAD/gAAB+AAAf4AAf/AAP/AAB/gAAHgAAAQAAABAAIAHADgAeAeAA8HwAB8+AAD/gAAD8AAAPwAAD/gAAfPgADwfAAeAeABwA4AEAAgAAAAABAAAAHgAAAfwAAA/wAAAf4BwAP4/AAP/8AAP+AAD/AAB/wAA/4AAP8AAB+AAAHAAAAQAAAAAAIAHADgAcAeABwD4AHA/gAcHuABx84AHPDgAf4OAB/A4AHwDgAeAOABgA4AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAH4Af//////n//AAAA4AAADgAAAAAAAAAAAAAAAAAP//+A///4D///gAAAAAAAAAAAAAAAAAAA4AAADgAAAOAAAA//5/9////wAH4AAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAeAAAD4AAAOAAAA4AAADgAAAHAAAAcAAAA4AAADgAAAOAAAD4AAAPAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 32, atob("BgkMGhEZEgYMDAwQCAwICxILEBAREBEOEREJCREVEQ8ZEhEUExAOFBQHDREPGBMUERQSEhEUERsREBIMCwwTEg4QERAREQoREQcHDgcYEREREQoPDBEPFg8PDwwIDBMc"), 28+(scale<<8)+(1<<16));
+ return this;
+};
+
+var imgLock = {
+ width : 16, height : 16, bpp : 1,
+ transparent : 0,
+ buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
+};
+
+var imgSteps = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA=="))
+};
+
+var imgBattery = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA"))
+};
+
+var imgBpm = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA"))
+};
+
+var imgTemperature = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA=="))
+};
+
+var imgWind = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A=="))
+};
+
+var imgTimer = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA=="))
+};
+
+var imgCharging = {
+ width : 24, height : 24, bpp : 1,
+ transparent : 1,
+ buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
+};
+
+
+
+/*
+ * Draw timeout
+ */
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+/*
+ * Helper
+ */
+function getSteps() {
+ try{
+ if (WIDGETS.wpedom !== undefined) {
+ return WIDGETS.wpedom.getSteps();
+ } else if (WIDGETS.activepedom !== undefined) {
+ return WIDGETS.activepedom.getSteps();
+ } else {
+ return Bangle.getHealthStatus("day").steps;
+ }
+ } catch(ex) {
+ // In case we failed, we can only show 0 steps.
+ }
+
+ return 0;
+}
+
+
+function getWeather(){
+ var weatherJson;
+
+ try {
+ weatherJson = storage.readJSON('weather.json');
+ var weather = weatherJson.weather;
+
+ // Temperature
+ weather.temp = locale.temp(weather.temp-273.15);
+
+ // Humidity
+ weather.hum = weather.hum + "%";
+
+ // Wind
+ const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
+ weather.wind = Math.round(wind[1]) + " km/h";
+
+ return weather
+
+ } catch(ex) {
+ // Return default
+ }
+
+ return {
+ temp: "? °C",
+ hum: "-",
+ txt: "-",
+ wind: "? km/h",
+ wdir: "-",
+ wrose: "-"
+ };
+}
+
+function isAlarmEnabled(){
+ try{
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ if(alarmObj===undefined || !alarmObj.on){
+ return false;
+ }
+
+ return true;
+
+ } catch(ex){ }
+ return false;
+}
+
+function getAlarmMinutes(){
+ if(!isAlarmEnabled()){
+ return -1;
+ }
+
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
+}
+
+function increaseAlarm(){
+ try{
+ var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, {
+ timer : (minutes+5)*60*1000,
+ });
+ alarm.reload();
+ } catch(ex){ }
+}
+
+function decreaseAlarm(){
+ try{
+ var minutes = getAlarmMinutes();
+ minutes -= 5;
+
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, undefined);
+
+ if(minutes > 0){
+ alarm.setAlarm(TIMER_IDX, {
+ timer : minutes*60*1000,
+ });
+ }
+
+ alarm.reload();
+ } catch(ex){ }
+}
+
+
+/*
+ * D R A W
+ */
+function draw() {
+ // queue draw in one minute
+ queueDraw();
+
+ // Set info
+ var showInfo = settings.showInfo;
+ if(isAlarmEnabled()){
+ showInfo = 100;
+ }
+
+ if(Bangle.isCharging()){
+ showInfo = 101;
+ }
+
+
+ // Draw background
+ var yOffset = settings.fullscreen ? 0 : 10;
+ var y = H/5*2 + yOffset;
+ g.reset().clearRect(0,0,W,W);
+ g.setColor(g.theme.fg);
+ g.fillRect(0,y,W,H);
+
+ // Draw date
+ y -= settings.fullscreen ? 5 : 0;
+ var date = new Date();
+ g.setColor(g.theme.fg);
+ g.setFontAlign(1,1);
+ g.setMediumFont();
+ var dateStr = date.getDate();
+ dateStr = ("0" + dateStr).substr(-2);
+ g.drawString(dateStr, W/2-1, y+4);
+
+ g.setSmallFont();
+ g.setFontAlign(-1,1);
+ g.drawString(locale.dow(date, true), W/2 + 10, y-23);
+ g.drawString(locale.month(date, 1), W/2 + 10, y+1);
+
+ // Draw time
+ g.setColor(g.theme.bg);
+ g.setFontAlign(0,-1);
+ var timeStr = locale.time(date,1);
+ y += settings.fullscreen ? 20 : 10;
+
+ if(showInfo == 0){
+ y += 8;
+ g.setLargeFont();
+ } else {
+ g.setMediumFont();
+ }
+
+ g.drawString(timeStr, W/2, y);
+
+ // Draw info or timer
+ y += H/5*2-5;
+ g.setFontAlign(0,0);
+ if(showInfo > 0){
+ g.setSmallFont();
+
+ var infoStr = "";
+ var infoImg;
+ if(showInfo == 100){
+ infoStr = getAlarmMinutes() + " min.";
+ infoImg = imgTimer;
+ } else if(showInfo == 101){
+ infoStr = E.getBattery() + "%";
+ infoImg = imgCharging;
+ } else if (showInfo == 1){
+ infoStr = E.getBattery() + "%";
+ infoImg = imgBattery;
+ } else if (showInfo == 2){
+ infoStr = getSteps()
+ infoStr = Math.round(infoStr/100) / 10; // This ensures that we do not show e.g. 15.0k and 15k instead
+ infoStr = infoStr + "k";
+ infoImg = imgSteps;
+ } else if (showInfo == 3){
+ infoStr = Math.round(Bangle.getHealthStatus("day").bpm) + " bpm";
+ infoImg = imgBpm;
+ } else if (showInfo == 4){
+ var weather = getWeather();
+ infoStr = weather.temp;
+ infoImg = imgTemperature;
+ } else if (showInfo == 5){
+ var weather = getWeather();
+ infoStr = weather.wind;
+ infoImg = imgWind;
+ }
+
+ var imgWidth = 0;
+ if(infoImg !== undefined){
+ imgWidth = infoImg.width;
+ var strWidth = g.stringWidth(infoStr);
+ g.drawImage(infoImg, W/2 - strWidth/2 - infoImg.width/2 - 5, y - infoImg.height/2);
+ }
+ g.drawString(infoStr, W/2 + imgWidth/2, y+3);
+ }
+
+ // Draw lock
+ if(settings.showLock && Bangle.isLocked()){
+ g.setColor(g.theme.fg);
+ g.drawImage(imgLock, W-16, 2);
+ }
+
+ // Draw widgets if not fullscreen
+ if(settings.fullscreen){
+ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+ } else {
+ Bangle.drawWidgets();
+ }
+}
+
+Bangle.loadWidgets();
+
+// Clear the screen once, at startup and set the correct theme.
+var bgOrig = g.theme.bg
+var fgOrig = g.theme.fg
+g.setTheme({bg:fgOrig,fg:bgOrig}).clear();
+draw();
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+Bangle.on('lock', function(isLocked) {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ draw();
+});
+
+Bangle.on('charging',function(charging) {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ draw();
+});
+
+Bangle.on('touch', function(btn, e){
+ var left = parseInt(g.getWidth() * 0.2);
+ var right = g.getWidth() - left;
+ var upper = parseInt(g.getHeight() * 0.2);
+ var lower = g.getHeight() - upper;
+
+ var is_left = e.x < left;
+ var is_right = e.x > right;
+ var is_upper = e.y < upper;
+ var is_lower = e.y > lower;
+
+ if(is_upper){
+ Bangle.buzz(40, 0.6);
+ increaseAlarm();
+ draw();
+ }
+
+ if(is_lower){
+ Bangle.buzz(40, 0.6);
+ decreaseAlarm();
+ draw();
+ }
+
+ var maxInfo = 6;
+ if(is_right){
+ Bangle.buzz(40, 0.6);
+ settings.showInfo = (settings.showInfo+1) % maxInfo;
+ storage.write(SETTINGS_FILE, settings);
+ draw();
+ }
+
+ if(is_left){
+ Bangle.buzz(40, 0.6);
+ settings.showInfo = settings.showInfo-1;
+ settings.showInfo = settings.showInfo < 0 ? maxInfo-1 : settings.showInfo;
+ storage.write(SETTINGS_FILE, settings);
+ draw();
+ }
+});
+
+
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
diff --git a/apps/bwclk/app.png b/apps/bwclk/app.png
new file mode 100644
index 000000000..5073f0ed0
Binary files /dev/null and b/apps/bwclk/app.png differ
diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json
new file mode 100644
index 000000000..babaa37bf
--- /dev/null
+++ b/apps/bwclk/metadata.json
@@ -0,0 +1,18 @@
+{
+ "id": "bwclk",
+ "name": "BlackWhite Clock",
+ "version": "0.05",
+ "description": "Black and white clock.",
+ "readme": "README.md",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"bwclk.app.js","url":"app.js"},
+ {"name":"bwclk.img","url":"app-icon.js","evaluate":true},
+ {"name":"bwclk.settings.js","url":"settings.js"}
+ ]
+}
diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png
new file mode 100644
index 000000000..02ef7a704
Binary files /dev/null and b/apps/bwclk/screenshot.png differ
diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png
new file mode 100644
index 000000000..2f31c8b3c
Binary files /dev/null and b/apps/bwclk/screenshot_2.png differ
diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js
new file mode 100644
index 000000000..82d1cad0b
--- /dev/null
+++ b/apps/bwclk/settings.js
@@ -0,0 +1,40 @@
+(function(back) {
+ const SETTINGS_FILE = "bwclk.setting.json";
+
+ // initialize with default settings...
+ const storage = require('Storage')
+ let settings = {
+ fullscreen: false,
+ showLock: true,
+ };
+ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+ for (const key in saved_settings) {
+ settings[key] = saved_settings[key]
+ }
+
+ function save() {
+ storage.write(SETTINGS_FILE, settings)
+ }
+
+
+ E.showMenu({
+ '': { 'title': 'BlackWhite Clock' },
+ '< Back': back,
+ 'Fullscreen': {
+ value: settings.fullscreen,
+ format: () => (settings.fullscreen ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.fullscreen = !settings.fullscreen;
+ save();
+ },
+ },
+ 'Show Lock': {
+ value: settings.showLock,
+ format: () => (settings.showLock ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.showLock = !settings.showLock;
+ save();
+ },
+ }
+ });
+ })
diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog
index 4bb7838ac..d1adafc4c 100644
--- a/apps/compass/ChangeLog
+++ b/apps/compass/ChangeLog
@@ -3,3 +3,4 @@
0.03: Eliminate flickering
0.04: Fix for Bangle.js 2 and themes
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
+0.06: Add button for force compass calibration
diff --git a/apps/compass/README.md b/apps/compass/README.md
new file mode 100644
index 000000000..d60192f73
--- /dev/null
+++ b/apps/compass/README.md
@@ -0,0 +1,29 @@
+# Compass
+
+This app uses Bangle.js's built-in magnetometer as a compass.
+
+## Usage
+
+Hold your Bangle.js **face up** (so the display is parallel to the ground),
+and the red arrow will point north, with the heading in degrees printed at
+the top of the screen.
+
+This compass app does not include tilt compensation - so much like a real
+compass you should always keep it face up when taking a reading.
+
+The first time you run the compass after your Bangle has booted (or if
+you move to an area with a substantially different magnetic field) you will
+need to recalibrate your compass (even if a heading is shown).
+
+## Calibration
+
+Press the button next to the `RESET` label on the screen. The North/South marker
+will disappear and a message will appear asking you to rotate the watch 360 degrees.
+
+* Hold the watch face up, so the display is parallel to the ground
+* Rotate it around slowly, all 360 degrees (with the display still parallel to the ground)
+* The `Uncalibrated` message will disappear before you have finished rotating the full 360 degrees - but you should still complete the full rotation in order for the compass to work properly.
+
+Once you've rotated the full 360 degrees your compass should now work fine,
+and calibration is stored between runs of the app. However if you go near
+to a strong magnet you may still need to recalibrate.
diff --git a/apps/compass/compass.js b/apps/compass/compass.js
index 65ad83c4f..4730111ac 100644
--- a/apps/compass/compass.js
+++ b/apps/compass/compass.js
@@ -38,7 +38,7 @@ Bangle.on('mag', function(m) {
if (!wasUncalibrated) {
g.clearRect(0,24,W,48);
g.setFontAlign(0,-1).setFont("6x8");
- g.drawString("Uncalibrated\nturn 360° around",M,24+4);
+ g.drawString(/*LANG*/"Uncalibrated\nturn 360° around",M,24+4);
wasUncalibrated = true;
}
} else {
@@ -64,7 +64,12 @@ Bangle.on('mag', function(m) {
oldHeading = m.heading;
});
-g.clear();
+g.clear(1);
+g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"RESET", g.getWidth()-5, g.getHeight()/2);
+setWatch(function() {
+ Bangle.resetCompass();
+}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
+
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setCompassPower(1);
diff --git a/apps/compass/metadata.json b/apps/compass/metadata.json
index 318d90c86..3e3b37f72 100644
--- a/apps/compass/metadata.json
+++ b/apps/compass/metadata.json
@@ -1,12 +1,13 @@
{
"id": "compass",
"name": "Compass",
- "version": "0.05",
+ "version": "0.06",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],
"tags": "tool,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
"storage": [
{"name":"compass.app.js","url":"compass.js"},
{"name":"compass.img","url":"compass-icon.js","evaluate":true}
diff --git a/apps/fuzzyw/ChangeLog b/apps/fuzzyw/ChangeLog
new file mode 100644
index 000000000..7b83706bf
--- /dev/null
+++ b/apps/fuzzyw/ChangeLog
@@ -0,0 +1 @@
+0.01: First release
diff --git a/apps/fuzzyw/README.md b/apps/fuzzyw/README.md
new file mode 100644
index 000000000..906eb167b
--- /dev/null
+++ b/apps/fuzzyw/README.md
@@ -0,0 +1,26 @@
+# Fuzzy Text Clock
+
+An imprecise clock for when you're not in a rush.
+
+This clock is a remake of one of my favourite Pebble watchfaces, Fuzzy Text International. I use this watch for weekends and holidays, when 'within 5 minutes of the actual time' is close enough!
+
+By default it will use the language set on the watch, go to settings to pick:
+* en_GB - English
+* en_US - American
+* es_ES - Spanish
+* fr_FR - French
+* no_NO - Norwegian
+* sv_SE - Swedish
+* de_DE - German
+
+Most translations are taken from the original Fuzzy Text International code.
+
+## TODO
+* Bold hour word (as the pebble version has)
+* Animation when changing time?
+
+## References
+Based on Pebble app Fuzzy Text International: https://github.com/hallettj/Fuzzy-Text-International
+
+
+
diff --git a/apps/fuzzyw/fuzzy_strings.json b/apps/fuzzyw/fuzzy_strings.json
new file mode 100644
index 000000000..55f443813
--- /dev/null
+++ b/apps/fuzzyw/fuzzy_strings.json
@@ -0,0 +1,186 @@
+{
+ "en_GB":{
+ "hours":[
+ "midnight", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten", "eleven",
+ "twelve", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten", "eleven"
+ ],
+ "minutes":[
+ "*$1 o'clock",
+ "five past *$1",
+ "ten past *$1",
+ "quarter past *$1",
+ "twenty past *$1",
+ "twenty five past *$1",
+ "half past *$1",
+ "twenty five to *$2",
+ "twenty to *$2",
+ "quarter to *$2",
+ "ten to *$2",
+ "five to *$2"
+ ],
+ "text_scale":3.5
+ },
+ "en_US":{
+ "hours":[
+ "midnight", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten", "eleven",
+ "twelve", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten", "eleven"
+ ],
+ "minutes":[
+ "*$1 o'clock",
+ "five after *$1",
+ "ten after *$1",
+ "quarter after *$1",
+ "twenty after *$1",
+ "twenty five after *$1",
+ "half past *$1",
+ "twenty five to *$2",
+ "twenty to *$2",
+ "quarter to *$2",
+ "ten to *$2",
+ "five to *$2"
+ ],
+ "text_scale":3.5
+ },
+ "es_ES":{
+ "hours":[
+ "doce", "una", "dos", "tres", "cuatro", "cinco",
+ "seis", "siete", "ocho", "nueve", "diez", "once",
+ "doce", "una", "dos", "tres", "cuatro", "cinco",
+ "seis", "siete", "ocho", "nueve", "diez", "once"
+ ],
+ "minutes":[
+ "*$1 en punto",
+ "*$1 y cinco",
+ "*$1 y diez",
+ "*$1 y cuarto",
+ "*$1 y veinte",
+ "*$1 y veinti- cinco",
+ "*$1 y media",
+ "*$2 menos veinti- cinco",
+ "*$2 menos veinte",
+ "*$2 menos cuarto",
+ "*$2 menos diez",
+ "*$2 menos cinco"
+ ],
+ "text_scale":3.5
+ },
+ "fr_FR":{
+ "hours":[
+ "douze", "une", "deux", "trois", "quatre", "cinq",
+ "six", "sept", "huit", "neuf", "dix", "onze",
+ "douze", "une", "deux", "trois", "quatre", "cinq",
+ "six", "sept", "huit", "neuf", "dix", "onze"
+ ],
+ "minutes":[
+ "*$1 heures",
+ "*$1 heures cinq",
+ "*$1 heures dix",
+ "*$1 heures et quart",
+ "*$1 heures vingt",
+ "*$1 heures vingt- cinq",
+ "*$1 heures et demie",
+ "*$2 moins vingt- cinq",
+ "*$2 heures moins vingt",
+ "*$2 moins le quart",
+ "*$2 heures moins dix",
+ "*$2 heures moins cinq"
+ ],
+ "text_scale":3.5
+ },
+ "no_NB":{
+ "hours":[
+ "tolv", "ett", "to", "tre", "fire", "fem",
+ "seks", "sju", "åtte", "ni", "ti", "elleve",
+ "tolv", "ett", "to", "tre", "fire", "fem",
+ "seks", "sju", "åtte", "ni", "ti", "elleve"
+ ],
+ "minutes":[
+ "klokka er *$1",
+ "fem over *$1",
+ "ti over *$1",
+ "kvart over *$1",
+ "ti på halv *$2",
+ "fem på halv *$2",
+ "halv *$2",
+ "fem over halv *$2",
+ "ti over halv *$2",
+ "kvart på *$2",
+ "ti på *$2",
+ "fem på *$2"
+ ],
+ "text_scale":3.5
+ },
+ "nn_NO":{
+ "hours":[
+ "tolv", "eitt", "to", "tre", "fire", "fem",
+ "seks", "sju", "åtte", "ni", "ti", "elleve",
+ "tolv", "eitt", "to", "tre", "fire", "fem",
+ "seks", "sju", "åtte", "ni", "ti", "elleve"
+ ],
+ "minutes":[
+ "klokka er *$1",
+ "fem over *$1",
+ "ti over *$1",
+ "kvart over *$1",
+ "ti på halv *$2",
+ "fem på halv *$2",
+ "halv *$2",
+ "fem over halv *$2",
+ "ti over halv *$2",
+ "kvart på *$2",
+ "ti på *$2",
+ "fem på *$2"
+ ],
+ "text_scale":3.5
+ },
+ "sv_SE":{
+ "hours":[
+ "tolv", "ett", "två", "tre", "fyra", "fem",
+ "sex", "sju", "åtta", "nio", "tio", "elva",
+ "tolv", "ett", "två", "tre", "fyra", "fem",
+ "sex", "sju", "åtta", "nio", "tio", "elva"
+ ],
+ "minutes":[
+ "*$1",
+ "fem över *$1",
+ "tio över *$1",
+ "kvart över *$1",
+ "tjugo över *$1",
+ "fem i halv *$2",
+ "halv *$2",
+ "fem över halv *$2",
+ "tjugo i *$2",
+ "kvart i *$2",
+ "tio i *$2",
+ "fem i *$2"
+ ],
+ "text_scale":3.5
+ },
+ "de_DE":{
+ "hours":[
+ "zwölf", "eins", "zwei", "drei", "vier", "fünf",
+ "sechs", "sieben", "acht", "neun", "zehn", "elf",
+ "zwölf", "eins", "zwei", "drei", "vier", "fünf",
+ "sechs", "sieben", "acht", "neun", "zehn", "elf"
+ ],
+ "minutes":[
+ "*$1 uhr",
+ "fünf nach *$1",
+ "zehn nach *$1",
+ "viertel nach *$1",
+ "zwanzig nach *$1",
+ "fünf for halb *$2",
+ "halb *$2",
+ "fünf nach halb *$2",
+ "zwanzig vor *$2",
+ "viertel vor *$2",
+ "zehn vor *$2",
+ "fünf vor *$2"
+ ],
+ "text_scale":3.5
+ }
+}
diff --git a/apps/fuzzyw/fuzzyw-dark.png b/apps/fuzzyw/fuzzyw-dark.png
new file mode 100644
index 000000000..88220e5c7
Binary files /dev/null and b/apps/fuzzyw/fuzzyw-dark.png differ
diff --git a/apps/fuzzyw/fuzzyw-light.png b/apps/fuzzyw/fuzzyw-light.png
new file mode 100644
index 000000000..5383e08a4
Binary files /dev/null and b/apps/fuzzyw/fuzzyw-light.png differ
diff --git a/apps/fuzzyw/fuzzyw.app.js b/apps/fuzzyw/fuzzyw.app.js
new file mode 100644
index 000000000..07b5c4068
--- /dev/null
+++ b/apps/fuzzyw/fuzzyw.app.js
@@ -0,0 +1,75 @@
+// adapted from https://github.com/hallettj/Fuzzy-Text-International/
+const fuzzy_strings = require("Storage").readJSON("fuzzy_strings.json");
+
+const SETTINGS_FILE = "fuzzyw.settings.json";
+let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'language': 'System', 'alignment':'Centre'};
+
+if (settings.language == 'System') {
+ settings.language = require('locale').name;
+}
+
+let fuzzy_string = fuzzy_strings[settings.language];
+
+let timeout = 2.5*60;
+let drawTimeout;
+
+function queueDraw(seconds) {
+ let millisecs = seconds * 1000;
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, millisecs - (Date.now() % millisecs));
+}
+
+const h = g.getHeight();
+const w = g.getWidth();
+let align_mode = 0;
+let align_pos = w/2;
+if (settings.alignment =='Left') {
+ align_mode = -1;
+ align_pos = 0;
+} else if (settings.alignment == 'Right') {
+ align_mode = 1;
+ align_pos = w;
+}
+
+function getTimeString(date) {
+ let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300);
+ let hour = date.getHours() + Math.floor(segment/12);
+ f_string = fuzzy_string.minutes[segment % 12];
+ if (f_string.includes('$1')) {
+ f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 24]);
+ } else {
+ f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 24]);
+ }
+ return f_string;
+}
+
+function draw() {
+ let time_string = getTimeString(new Date()).replace('*', '');
+ // print(time_string);
+ g.setFont('Vector', (h-24*2)/fuzzy_string.text_scale);
+ g.setFontAlign(align_mode, 0);
+ g.clearRect(0, 24, w, h-24);
+ g.setColor(g.theme.fg);
+ g.drawString(g.wrapString(time_string, w).join("\n"), align_pos, h/2);
+ queueDraw(timeout);
+}
+
+g.clear();
+draw();
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+
+Bangle.setUI('clock');
+Bangle.loadWidgets();
+Bangle.drawWidgets();
diff --git a/apps/fuzzyw/fuzzyw.icon.js b/apps/fuzzyw/fuzzyw.icon.js
new file mode 100644
index 000000000..acc7e2fcf
--- /dev/null
+++ b/apps/fuzzyw/fuzzyw.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgP/ABX8oYFD+AFE8AFE8IXE8YFKwFCj08h4FBocenEHCIPDjk4CoIFBhlwAoeMuIFEuBSBAoOI+AFD4HxGoQFB+AFD4P4uYFC8P4gYFD/w7BAFEfApfEj+B/Ecg/Ah8A+EMg/Dw0YseHj/Dw/8sfHAoPH/lhDoIFBwFwj4FB40AvkPAoU8v4dCAoIdDw04FIMP4EOgFwh47Bj8EvEfw/DJwgFXABY"))
diff --git a/apps/fuzzyw/fuzzyw.png b/apps/fuzzyw/fuzzyw.png
new file mode 100644
index 000000000..afd0b0f76
Binary files /dev/null and b/apps/fuzzyw/fuzzyw.png differ
diff --git a/apps/fuzzyw/fuzzyw.settings.js b/apps/fuzzyw/fuzzyw.settings.js
new file mode 100644
index 000000000..00219accf
--- /dev/null
+++ b/apps/fuzzyw/fuzzyw.settings.js
@@ -0,0 +1,46 @@
+(function(back) {
+ const SETTINGS_FILE = "fuzzyw.settings.json";
+
+ var align_options = ['Left','Centre','Right'];
+ var language_options = ['System', 'en_GB', 'en_US', 'es_ES', 'fr_FR', 'no_NO', 'sv_SE', 'de_DE'];
+
+ // initialize with default settings...
+ let s = {'language': 'System', 'alignment': 'Centre'};
+
+ // ...and overwrite them with any saved values
+ // This way saved values are preserved if a new version adds more settings
+ const storage = require('Storage')
+ let settings = storage.readJSON(SETTINGS_FILE, 1) || s;
+ const saved = settings || {}
+ for (const key in saved) {
+ s[key] = saved[key]
+ }
+
+ function save() {
+ settings = s
+ storage.write(SETTINGS_FILE, settings)
+ }
+
+ E.showMenu({
+ '': { 'title': 'Fuzzy Text Clock' },
+ '< Back': back,
+ 'Language': {
+ value: 0 | language_options.indexOf(s.language),
+ min: 0, max: language_options.length - 1,
+ format: v => language_options[v],
+ onchange: v => {
+ s.language = language_options[v];
+ save();
+ }
+ },
+ 'Alignment': {
+ value: 0 | align_options.indexOf(s.alignment),
+ min: 0, max: align_options.length - 1,
+ format: v => align_options[v],
+ onchange: v => {
+ s.alignment = align_options[v];
+ save();
+ }
+ },
+ });
+})
diff --git a/apps/fuzzyw/metadata.json b/apps/fuzzyw/metadata.json
new file mode 100644
index 000000000..ebd20e49f
--- /dev/null
+++ b/apps/fuzzyw/metadata.json
@@ -0,0 +1,20 @@
+{
+ "id":"fuzzyw",
+ "name":"Fuzzy Text Clock",
+ "shortName": "Fuzzy Text",
+ "version": "0.01",
+ "description": "An imprecise clock for when you're not in a rush",
+ "readme": "README.md",
+ "icon":"fuzzyw.png",
+ "screenshots": [{"url":"fuzzyw-light.png"},{"url":"fuzzyw-dark.png"}],
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS", "BANGLEJS2"],
+ "allow_emulator": true,
+ "storage": [
+ {"name":"fuzzyw.app.js","url":"fuzzyw.app.js"},
+ {"name":"fuzzyw.settings.js","url":"fuzzyw.settings.js"},
+ {"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true},
+ {"name":"fuzzy_strings.json","url":"fuzzy_strings.json"}
+ ]
+}
diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog
index 9d43f6575..4935fe714 100644
--- a/apps/lcars/ChangeLog
+++ b/apps/lcars/ChangeLog
@@ -17,3 +17,4 @@
0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.19: Alarms can not go bigger than 100.
+0.20: Use alarm for alarm functionality instead of own implementation.
\ No newline at end of file
diff --git a/apps/lcars/README.md b/apps/lcars/README.md
index f979b2304..7024e8edf 100644
--- a/apps/lcars/README.md
+++ b/apps/lcars/README.md
@@ -3,7 +3,8 @@
A simple LCARS inspired clock.
Note: To display the steps, the wpedom app is required. To show weather data
such as temperature, humidity or window you BangleJS must be connected
-with Gadgetbride and the weather app must be installed.
+with Gadgetbride and the weather app must be installed. To use the timer
+the "sched" app must be installed on your device.
## Control
* Tap left / right to change between screens.
@@ -15,7 +16,7 @@ with Gadgetbride and the weather app must be installed.
* Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random and real images of planets.
- * Tap on top/bottom of screen 1 to activate an alarm.
+ * Tap on top/bottom of screen 1 to activate an alarm. Depends on widtmr.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
@@ -36,8 +37,9 @@ Access different screens via tap on the left/ right side of the screen


+## Creator
+- [David Peer](https://github.com/peerdavid)
## Contributors
-- [David Peer](https://github.com/peerdavid).
-- [Adam Schmalhofer](https://github.com/adamschmalhofer).
-- [Jon Warrington](https://github.com/BartokW).
+- [Adam Schmalhofer](https://github.com/adamschmalhofer)
+- [Jon Warrington](https://github.com/BartokW)
diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js
index 433d33427..577955d2e 100644
--- a/apps/lcars/lcars.app.js
+++ b/apps/lcars/lcars.app.js
@@ -1,6 +1,7 @@
+const TIMER_IDX = "lcars";
const SETTINGS_FILE = "lcars.setting.json";
const locale = require('locale');
-const storage = require('Storage')
+const storage = require('Storage');
let settings = {
alarm: -1,
dataRow1: "Steps",
@@ -124,11 +125,16 @@ Graphics.prototype.setFontAntonioLarge = function(scale) {
*/
var drawTimeout;
function queueDraw() {
+
+ // Faster updates during alarm to ensure that it is
+ // shown correctly...
+ var timeout = isAlarmEnabled() ? 10000 : 60000;
+
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
- }, 60000 - (Date.now() % 60000));
+ }, timeout - (Date.now() % timeout));
}
/**
@@ -238,6 +244,7 @@ function drawInfo(){
return;
}
+ g.setFontAlign(-1, -1, 0);
g.setFontAntonioMedium();
g.setColor(cOrange);
g.clearRect(120, 10, g.getWidth(), 75);
@@ -480,9 +487,6 @@ function draw(){
// Queue draw first to ensure that its called in one minute again.
queueDraw();
- // First handle alarm to show this correctly afterwards
- handleAlarm();
-
// Next draw the watch face
g.reset();
g.clearRect(0, 0, g.getWidth(), g.getHeight());
@@ -561,43 +565,57 @@ function getWeather(){
/*
* Handle alarm
*/
-function getCurrentTimeInMinutes(){
- return Math.floor(Date.now() / (1000*60));
-}
-
function isAlarmEnabled(){
- return settings.alarm >= 0;
+ try{
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ if(alarmObj===undefined || !alarmObj.on){
+ return false;
+ }
+
+ return true;
+
+ } catch(ex){ }
+ return false;
}
function getAlarmMinutes(){
- var currentTime = getCurrentTimeInMinutes();
- return settings.alarm - currentTime;
+ if(!isAlarmEnabled()){
+ return -1;
+ }
+
+ var alarm = require('sched');
+ var alarmObj = alarm.getAlarm(TIMER_IDX);
+ return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
-function handleAlarm(){
- if(!isAlarmEnabled()){
- return;
- }
+function increaseAlarm(){
+ try{
+ var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, {
+ timer : (minutes+5)*60*1000,
+ });
+ alarm.reload();
+ } catch(ex){ }
+}
- if(getAlarmMinutes() > 0){
- return;
- }
+function decreaseAlarm(){
+ try{
+ var minutes = getAlarmMinutes();
+ minutes -= 5;
- // Alarm
- var t = 300;
- Bangle.buzz(t, 1)
- .then(() => new Promise(resolve => setTimeout(resolve, t)))
- .then(() => Bangle.buzz(t, 1))
- .then(() => new Promise(resolve => setTimeout(resolve, t)))
- .then(() => Bangle.buzz(t, 1))
- .then(() => new Promise(resolve => setTimeout(resolve, t)))
- .then(() => Bangle.buzz(t, 1))
- .then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
- .then(() => {
- // Update alarm state to disabled
- settings.alarm = -1;
- storage.writeJSON(SETTINGS_FILE, settings);
- });
+ var alarm = require('sched')
+ alarm.setAlarm(TIMER_IDX, undefined);
+
+ if(minutes > 0){
+ alarm.setAlarm(TIMER_IDX, {
+ timer : minutes*60*1000,
+ });
+ }
+
+ alarm.reload();
+ } catch(ex){ }
}
@@ -625,27 +643,6 @@ Bangle.on('charging',function(charging) {
});
-function increaseAlarm(){
- if(isAlarmEnabled() && getAlarmMinutes() < 95){
- settings.alarm += 5;
- } else {
- settings.alarm = getCurrentTimeInMinutes() + 5;
- }
-
- storage.writeJSON(SETTINGS_FILE, settings);
-}
-
-
-function decreaseAlarm(){
- if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
- settings.alarm -= 5;
- } else {
- settings.alarm = -1;
- }
-
- storage.writeJSON(SETTINGS_FILE, settings);
-}
-
function feedback(){
Bangle.buzz(40, 0.3);
}
diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json
index 1335b8e1d..7155442f8 100644
--- a/apps/lcars/metadata.json
+++ b/apps/lcars/metadata.json
@@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
- "version":"0.19",
+ "version":"0.20",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",
diff --git a/apps/lightswitch/ChangeLog b/apps/lightswitch/ChangeLog
index 4210ccf03..2c6d2b5db 100644
--- a/apps/lightswitch/ChangeLog
+++ b/apps/lightswitch/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Add the option to enable touching the widget only on clock and settings.
0.03: Settings page now uses built-in min/max/wrap (fix #1607)
+0.04: Add masking widget input to other apps (using espruino/Espruino#2151), add a oversize option to increase the touch area.
diff --git a/apps/lightswitch/README.md b/apps/lightswitch/README.md
index d58de7ca4..67d070f5c 100644
--- a/apps/lightswitch/README.md
+++ b/apps/lightswitch/README.md
@@ -1,8 +1,11 @@
# Light Switch Widget
-Whis this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
+With this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
In addition it shows the lock status with the option to personalize the lock icon with a tiny image.
+All touch and drag inputs related to this widget are cached/masked to prevent actions in the active app.
+(See [espruino/Espruino#2151](https://github.com/espruino/Espruino/issues/2151) for more information.)
+
---
### Control
---
@@ -39,6 +42,9 @@ In addition it shows the lock status with the option to personalize the lock ico
* _clk+launch_ -> on all apps of the types _clock_ and _launch_
* _except apps_ -> on all apps of the types _clock_ and _launch_ and in the settings
* _always on_ -> always enabled when the widget is displayed
+* __Oversize__
+ _0px_ / _1px_ / _..._ / __20px__ / _..._ / _50px_
+ To make it easier to hit the widget, this value extends the touch area of the widget in all directions.
* __Drag Delay__
_off_ / _50ms_ / _100ms_ / _..._ / __500ms__ / _..._ / _1000ms_
Change the maximum delay between first touch and re-touch/drag to change the brightness or disable changing the brightness completely.
@@ -85,8 +91,6 @@ This images are stored in a seperate file _(lightswitch.images.json)_.
### Worth Mentioning
---
#### To do list
-* Catch the touch and draw input related to this widget to prevent actions in the active app.
- _(For now I have no idea how to achieve this, help is appreciated)_
* Manage images for the lock icon through a _Customize and Upload App_ page.
#### Requests, Bugs and Feedback
diff --git a/apps/lightswitch/metadata.json b/apps/lightswitch/metadata.json
index 9ac388eda..54dc8389f 100644
--- a/apps/lightswitch/metadata.json
+++ b/apps/lightswitch/metadata.json
@@ -2,7 +2,7 @@
"id": "lightswitch",
"name": "Light Switch Widget",
"shortName": "Light Switch",
- "version": "0.03",
+ "version": "0.04",
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
"icon": "images/app.png",
"screenshots": [
diff --git a/apps/lightswitch/settings.js b/apps/lightswitch/settings.js
index bebb16d15..5ac70bc28 100644
--- a/apps/lightswitch/settings.js
+++ b/apps/lightswitch/settings.js
@@ -6,7 +6,8 @@
var settings = Object.assign({
colors: "011",
image: "default",
- touchOn: "clock,launch",
+ touchOn: "always",
+ oversize: 20,
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
@@ -45,7 +46,7 @@
return {
value: entry.value.indexOf(settings[key]),
min : 0,
- max : entry.value.length-1,
+ max : entry.value.length - 1,
wrap : true,
format: v => entry.title ? entry.title[v] : entry.value[v],
onchange: function(v) {
@@ -57,11 +58,11 @@
// return entry for numerical value
return {
value: settings[key] * entry.factor,
- step: entry.step,
- format: v => v > 0 ? v + entry.unit : "off",
min : entry.min,
max : entry.max,
+ step: entry.step,
wrap : true,
+ format: v => v > 0 ? v + entry.unit : "off",
onchange: function(v) {
writeSetting(key, v / entry.factor, entry.drawWidgets);
},
@@ -96,6 +97,14 @@
value: ["", "clock", "clock,setting.app.js", "clock,launch", "clock,setting.app.js,launch", "always"],
drawWidgets: true
},
+ oversize: {
+ factor: 1,
+ unit: "px",
+ min: 0,
+ max: 50,
+ step: 1,
+ drawWidgets: true
+ },
dragDelay: {
factor: 1,
unit: "ms",
@@ -142,6 +151,7 @@
"Image": getEntry("image"),
"-- Control": 0,
"Touch": getEntry("touchOn"),
+ "Oversize": getEntry("oversize"),
"Drag Delay": getEntry("dragDelay"),
"Min Value": getEntry("minValue"),
"-- Unlock": 0,
diff --git a/apps/lightswitch/settings.json b/apps/lightswitch/settings.json
index 3d88e2282..176c3cea0 100644
--- a/apps/lightswitch/settings.json
+++ b/apps/lightswitch/settings.json
@@ -10,8 +10,8 @@
"101" -> magenta
* image: string //
- "default" ->
- "random" ->
+ "default" -> image nearest to the default lock
+ "random" -> a random image from all available
* touchOn: string // select when widget touch is active
"" -> only on default clock
@@ -19,6 +19,9 @@
"clock,launch" -> on all clocks and lanchers (default)
"always" -> always
+ * oversize: int // extends the touch area of the widget in px in all directions
+ 0 to 50, 20 as default
+
* dragDelay: int // drag listener reset time in ms
// time until a drag is needed to activate backlight changing mode
0 -> disabled
@@ -59,6 +62,7 @@
"colors": "011",
"image": "default",
"touchOn": "clock,launch",
+ "oversize": 20,
"dragDelay": 500,
"minValue": 0.1,
"unlockSide": "",
diff --git a/apps/lightswitch/widget.js b/apps/lightswitch/widget.js
index 119a114fe..829f75102 100644
--- a/apps/lightswitch/widget.js
+++ b/apps/lightswitch/widget.js
@@ -3,7 +3,8 @@
var settings = Object.assign({
colors: "011",
image: "default",
- touchOn: "clock,launch",
+ touchOn: "always",
+ oversize: 20,
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
@@ -162,6 +163,11 @@
// change brigthness value, skip write to storage while still touching
w.changeValue(value, event.b);
+ // masks this drag event by messing up the event handler
+ // see https://github.com/espruino/Espruino/issues/2151
+ Bangle.removeListener("drag", w.dragListener);
+ Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
+
// on touch release remove drag listener and reset drag status to indicate stopped drag action
if (!event.b) {
Bangle.removeListener("drag", w.dragListener);
@@ -184,14 +190,14 @@
if (w.dragStatus === "off") {
// check if inside widget area
- if (!(!w || cursor.x < w.x || cursor.x > w.x + w.width ||
- cursor.y < w.y || cursor.y > w.y + 23)) {
+ if (!(!w || cursor.x < w.x - w.oversize || cursor.x > w.x + w.width + w.oversize ||
+ cursor.y < w.y - w.oversize || cursor.y > w.y + 23 + w.oversize)) {
// first touch feedback
Bangle.buzz(25);
// check if drag is disabled
if (w.dragDelay) {
- // add drag listener
- Bangle.on("drag", w.dragListener);
+ // add drag listener at first position
+ Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
// set drag timeout
w.dragStatus = setTimeout((w) => {
// remove drag listener
@@ -204,6 +210,10 @@
}
// switch backlight
w.changeValue();
+ // masks this touch event by messing up the event handler
+ // see https://github.com/espruino/Espruino/issues/2151
+ Bangle.removeListener("touch", w.touchListener);
+ Bangle["#ontouch"] = [w.touchListener].concat(Bangle["#ontouch"]);
}
}
@@ -236,11 +246,11 @@
// add lock listener
Bangle.on("lock", w.draw);
- // add touch listener to control the light depending on settings
+ // add touch listener to control the light depending on settings at first position
if (w.touchOn === "always" || !global.__FILE__ ||
w.touchOn.includes(__FILE__) ||
w.touchOn.includes(require("Storage").readJSON(__FILE__.replace("app.js", "info")).type))
- Bangle.on("touch", w.touchListener);
+ Bangle["#ontouch"] = [w.touchListener].concat(Bangle["#ontouch"]);
// add tap listener to unlock and/or flash backlight
if (w.unlockSide || w.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
diff --git a/apps/locale/locale.html b/apps/locale/locale.html
index bac938ffa..6eb0d94ea 100644
--- a/apps/locale/locale.html
+++ b/apps/locale/locale.html
@@ -16,6 +16,7 @@
Then click
+