diff --git a/Bangle.js.svg b/Bangle.js.svg
new file mode 100644
index 000000000..90c908c9b
--- /dev/null
+++ b/Bangle.js.svg
@@ -0,0 +1,478 @@
+
+
diff --git a/README.md b/README.md
index 2d0b54a7d..877fe5e2c 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@ Bangle.js App Loader (and Apps)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
+**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
+submitting code to this repository you confirm that you are happy with it being MIT licensed,
+and that it is not licensed in another way that would make this impossible.
+
## How does it work?
* A list of apps is in `apps.json`
@@ -32,6 +36,7 @@ easily distinguish between file types, we use the following:
* `stuff.img` is an image
* `stuff.app.js` is JS code for applications
* `stuff.wid.js` is JS code for widgets
+* `stuff.settings.js` is JS code for the settings menu
* `stuff.boot.js` is JS code that automatically gets run at boot time
* `stuff.json` is used for JSON settings for an app
@@ -314,6 +319,48 @@ the data you require from Bangle.js.
See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
+### Adding configuration to the "Settings" menu
+
+Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings".
+To do so, the app needs to include a `settings.js` file, containing a single function
+that handles configuring the app.
+When the app settings are opened, this function is called with one
+argument, `back`: a callback to return to the settings menu.
+
+Example `settings.js`
+```js
+// make sure to enclose the function in parentheses
+(function(back) {
+ let settings = require('Storage').readJSON('app.settings.json',1)||{};
+ function save(key, value) {
+ settings[key] = value;
+ require('Storage').write('app.settings.json',settings);
+ }
+ const appMenu = {
+ '': {'title': 'App Settings'},
+ '< Back': back,
+ 'Monkeys': {
+ value: settings.monkeys||12,
+ onchange: (m) => {save('monkeys', m)}
+ }
+ };
+ E.showMenu(appMenu)
+})
+```
+In this example the app needs to add both `app.settings.js` and
+`app.settings.json` to `apps.json`:
+```json
+ { "id": "app",
+ ...
+ "storage": [
+ ...
+ {"name":"app.settings.js","url":"settings.js"},
+ {"name":"app.settings.json","content":"{}"}
+ ]
+ },
+```
+That way removing the app also cleans up `app.settings.json`.
+
## Coding hints
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
diff --git a/apps.json b/apps.json
index 2f057b040..2bbc7dd33 100644
--- a/apps.json
+++ b/apps.json
@@ -27,7 +27,7 @@
{ "id": "daysl",
"name": "Days left",
"icon": "app.png",
- "version":"0.02",
+ "version":"0.03",
"description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.",
"tags": "",
"allow_emulator":false,
@@ -78,24 +78,26 @@
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
- "version":"0.04",
+ "version":"0.05",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
"storage": [
{"name":"welcome.js","url":"welcome.js"},
{"name":"welcome.app.js","url":"app.js"},
+ {"name":"welcome.settings.js","url":"settings.js"},
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "gbridge",
"name": "Gadgetbridge",
"icon": "app.png",
- "version":"0.06",
+ "version":"0.07",
"description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget",
+ "type":"widget",
"storage": [
- {"name":"gbridge.app.js","url":"app.js"},
+ {"name":"gbridge.settings.js","url":"settings.js"},
{"name":"gbridge.img","url":"app-icon.js","evaluate":true},
{"name":"gbridge.wid.js","url":"widget.js"}
]
@@ -117,11 +119,12 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
- "version":"0.08",
+ "version":"0.10",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
{"name":"setting.app.js","url":"settings.js"},
+ {"name":"setting.boot.js","url":"boot.js"},
{"name":"setting.json","url":"settings-default.json","evaluate":true},
{"name":"setting.img","url":"settings-icon.js","evaluate":true}
],
@@ -159,7 +162,7 @@
{ "id": "aclock",
"name": "Analog Clock",
"icon": "clock-analog.png",
- "version":"0.10",
+ "version": "0.11",
"description": "An Analog Clock",
"tags": "clock",
"type":"clock",
@@ -336,13 +339,16 @@
},
{ "id": "widbatpc",
"name": "Battery Level Widget (with percentage)",
+ "shortName": "Battery Widget",
"icon": "widget.png",
- "version":"0.06",
+ "version":"0.08",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"tags": "widget,battery",
"type":"widget",
"storage": [
- {"name":"widbatpc.wid.js","url":"widget.js"}
+ {"name":"widbatpc.wid.js","url":"widget.js"},
+ {"name":"widbatpc.settings.js","url":"settings.js"},
+ {"name":"widbatpc.settings.json","content": "{}"}
]
},
{ "id": "widbt",
@@ -864,6 +870,19 @@
{"name":"torch.img","url":"app-icon.js","evaluate":true}
]
},
+ { "id": "wohrm",
+ "name": "Workout HRM",
+ "icon": "app.png",
+ "version":"0.06",
+ "description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
+ "tags": "hrm,workout",
+ "type": "app",
+ "allow_emulator":true,
+ "storage": [
+ {"name":"wohrm.app.js","url":"app.js"},
+ {"name":"wohrm.img","url":"app-icon.js","evaluate":true}
+ ]
+ },
{ "id": "widid",
"name": "Bluetooth ID Widget",
"icon": "widget.png",
@@ -1014,7 +1033,7 @@
"name": "Touch Launcher",
"shortName":"Menu",
"icon": "app.png",
- "version":"0.02",
+ "version":"0.04",
"description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher",
"type":"launch",
@@ -1060,5 +1079,18 @@
"storage": [
{"name":"widmp.wid.js","url":"widget.js"}
]
+ },
+ { "id": "minionclk",
+ "name": "Minion clock",
+ "icon": "minionclk.png",
+ "version": "0.01",
+ "description": "Minion themed clock.",
+ "tags": "clock,minion",
+ "type": "clock",
+ "allow_emulator": true,
+ "storage": [
+ {"name":"minionclk.app.js","url":"app.js"},
+ {"name":"minionclk.img","url":"app-icon.js","evaluate":true}
+ ]
}
]
diff --git a/apps/aclock/ChangeLog b/apps/aclock/ChangeLog
index a179800be..98e3da8e7 100644
--- a/apps/aclock/ChangeLog
+++ b/apps/aclock/ChangeLog
@@ -5,3 +5,4 @@
0.08: make dots bigger and date more readable
0.09: center date, remove box around it, internal refactor to remove redundant code.
0.10: remove debug, refactor seconds to show elapsed secs each time app is displayed
+0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date
diff --git a/apps/aclock/clock-analog.js b/apps/aclock/clock-analog.js
index 419ed0933..7b60a728f 100644
--- a/apps/aclock/clock-analog.js
+++ b/apps/aclock/clock-analog.js
@@ -1,3 +1,4 @@
+// eliminate ide undefined errors
let g;
let Bangle;
@@ -5,15 +6,18 @@ let Bangle;
const locale = require('locale');
const p = Math.PI / 2;
const pRad = Math.PI / 180;
-const faceWidth = 100; // watch face radius
+const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
+const widgetHeight=24+1;
let timer = null;
let currentDate = new Date();
-const centerPx = g.getWidth() / 2;
+const centerX = g.getWidth() / 2;
+const centerY = (g.getWidth() / 2) + widgetHeight/2;
+
const seconds = (angle) => {
const a = angle * pRad;
- const x = centerPx + Math.sin(a) * faceWidth;
- const y = centerPx - Math.cos(a) * faceWidth;
+ const x = centerX + Math.sin(a) * faceWidth;
+ const y = centerY - Math.cos(a) * faceWidth;
// if 15 degrees, make hour marker larger
const radius = (angle % 15) ? 2 : 4;
@@ -25,14 +29,14 @@ const hand = (angle, r1, r2) => {
const r3 = 3;
g.fillPoly([
- Math.round(centerPx + Math.sin(a) * r1),
- Math.round(centerPx - Math.cos(a) * r1),
- Math.round(centerPx + Math.sin(a + p) * r3),
- Math.round(centerPx - Math.cos(a + p) * r3),
- Math.round(centerPx + Math.sin(a) * r2),
- Math.round(centerPx - Math.cos(a) * r2),
- Math.round(centerPx + Math.sin(a - p) * r3),
- Math.round(centerPx - Math.cos(a - p) * r3)
+ Math.round(centerX + Math.sin(a) * r1),
+ Math.round(centerY - Math.cos(a) * r1),
+ Math.round(centerX + Math.sin(a + p) * r3),
+ Math.round(centerY - Math.cos(a + p) * r3),
+ Math.round(centerX + Math.sin(a) * r2),
+ Math.round(centerY - Math.cos(a) * r2),
+ Math.round(centerX + Math.sin(a - p) * r3),
+ Math.round(centerY - Math.cos(a - p) * r3)
]);
};
@@ -54,6 +58,7 @@ const drawAll = () => {
seconds((360 * i) / 60);
}
onSecond();
+
};
const resetSeconds = () => {
@@ -88,8 +93,8 @@ const drawDate = () => {
// console.log(`${dayString}|${dateString}`);
// center date
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
- const t = centerPx + 37;
- g.drawString(dateDisplay, l, t);
+ const t = centerY + 37;
+ g.drawString(dateDisplay, l, t, true);
// console.log(l, t);
};
const onMinute = () => {
diff --git a/apps/daysl/ChangeLog b/apps/daysl/ChangeLog
index 4e0b8e6cf..c3faf0092 100644
--- a/apps/daysl/ChangeLog
+++ b/apps/daysl/ChangeLog
@@ -1,2 +1,3 @@
0.01: New Widget!
0.02: Improved calculation, new image for app
+0.03: Improved display of number
diff --git a/apps/daysl/widget.js b/apps/daysl/widget.js
index 6fb755d1e..4a32d5f26 100644
--- a/apps/daysl/widget.js
+++ b/apps/daysl/widget.js
@@ -1,39 +1,84 @@
const storage = require('Storage');
let settings;
+let height = 23;
+let width = 34;
+var debug = 0; //1 = show debug info
+
+//write settings to file
function updateSettings() {
storage.write('daysleft.json', settings);
+}
+
+//Define standard settings
+function resetSettings() {
+ settings = {
+ day : 17,
+ month : 6,
+ year: 2020
+ };
+ updateSettings();
+}
+
+settings = storage.readJSON('daysleft.json',1); //read storage
+if (!settings) resetSettings(); //if settings file was not found, set to standard
+
+var dd = settings.day,
+ mm = settings.month-1, //-1 because month is zero-based
+ yy = settings.year;
+
+const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
+const targetDate = new Date(yy, mm, dd); //is 00:00
+const today = new Date(); //includes current time
+
+const currentYear = today.getFullYear();
+const currentMonth = today.getMonth();
+const currentDay = today.getDate();
+const todayMorning = new Date (currentYear, currentMonth, currentDay, 0, 0, 0); //create date object with today, but 00:00:00
+
+const diffDays = (targetDate - todayMorning) / oneDay; //calculate day difference
+
+function drawWidget() {
+ if (debug == 1) g.drawRect(this.x,this.y,this.x+width,this.y+height); //draw rectangle around widget area
+ g.reset();
+
+ //define font size and string position
+ //small if number has more than 3 digits (positive number)
+ if (diffDays >= 1000) {
+ g.setFont("6x8", 1);
+ g.drawString(diffDays,this.x+10,this.y+7);
}
-
- function resetSettings() {
- settings = {
- day : 17,
- month : 6,
- year: 2020
- };
- updateSettings();
+ //large if number has 3 digits (positive number)
+ if (diffDays <= 999 && diffDays >= 100) {
+ g.setFont("6x8", 2);
+ g.drawString(diffDays,this.x,this.y+4);
}
+ //large if number has 2 digits (positive number)
+ if (diffDays <= 99 && diffDays >= 10) {
+ g.setFont("6x8", 2);
+ g.drawString(diffDays,this.x+6,this.y+4);
+ }
+ //large if number has 1 digit (positive number)
+ if (diffDays <= 9 && diffDays >= 0) {
+ g.setFont("6x8", 2);
+ g.drawString(diffDays,this.x+13,this.y+4);
+ }
+ //large if number has 1 digit (negative number)
+ if (diffDays <= -1 && diffDays >= -9) {
+ g.setFont("6x8", 2);
+ g.drawString(diffDays,this.x+5,this.y+4);
+ }
+ //large if number has 2 digits (negative number)
+ if (diffDays <= -10 && diffDays >= -99) {
+ g.setFont("6x8", 2);
+ g.drawString(diffDays,this.x,this.y+4);
+ }
+ //large if number has 3 digits or more (negative number)
+ if (diffDays <= -100) {
+ g.setFont("6x8", 1);
+ g.drawString(diffDays,this.x,this.y+7);
+ }
+}
- settings = storage.readJSON('daysleft.json',1);
- if (!settings) resetSettings();
-
- var dd = settings.day,
- mm = settings.month-1, //month is zero-based
- yy = settings.year;
-
- const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
- const targetDate = new Date(yy, mm, dd);
- const today = new Date();
-
- //create date object with today, but 00:00:00
- const currentYear = today.getFullYear();
- const currentMonth = today.getMonth();
- const currentDay = today.getDate();
- const todayMorning = new Date (currentYear, currentMonth, currentDay, 0, 0, 0);
-
- const diffDays = (targetDate - todayMorning) / oneDay;
-
-WIDGETS["daysl"]={area:"tl",width:40,draw:function(){
- g.setFont("6x8", 1);
- g.drawString(diffDays,this.x+12,this.y+12);
-}};
\ No newline at end of file
+//draw widget
+WIDGETS["daysl"]={area:"tl",width:width,draw:drawWidget};
\ No newline at end of file
diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog
index 0bcf94e25..e02ef176d 100644
--- a/apps/gbridge/ChangeLog
+++ b/apps/gbridge/ChangeLog
@@ -5,3 +5,4 @@
0.05: Show incoming call notification
Optimize animation, limit title length
0.06: Gadgetbridge App 'Connected' state is no longer toggleable
+0.07: Move configuration to settings menu
diff --git a/apps/gbridge/app.js b/apps/gbridge/app.js
deleted file mode 100644
index d12f0f768..000000000
--- a/apps/gbridge/app.js
+++ /dev/null
@@ -1,19 +0,0 @@
-function gb(j) {
- Bluetooth.println(JSON.stringify(j));
-}
-
-var mainmenu = {
- "" : { "title" : "Gadgetbridge" },
- "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
- "Find Phone" : function() { E.showMenu(findPhone); },
- "Exit" : ()=> {load();},
-};
-
-var findPhone = {
- "" : { "title" : "-- Find Phone --" },
- "On" : _=>gb({t:"findPhone",n:true}),
- "Off" : _=>gb({t:"findPhone",n:false}),
- "< Back" : function() { E.showMenu(mainmenu); },
-};
-
-E.showMenu(mainmenu);
diff --git a/apps/gbridge/settings.js b/apps/gbridge/settings.js
new file mode 100644
index 000000000..723c9cae9
--- /dev/null
+++ b/apps/gbridge/settings.js
@@ -0,0 +1,21 @@
+(function(back) {
+ function gb(j) {
+ Bluetooth.println(JSON.stringify(j));
+ }
+
+ var mainmenu = {
+ "" : { "title" : "Gadgetbridge" },
+ "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
+ "Find Phone" : function() { E.showMenu(findPhone); },
+ "< Back" : back,
+ };
+
+ var findPhone = {
+ "" : { "title" : "-- Find Phone --" },
+ "On" : _=>gb({t:"findPhone",n:true}),
+ "Off" : _=>gb({t:"findPhone",n:false}),
+ "< Back" : function() { E.showMenu(mainmenu); },
+ };
+
+ E.showMenu(mainmenu);
+})
diff --git a/apps/minionclk/ChangeLog b/apps/minionclk/ChangeLog
new file mode 100755
index 000000000..7b83706bf
--- /dev/null
+++ b/apps/minionclk/ChangeLog
@@ -0,0 +1 @@
+0.01: First release
diff --git a/apps/minionclk/app-icon.js b/apps/minionclk/app-icon.js
new file mode 100755
index 000000000..f78fb9e35
--- /dev/null
+++ b/apps/minionclk/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwhAaXlZEolVVvOj0mq1XOv9/qtWFb8rquj1XV5wBDAA2jvMqNLMqwAsCABBhBAIujqpbWvIsCLowBHMg0qFyVVFQJNDK4YoEAYxjFvIuQwHP6ur5+rDgfU5wBDMI2qCYOsC4XV0bFOwIWBAAeBAIOrMYYsF54sCCIWswQDB52AGBcrFIOtF4gAEMoJfDAAOrCYQuDAIowKFwQWIBIeA52pGQPWLYgAFA4YDBGA4uDEwQYFGQvPL4IuKC4wwHFglWAIQYIYoQuFCYwDGqrqFF4YYCYBKeHHwgRLlZhCLowMBKIIubWAtWF4PXBQRdEBAIBHGAoTRCIRfCDQQvBLofXB4NVBIQcDFweAOYdWp9WqwrDGA8ABoRJFAAOswFOqtUwBNFeQYVCwMqp4ABqwbDCowvCAgKOGf4N5aAIwKKIVPpwRBGQOBFw5fCIgZfGwFVlT/BqtVBQQwFUAVWFwMAlYvCL6mBqkqDgNUF4RLGAgVPFwMAklPwD0GX5gOCXoReBJgSvDIwWrAoN6py/Cp58DCYxQBVIYwHqyQBbgL6EX4qQDFwRdFLAifBaoQaEAJIuDCYWrEgoTIGAerWIJfHGRZdECZ5fD0bQBIwJgEIoynEGBJxIAAYPBwHNlUq6owBMIZ4OMKQPC0eiqsr53PX4guOwBhOComk0ejqpfB53OJZAeDU4lVvN5OQQXKBIeA0RfBvNV5wwBMI4uD1oLDFoN4AQJEMBYWl0fOL4NUL4QwCPw4BEwN4AAejvGACZaMBLoRfGAIWr1Z8HwGkFQRIBDgekwAVGFoOAB4QTDqgvBFwQDD1Wk1el1YsBv4oDAIoAB0d/GQOlAIV/CpF51ZfFAIgAEUoOiAJYmEAAoHDvOBFxWqAIWpFxwnDOJABBquJ1QwM5oDCMYYDDAIN/5+r6CABHRKOBmVewIwCYY4sC0bGB678B1ekZYIAB0ulwOlvWkIQLJCMY0yq2sr2sMJYABp96vQnB0tPz4FBBANzAAWj5pdI0dWq0zr2Ir2rMJKQCvNIp9PudIuYDBAIV0FwSMFL4d/LoMzL4WBwIwD6hhH5ujuZXBFAIDDAIOdFwPNL4hdCwBdCq15AgMrAQLDB52pRYYACBIWjvGdK4NJAQOdvIlB5oXB0QwBLoWjqsrAAaSCGANVGAJHBSQjBEAAINBewIDCLYIBCAAJfDv5dCLAIvBvIEDAAJoBwGqDQSUBY4htDBwIBBAoIwDL4WjvNdhAvCAAYFGhDGCY4IAB1QABFwQwDv4BB1V/0eA0mALINdP4IpGMYcAAIQABGAIxDAAIFBruCroAGq1eFALkDmdeD4IjDGQYCCBQYDBCgIqBAJAoBAQIDCmeBCoIDCGgIfBLooADL4YBCJAIiCAANdAIQoCAI4ABAYdWKQQDDMooEBlVPBwJfCGAwABFxABDSAZYGLo1OvFOIgQaBLYZhFAIsyFgYAEFAUqpxUBFYQ="))
\ No newline at end of file
diff --git a/apps/minionclk/app.js b/apps/minionclk/app.js
new file mode 100755
index 000000000..88fe446ae
--- /dev/null
+++ b/apps/minionclk/app.js
@@ -0,0 +1,68 @@
+const bob = require("heatshrink").decompress(atob("nk8hAaXlYLWAEsqvN/0gBBql5lQ2tquj1XV5wBJ52j0hACPsdP1QsBAQQAGBIIBF51/P8OkN5R1GIxF5HLmAFgoDLPZfOpzmZ6vPFwomCPaA6DAYOjeq2A1YyCdI4HGQJQ8F1T2SJ4Oq1XW1es1mtAQOrPoPUAIh3J54ZHIAR5S62s64cBwIBGQIOqHQK4HKQYVDAAIFC1g+BHh9VHAQAFDwQDDHoJ5E54BB6AaBKQ5YGqo6MwJzGHQ4BDeIj/BR4JxDABY8BvI6OOYgaEHwZADHgQ6BZA42GAIusPJNW64eFqzJDlcrERA8BHQI2FqwaBDYYGBPI45GCoIgCLoVWQ5NWXA2rKhaiGLAwOGEAmADxJPDVA51ElQaMC4ouEWALdEHRg8Dc4woCDJo8EAIYxCHQQIFHiwaRegJ5EcYWsHgbrKbBA8GDSrNDO4wfRKgR3FDSh3CN4UrdwZbSHYZ5DHajMFHYQGCHalWO4jtQDQwABwAGCAAQfTKoK0EHahwCeARdFHakASIZWVZ4Q8CO4YgWO4QbCO6hWGEIKYZKzZ3DLog7UG4I6C1lWDSdWO4bpCO4bwUwKYEHajMDwOAlUkLojTUd4gaTZoRWC0YIB1eJLqo4EWiqRE0mjlcr1QkEeKFWOooBCHiB2CC4WA5wzB52rEQgfPHQwABAYJXOHQ2iO4XO6omFEJh1BEAgBGPJlWDIQbC0ej50qgHV1XPEwohKcwRbEvJ3EBQTrLFomkOwOjlR3C5w8GMAR8ClYuBLIgOCvN4HgIZFDQYbBlaOBR4YNCwA5B0XOpzvBHgWqTw4AFxB1EvQ6BAAI8GDZILEdgQBCqp3DPIRfIEQwABvJ1CvGkvGiwA6IAYoBCv6wCAAVOlQ6DAIWkL5ABEwN40Z1CAYJfCv7zEAJNWOYZ3KAIWq5yYHFYLOBLIrVCAIh7BOIzpECoYDDpw7BHAQDG1WqwGkAIN/CwIABLY4LDAAZ9BwAABvLCBC5IBBvOrO44BEAAmjAIPN0XOAJyHIAAgPEquBHBi4BAId+HwWiHxqJDRpYBDq2I1R3LIAQ6BAAOpPogABGgIDDOomk0nP5pIGd42Aq1ewI8CeZI6CHAJhCEAIDCdIo2B0er1esAQIZBC4Z9JlVW1leeKGp0es5+s6+s6ABB0oFBAIervWr0ulub7OqtWmdexJ4BGxB5G0V6pF6wItB0t6p9PvQABvINBudJudPzwXCHQwBDlY6BO4WIwPPPJbvDvA0BFgNzAAIDGugGCzrtHdYh1DAIOBrzxBPIgAIeIXNHgoBCGwYADzoVB0fNOpMyHQdW5+Bro7BHgQAB6jxJAAOjOYhxCAIukOoPN5ujdZFWqyyD0d6AwUrquBwAuB1I1FHgRJBMoQ9BWg1zzw0BDYI6B0R3DAAJ1BvMyp8rAAV4IQWBIodewAeCHAZ5IAAJoBAAXHHAJWDO4TtEdYQvEHgejAwIKClcqIQRdDXYbzFeoQBIGwIDDOot/VgQ6FAAIGBlgBCAAMzmZPBF4LzDACB1FAAOi1WjvFVr0zGYQ7GAAMAAYpPBwNeAIOIwOrfYOA1eA1WkAIWjAIekv4PBwCVBruBq5eBEYIABlcBF4wCEHw55CHwQABIQQBBABkzAILlCHQR1CFYavEPgsAAAIDEDQNdAwQAaHQNWEwQ0DHAh3KleBLoI7dHQKuFWQo0EAIsISoKdBHbyyHNgwADlVVpwEBDANWro7fd4Q6HO495vF5QgIYCd75eBHYUINAN5lQ3EA"));
+
+const locale = require("locale");
+
+const black = 0x0000;
+const white = 0xFFFF;
+
+let hour;
+let minute;
+let date;
+
+function draw() {
+ const d = new Date();
+
+ const newHour = ('0' + d.getHours()).substr(-2);
+ const newMinute = ('0' + d.getMinutes()).substr(-2);
+ const newDate = locale.date(d).trim();
+
+ g.setFontAlign(0, 0, 0);
+
+ if (newHour !== hour) {
+ g.setFontVector(48);
+ g.setColor(black);
+ g.drawString(hour, 64, 92);
+ g.setColor(white);
+ g.drawString(newHour, 64, 92);
+ hour = newHour;
+ }
+
+ if (newMinute !== minute) {
+ g.setFontVector(48);
+ g.setColor(black);
+ g.drawString(minute, 172, 92);
+ g.setColor(white);
+ g.drawString(newMinute, 172, 92);
+ minute = newMinute;
+ }
+
+ if (newDate !== date) {
+ g.setFontVector(12);
+ g.setColor(black);
+ g.drawString(date, 120, 228);
+ g.setColor(0xFFFF);
+ g.drawString(newDate, 120, 228);
+ date = newDate;
+ }
+}
+
+function drawAll() {
+ hour = '';
+ minute = '';
+ date = '';
+ g.drawImage(bob, 0, 0, { scale: 4 });
+ draw();
+}
+
+Bangle.on('lcdPower', function(on) {
+ if (on) {
+ drawAll();
+ }
+});
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+setInterval(draw, 1000);
+drawAll();
+
+setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
diff --git a/apps/minionclk/minionclk.png b/apps/minionclk/minionclk.png
new file mode 100755
index 000000000..77cac31df
Binary files /dev/null and b/apps/minionclk/minionclk.png differ
diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog
index 0cfa04bf0..73bbc7bd1 100644
--- a/apps/setting/ChangeLog
+++ b/apps/setting/ChangeLog
@@ -5,3 +5,6 @@
0.06: Remove distance setting as there's a separate app for Locale now
0.07: Added vibrate as beep workaround
0.08: Add support for app/widget settings
+0.09: Move Welcome into App/widget settings
+0.10: Added LCD wake-up settings
+ Adds LCD brightness setting
diff --git a/apps/setting/boot.js b/apps/setting/boot.js
new file mode 100644
index 000000000..8bf9df50d
--- /dev/null
+++ b/apps/setting/boot.js
@@ -0,0 +1,6 @@
+(() => {
+ var settings = require('Storage').readJSON('setting.json', true);
+ if (settings != undefined) {
+ Bangle.setOptions(settings.options);
+ }
+})()
diff --git a/apps/setting/settings-default.json b/apps/setting/settings-default.json
index 0800593cb..c61fd6109 100644
--- a/apps/setting/settings-default.json
+++ b/apps/setting/settings-default.json
@@ -10,4 +10,16 @@
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
// welcomed : undefined/true (whether welcome app should show)
+ brightness: 1, // LCD brightness from 0 to 1
+ options: {
+ wakeOnBTN1: true,
+ wakeOnBTN2: true,
+ wakeOnBTN3: true,
+ wakeOnFaceUp: false,
+ wakeOnTouch: false,
+ wakeOnTwist: true,
+ twistThreshold: 819.2,
+ twistMaxY: -800,
+ twistTimeout: 1000
+ }
}
diff --git a/apps/setting/settings.js b/apps/setting/settings.js
index dbb03555c..cbd856ec5 100644
--- a/apps/setting/settings.js
+++ b/apps/setting/settings.js
@@ -9,6 +9,21 @@ function updateSettings() {
storage.write('setting.json', settings);
}
+function updateOptions() {
+ updateSettings();
+ Bangle.setOptions(settings.options)
+}
+
+function gToInternal(g) {
+ // converts g to Espruino internal unit
+ return g * 8192;
+}
+
+function internalToG(u) {
+ // converts Espruino internal unit to g
+ return u / 8192
+}
+
function resetSettings() {
settings = {
ble: true, // Bluetooth enabled by default
@@ -18,22 +33,34 @@ function resetSettings() {
vibrate: true, // Vibration enabled by default. App must support
beep: "vib", // Beep enabled by default. App must support
timezone: 0, // Set the timezone for the device
- HID : false, // BLE HID mode, off by default
+ HID: false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
+ brightness: 1, // LCD brightness from 0 to 1
// welcomed : undefined/true (whether welcome app should show)
+ options: {
+ wakeOnBTN1: true,
+ wakeOnBTN2: true,
+ wakeOnBTN3: true,
+ wakeOnFaceUp: false,
+ wakeOnTouch: false,
+ wakeOnTwist: true,
+ twistThreshold: 819.2,
+ twistMaxY: -800,
+ twistTimeout: 1000
+ }
};
updateSettings();
}
-settings = storage.readJSON('setting.json',1);
+settings = storage.readJSON('setting.json', 1);
if (!settings) resetSettings();
const boolFormat = v => v ? "On" : "Off";
function showMainMenu() {
- var beepV = [ false,true,"vib" ];
- var beepN = [ "Off","Piezo","Vibrate" ];
+ var beepV = [false, true, "vib"];
+ var beepN = ["Off", "Piezo", "Vibrate"];
const mainmenu = {
'': { 'title': 'Settings' },
'Make Connectable': makeConnectable,
@@ -72,14 +99,25 @@ function showMainMenu() {
Bangle.setLCDTimeout(settings.timeout);
}
},
+ 'LCD Brightness': {
+ value: settings.brightness,
+ min: 0,
+ max: 1,
+ step: 0.1,
+ onchange: v => {
+ settings.brightness = v || 1;
+ updateSettings();
+ Bangle.setLCDBrightness(settings.brightness);
+ }
+ },
'Beep': {
- value: 0|beepV.indexOf(settings.beep),
- min:0,max:2,
- format: v=>beepN[v],
+ value: 0 | beepV.indexOf(settings.beep),
+ min: 0, max: 2,
+ format: v => beepN[v],
onchange: v => {
settings.beep = beepV[v];
- if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200) } // piezo
- else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200) } // vibrate
+ if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo
+ else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200); } // vibrate
updateSettings();
}
},
@@ -91,18 +129,10 @@ function showMainMenu() {
updateSettings();
if (settings.vibrate) {
VIBRATE.write(1);
- setTimeout(()=>VIBRATE.write(0), 10);
+ setTimeout(() => VIBRATE.write(0), 10);
}
}
},
- 'Welcome App': {
- value: !settings.welcomed,
- format: boolFormat,
- onchange: v => {
- settings.welcomed = v?undefined:true;
- updateSettings();
- }
- },
'Locale': showLocaleMenu,
'Select Clock': showClockMenu,
'HID': {
@@ -114,14 +144,101 @@ function showMainMenu() {
}
},
'Set Time': showSetTimeMenu,
+ 'LCD Wake-Up': showWakeUpMenu,
'App/widget settings': showAppSettingsMenu,
'Reset Settings': showResetMenu,
'Turn Off': Bangle.off,
- '< Back': ()=> {load();}
+ '< Back': () => { load(); }
};
return E.showMenu(mainmenu);
}
+function showWakeUpMenu() {
+ const wakeUpMenu = {
+ '': { 'title': 'LCD Wake-Up' },
+ '< Back': showMainMenu,
+ 'Wake On BTN1': {
+ value: settings.options.wakeOnBTN1,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1;
+ updateOptions();
+ }
+ },
+ 'Wake On BTN2': {
+ value: settings.options.wakeOnBTN2,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2;
+ updateOptions();
+ }
+ },
+ 'Wake On BTN3': {
+ value: settings.options.wakeOnBTN3,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3;
+ updateOptions();
+ }
+ },
+ 'Wake on FaceUp': {
+ value: settings.options.wakeOnFaceUp,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp;
+ updateOptions();
+ }
+ },
+ 'Wake on Touch': {
+ value: settings.options.wakeOnTouch,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnTouch = !settings.options.wakeOnTouch;
+ updateOptions();
+ }
+ },
+ 'Wake On Twist': {
+ value: settings.options.wakeOnTwist,
+ format: boolFormat,
+ onchange: () => {
+ settings.options.wakeOnTwist = !settings.options.wakeOnTwist;
+ updateOptions();
+ }
+ },
+ 'Twist Threshold': {
+ value: internalToG(settings.options.twistThreshold),
+ min: -0.5,
+ max: 0.5,
+ step: 0.01,
+ onchange: v => {
+ settings.options.twistThreshold = gToInternal(v || 0.1);
+ updateOptions();
+ }
+ },
+ 'Twist Max Y': {
+ value: settings.options.twistMaxY,
+ min: -1500,
+ max: 1500,
+ step: 100,
+ onchange: v => {
+ settings.options.twistMaxY = v || -800;
+ updateOptions();
+ }
+ },
+ 'Twist Timeout': {
+ value: settings.options.twistTimeout,
+ min: 0,
+ max: 2000,
+ step: 100,
+ onchange: v => {
+ settings.options.twistTimeout = v || 1000;
+ updateOptions();
+ }
+ }
+ }
+ return E.showMenu(wakeUpMenu)
+}
+
function showLocaleMenu() {
const localemenu = {
'': { 'title': 'Locale' },
@@ -138,7 +255,7 @@ function showLocaleMenu() {
},
'Clock Style': {
value: !!settings["12hour"],
- format : v => v?"12hr":"24hr",
+ format: v => v ? "12hr" : "24hr",
onchange: v => {
settings["12hour"] = v;
updateSettings();
@@ -166,33 +283,33 @@ function showResetMenu() {
}
function makeConnectable() {
- try { NRF.wake(); } catch(e) {}
+ try { NRF.wake(); } catch (e) { }
Bluetooth.setConsole(1);
- var name="Bangle.js "+NRF.getAddress().substr(-5).replace(":","");
- E.showPrompt(name+"\nStay Connectable?",{title:"Connectable"}).then(r=>{
- if (settings.ble!=r) {
+ var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", "");
+ E.showPrompt(name + "\nStay Connectable?", { title: "Connectable" }).then(r => {
+ if (settings.ble != r) {
settings.ble = r;
updateSettings();
}
- if (!r) try { NRF.sleep(); } catch(e) {}
+ if (!r) try { NRF.sleep(); } catch (e) { }
showMainMenu();
});
}
function showClockMenu() {
- var clockApps = require("Storage").list(/\.info$/).map(app=>{
+ var clockApps = require("Storage").list(/\.info$/).map(app => {
try { return require("Storage").readJSON(app); }
- catch (e) {}
- }).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
+ catch (e) { }
+ }).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
const clockMenu = {
'': {
'title': 'Select Clock',
},
'< Back': showMainMenu,
};
- clockApps.forEach((app,index) => {
+ clockApps.forEach((app, index) => {
var label = app.name;
if ((!settings.clock && index === 0) || (settings.clock === app.src)) {
- label = "* "+label;
+ label = "* " + label;
}
clockMenu[label] = () => {
if (settings.clock !== app.src) {
@@ -203,7 +320,7 @@ function showClockMenu() {
};
});
if (clockApps.length === 0) {
- clockMenu["No Clocks Found"] = () => {};
+ clockMenu["No Clocks Found"] = () => { };
}
return E.showMenu(clockMenu);
}
@@ -215,7 +332,7 @@ function showSetTimeMenu() {
const timemenu = {
'': {
'title': 'Set Time',
- 'predraw': function() {
+ 'predraw': function () {
d = new Date();
timemenu.Hour.value = d.getHours();
timemenu.Minute.value = d.getMinutes();
@@ -234,7 +351,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setHours(v);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
},
'Minute': {
@@ -245,7 +362,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setMinutes(v);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
},
'Second': {
@@ -256,7 +373,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setSeconds(v);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
},
'Date': {
@@ -267,7 +384,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setDate(v);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
},
'Month': {
@@ -278,7 +395,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setMonth(v - 1);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
},
'Year': {
@@ -289,16 +406,16 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setFullYear(v);
- setTime(d.getTime()/1000);
+ setTime(d.getTime() / 1000);
}
}
};
return E.showMenu(timemenu);
}
-function showAppSettingsMenu(){
+function showAppSettingsMenu() {
let appmenu = {
- '': {'title': 'App Settings'},
+ '': { 'title': 'App Settings' },
'< Back': showMainMenu,
}
const apps = storage.list(/\.info$/)
@@ -306,10 +423,10 @@ function showAppSettingsMenu(){
.filter(app => app && app.settings)
.sort((a, b) => a.sortorder - b.sortorder)
if (apps.length === 0) {
- appmenu['No app has settings'] = () => {};
+ appmenu['No app has settings'] = () => { };
}
apps.forEach(function (app) {
- appmenu[app.name] = () => {showAppSettings(app)};
+ appmenu[app.name] = () => { showAppSettings(app) };
})
E.showMenu(appmenu)
}
diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog
index bd3d5d225..c536d1a5b 100644
--- a/apps/toucher/ChangeLog
+++ b/apps/toucher/ChangeLog
@@ -1,2 +1,4 @@
0.01: New App!
-0.02: Add swipe support and doucle tap to run application
\ No newline at end of file
+0.02: Add swipe support and doucle tap to run application
+0.03: Close launcher when lcd turn off
+0.04: Complete rewrite to add animation and loop ( issue #210 )
\ No newline at end of file
diff --git a/apps/toucher/app.js b/apps/toucher/app.js
index 2b80198c9..5c3703129 100644
--- a/apps/toucher/app.js
+++ b/apps/toucher/app.js
@@ -1,4 +1,6 @@
+Bangle.setLCDMode("120x120");
g.clear();
+g.flip();
const Storage = require("Storage");
@@ -14,99 +16,144 @@ function getApps(){
});
}
-const selected = 0;
-const apps = getApps();
+const HEIGHT = g.getHeight();
+const WIDTH = g.getWidth();
+const HALF = WIDTH/2;
+const ANIMATION_FRAME = 3;
+const ANIMATION_STEP = HALF / ANIMATION_FRAME;
-function prev(){
- if (selected>=0) {
- selected--;
- }
- drawMenu();
+function getPosition(index){
+ return (index*HALF);
}
-function next() {
- if (selected+1 {
+ const x = getPosition(i) + HALF - offset;
+ const y = HALF - (HALF*0.3);//-(HALF*0.7);
+ let diff = (x - HALF);
+ if(diff < 0) diff *=-1;
+ let size = 30;
+ if((diff*0.5) < size) size -= (diff*0.5);
+ else size = 0;
+
+ const scale = size / 30;
+ if(size){
+ let c = size / 30 * 2;
+ c = c -1;
+ if(c < 0) c = 0;
+
+ if(app.back){
+ g.setFont('6x8', 1);
+ g.setFontAlign(0, -1);
+ g.setColor(c,c,c);
+ g.drawString('Back', HALF, HALF);
+ return;
+ }
+ // icon
+ const icon = app.icon ? Storage.read(app.icon) : null;
+ if(icon){
+ try {
+ g.drawImage(icon, x-(scale*24), y-(scale*24), { scale: scale });
+ } catch(e){
+ noIcon(x, y, size);
+ }
+ }else{
+ noIcon(x, y, size);
+ }
+ //text
+ g.setFont('6x8', 1);
+ g.setFontAlign(0, -1);
+ g.setColor(c,c,c);
+ g.drawString(app.name, HALF, HEIGHT - (HALF*0.7));
+
+ const type = app.type ? app.type : 'App';
+ const version = app.version ? app.version : '0.00';
+ const info = type+' v'+version;
+ g.setFontAlign(0,1);
+ g.setFont('4x6', 0.25);
+ g.setColor(c,c,c);
+ g.drawString(info, HALF, 110, { scale: scale });
+ }
+ });
+}
+
+function draw(ignoreLoop){
+ g.clear();
+ drawIcons(slideOffset);
+ g.flip();
+ if(slideOffset == target) return;
+ if(slideOffset < target) slideOffset+= ANIMATION_STEP;
+ else if(slideOffset > target) slideOffset -= ANIMATION_STEP;
+ if(!ignoreLoop) draw();
+}
+
+function animateTo(index){
+ target = getPosition(index);
+ draw();
+}
+function goTo(index){
+ current_app = index;
+ target = getPosition(index);
+ slideOffset = target;
+ draw(true);
+}
+
+goTo(1);
+
+function prev(){
+ if(current_app == 0) goTo(apps.length-1);
+ current_app -= 1;
+ if(current_app < 0) current_app = 0;
+ animateTo(current_app);
+}
+
+function next(){
+ if(current_app == apps.length-1) goTo(0);
+ current_app += 1;
+ if(current_app > apps.length-1) current_app = apps.length-1;
+ animateTo(current_app);
}
function run() {
- if(selected < 0) return load();
- if (!apps[selected].src) return;
- if (Storage.read(apps[selected].src)===undefined) {
+ const app = apps[current_app];
+ if(app.back) return load();
+ if (Storage.read(app.src)===undefined) {
E.showMessage("App Source\nNot found");
- setTimeout(drawMenu, 2000);
+ setTimeout(draw, 2000);
} else {
- E.showMessage("Loading...");
- load(apps[selected].src);
- }
-}
-
-function getCurrentApp(){
- return apps[selected];
-}
-
-function getNextApp(){
- return apps[selected+1];
-}
-
-function drawFallbackIcon(){
- g.setColor(1,1,1);
- g.fillRect(72, 40, 168, 136);
- g.setColor(0,0,0);
- g.setFont('6x8', 8);
- g.drawString('?', 124, 88);
-}
-
-function drawArrow(x, y, size, dir){
- size = size || 10;
- dir = dir || 1;
- g.moveTo(x, y).lineTo(x+(size*dir), y-size).lineTo(x+(size*dir),y+size).lineTo(x, y);
-}
-
-function drawMenu(){
-
- if(selected < 0){
+ Bangle.setLCDMode();
g.clear();
- g.setFontAlign(0,0);
- g.setFont('6x8', 2);
- g.drawString('Back', 120, 120);
- drawArrow(220, 120, 10, -1);
- return;
+ g.flip();
+ E.showMessage("Loading...");
+ load(app.src);
}
-
- const app = getCurrentApp();
- g.clear();
- g.setFontAlign(0,0);
- g.setFont('6x8', 2);
- if(!app) return g.drawString('???', 120, 120);
- g.drawString(app.name, 120, 160);
- if (app.icon) icon = Storage.read(app.icon);
- if (icon) try {g.drawImage(icon, 120-48, 40, { scale: 2 });} catch(e){ drawFallbackIcon(); }
- else drawFallbackIcon();
-
- g.setFont('6x8', 1);
-
- const type = app.type ? app.type : 'App';
- const version = app.version ? app.version : '0.00';
- const info = type+' v'+version;
- g.setFontAlign(-1,1);
- g.drawString(info, 20, 220);
-
- const count = (selected+1)+'/'+apps.length;
- g.setFontAlign(1,1);
- g.drawString(count, 220, 220);
-
- drawArrow(20, 120, 10, 1);
- if(getNextApp()) drawArrow(220, 120, 10, -1);
}
-drawMenu();
-// Physical buttons
-setWatch(prev, BTN1, {repeat:true});
-setWatch(next, BTN3, {repeat:true});
+setWatch(prev, BTN1, { repeat: true });
+setWatch(next, BTN3, { repeat: true });
setWatch(run, BTN2, {repeat:true,edge:"falling"});
// Screen event
@@ -127,4 +174,9 @@ Bangle.on('touch', function(button){
Bangle.on('swipe', dir => {
if(dir == 1) prev();
else next();
+});
+
+// close launcher when lcd is off
+Bangle.on('lcdPower', on => {
+ if(!on) return load();
});
\ No newline at end of file
diff --git a/apps/welcome/ChangeLog b/apps/welcome/ChangeLog
index d8d647138..34f6e3a82 100644
--- a/apps/welcome/ChangeLog
+++ b/apps/welcome/ChangeLog
@@ -2,3 +2,4 @@
0.02: Animate balloon intro
0.03: BTN3 now won't restart when at the end
0.04: Fix regression after tweaks to Storage.readJSON
+0.05: Move configuration into App/widget settings
diff --git a/apps/welcome/settings.js b/apps/welcome/settings.js
new file mode 100644
index 000000000..2fbd585c6
--- /dev/null
+++ b/apps/welcome/settings.js
@@ -0,0 +1,16 @@
+// The welcome app is special, and gets to use global settings
+(function(back) {
+ let settings = require('Storage').readJSON('setting.json', 1) || {}
+ E.showMenu({
+ '': { 'title': 'Welcome App' },
+ 'Run again': {
+ value: !settings.welcomed,
+ format: v => v ? 'Yes' : 'No',
+ onchange: v => {
+ settings.welcomed = v ? undefined : true
+ require('Storage').write('setting.json', settings)
+ },
+ },
+ '< Back': back,
+ })
+})
diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog
index 3988729c3..3627a86d3 100644
--- a/apps/widbatpc/ChangeLog
+++ b/apps/widbatpc/ChangeLog
@@ -3,3 +3,5 @@
0.04: Ensure redrawing works with variable size widget system
0.05: Change color depending on battery level, cloned from widbat
0.06: Show battery percentage as text
+0.07: Add settings: percentage/color/charger icon
+0.08: Draw percentage as inverted on monochrome battery
diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js
new file mode 100644
index 000000000..5c0bdbcae
--- /dev/null
+++ b/apps/widbatpc/settings.js
@@ -0,0 +1,58 @@
+// This file should contain exactly one function, which shows the app's settings
+/**
+ * @param {function} back Use back() to return to settings menu
+ */
+(function(back) {
+ const SETTINGS_FILE = 'widbatpc.settings.json'
+ const COLORS = ['By Level', 'Green', 'Monochrome']
+
+ // initialize with default settings...
+ let s = {
+ 'color': COLORS[0],
+ 'percentage': true,
+ 'charger': true,
+ }
+ // ...and overwrite them with any saved values
+ // This way saved values are preserved if a new version adds more settings
+ const storage = require('Storage')
+ const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
+ for (const key in saved) {
+ s[key] = saved[key]
+ }
+
+ // creates a function to safe a specific setting, e.g. save('color')(1)
+ function save(key) {
+ return function (value) {
+ s[key] = value
+ storage.write(SETTINGS_FILE, s)
+ WIDGETS["batpc"].reload()
+ }
+ }
+
+ const onOffFormat = b => (b ? 'on' : 'off')
+ const menu = {
+ '': { 'title': 'Battery Widget' },
+ '< Back': back,
+ 'Percentage': {
+ value: s.percentage,
+ format: onOffFormat,
+ onchange: save('percentage'),
+ },
+ 'Charging Icon': {
+ value: s.charger,
+ format: onOffFormat,
+ onchange: save('charger'),
+ },
+ 'Color': {
+ format: () => s.color,
+ onchange: function () {
+ // cycles through options
+ const oldIndex = COLORS.indexOf(s.color)
+ const newIndex = (oldIndex + 1) % COLORS.length
+ s.color = COLORS[newIndex]
+ save('color')(s.color)
+ },
+ },
+ }
+ E.showMenu(menu)
+})
diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js
index 7100dc111..9f88b5c49 100644
--- a/apps/widbatpc/widget.js
+++ b/apps/widbatpc/widget.js
@@ -1,20 +1,62 @@
(function(){
+const DEFAULTS = {
+ 'color': 'By Level',
+ 'percentage': true,
+ 'charger': true,
+}
+const COLORS = {
+ 'white': -1,
+ 'charging': 0x07E0, // "Green"
+ 'high': 0x05E0, // slightly darker green
+ 'ok': 0xFD20, // "Orange"
+ 'low':0xF800, // "Red"
+}
+const SETTINGS_FILE = 'widbatpc.settings.json'
+
+let settings
+function loadSettings() {
+ settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}
+}
+function setting(key) {
+ if (!settings) { loadSettings() }
+ return (key in settings) ? settings[key] : DEFAULTS[key]
+}
+
const levelColor = (l) => {
- if (Bangle.isCharging()) return 0x07E0; // "Green"
- if (l >= 50) return 0x05E0; // slightly darker green
- if (l >= 15) return 0xFD20; // "Orange"
- return 0xF800; // "Red"
+ // "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
+ const green = setting('percentage') ? COLORS.high : COLORS.charging
+ switch (setting('color')) {
+ case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
+ case 'Green': return green;
+ case 'By Level': // fall through
+ default:
+ if (setting('charger')) {
+ // charger icon -> always make percentage readable
+ if (Bangle.isCharging() || l >= 50) return green;
+ } else {
+ // no icon -> brightest green to indicate charging, even when showing percentage
+ if (Bangle.isCharging()) return COLORS.charging;
+ if (l >= 50) return COLORS.high;
+ }
+ if (l >= 15) return COLORS.ok;
+ return COLORS.low;
+ }
+}
+const chargerColor = () => {
+ return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging
}
function setWidth() {
- WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
+ WIDGETS["batpc"].width = 40;
+ if (Bangle.isCharging() && setting('charger')) {
+ WIDGETS["batpc"].width += 16;
+ }
}
function draw() {
var s = 39;
var x = this.x, y = this.y;
- const l = E.getBattery(), c = levelColor(l);
- if (Bangle.isCharging()) {
- g.setColor(c).drawImage(atob(
+ if (Bangle.isCharging() && setting('charger')) {
+ g.setColor(chargerColor()).drawImage(atob(
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
x+=16;
}
@@ -22,18 +64,40 @@ function draw() {
g.fillRect(x,y+2,x+s-4,y+21);
g.clearRect(x+2,y+4,x+s-6,y+19);
g.fillRect(x+s-3,y+10,x+s,y+14);
- g.setColor(c).fillRect(x+4,y+6,x+4+l*(s-12)/100,y+17);
+ const l = E.getBattery(),
+ c = levelColor(l);
+ const xl = x+4+l*(s-12)/100
+ g.setColor(c).fillRect(x+4,y+6,xl,y+17);
g.setColor(-1);
- g.setFontAlign(-1,-1);
+ if (!setting('percentage')) {
+ return;
+ }
+ let gfx = g
+ if (setting('color') === 'Monochrome') {
+ // draw text inverted on battery level
+ gfx = Graphics.createCallback(240, 240, 1,
+ (x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
+ }
+ gfx.setFontAlign(-1,-1);
if (l >= 100) {
- g.setFont('4x6', 2);
- g.drawString(l, x + 6, y + 7);
+ gfx.setFont('4x6', 2);
+ gfx.drawString(l, x + 6, y + 7);
} else {
if (l < 10) x+=6;
- g.setFont('6x8', 2);
- g.drawString(l, x + 6, y + 4);
+ gfx.setFont('6x8', 2);
+ gfx.drawString(l, x + 6, y + 4);
}
}
+// reload widget, e.g. when settings have changed
+function reload() {
+ loadSettings()
+ // need to redraw all widgets, because changing the "charger" setting
+ // can affect the width and mess with the whole widget layout
+ setWidth()
+ g.clear();
+ Bangle.drawWidgets();
+}
+
Bangle.on('charging',function(charging) {
if(charging) Bangle.buzz();
setWidth();
@@ -43,7 +107,7 @@ Bangle.on('charging',function(charging) {
var batteryInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
- WIDGETS["bat"].draw();
+ WIDGETS["batpc"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(draw, 60000);
@@ -54,6 +118,6 @@ Bangle.on('lcdPower', function(on) {
}
}
});
-WIDGETS["bat"]={area:"tr",width:40,draw:draw};
+WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
setWidth();
})()
diff --git a/apps/wohrm/ChangeLog b/apps/wohrm/ChangeLog
new file mode 100644
index 000000000..f5c64dbee
--- /dev/null
+++ b/apps/wohrm/ChangeLog
@@ -0,0 +1,6 @@
+0.01: Only tested on the emulator.
+0.02: Adapted to new App code layout
+0.03: Optimized rendering for the background
+0.04: Only buzz on high confidence (>85%)
+0.05: Improved buzz timing and rendering
+0.06: Removed debug outputs, fixed rendering for upper limit, improved rendering for +/- icons, changelog version order fixed
diff --git a/apps/wohrm/app-icon.js b/apps/wohrm/app-icon.js
new file mode 100644
index 000000000..4a69b16bd
--- /dev/null
+++ b/apps/wohrm/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwhC/AH4AVgnd5tABI3c7oJGAAUs5gAC4gJDpgJD4QWGhoMDAAPQBJYADBgoABBJYAChgJD5oDC4AJEAAfAC4fcBIfUDYYJEEogWCgQJEoYSHAAsgIw3MmYqIn89JAoXFn5DH4f/+YXFWQnE/4GEAAXP///ZgooE4X/ngvMPAQXEBoIXHHIJfDC4ss5nf+f9OosjFwgXF5oTBp8z+gMBMQPTn5dBNIgXCAwPDEQM/mQmCJQNP/8zDIJRDO4SnB6fz7k/poXEJwIJBmanGhvMl//loxC7nE/jUCon/6gzBC4PQC4MDKIJFDn9M4YXB5nUKYbACmAXBgE/+YMBOoMvngXDJIKDB6YvBOwRgDaoINB788p5wDn7HELwQABghWCBoPD/s/YwNN5i+Bc4dAC4bBCC4fyPIPU+Z0BDAZGEJAffYgPC+ZxBG4KkB6f/C4JGEAAQsBcIX/+QEBCgP9A4IXBCwwwB5pxDPYJoDcgIuIGASJH5rvBAwIWIeYQABl5jBAAXDIwLrCABCcC76gDAoP0RgwAFYYJ7DJAcsFxYABaYJ7DAAXECxhJEAAgWOPQgACIpoADUwb1BCyBJERZgYKkAXUglACygA/AH4AFA=="))
\ No newline at end of file
diff --git a/apps/wohrm/app.js b/apps/wohrm/app.js
new file mode 100644
index 000000000..7e0af4219
--- /dev/null
+++ b/apps/wohrm/app.js
@@ -0,0 +1,330 @@
+/* eslint-disable no-undef */
+const Setter = {
+ NONE: "none",
+ UPPER: 'upper',
+ LOWER: 'lower'
+};
+
+const shortBuzzTimeInMs = 80;
+const longBuzzTimeInMs = 400;
+
+let upperLimit = 130;
+let upperLimitChanged = true;
+
+let lowerLimit = 100;
+let lowerLimitChanged = true;
+
+let limitSetter = Setter.NONE;
+
+let currentHeartRate = 0;
+let hrConfidence = -1;
+let hrChanged = true;
+let confidenceChanged = true;
+
+let setterHighlightTimeout;
+
+function renderUpperLimitBackground() {
+ g.setColor(1,0,0);
+ g.fillRect(125,40, 210, 70);
+ g.fillRect(180,70, 210, 200);
+
+ //Round top left corner
+ g.fillEllipse(115,40,135,70);
+
+ //Round top right corner
+ g.setColor(0,0,0);
+ g.fillRect(205,40, 210, 45);
+ g.setColor(1,0,0);
+ g.fillEllipse(190,40,210,50);
+
+ //Round inner corner
+ g.fillRect(174,71, 179, 76);
+ g.setColor(0,0,0);
+ g.fillEllipse(160,71,179,82);
+
+ //Round bottom
+ g.setColor(1,0,0);
+ g.fillEllipse(180,190, 210, 210);
+}
+
+function renderLowerLimitBackground() {
+ g.setColor(0,0,1);
+ g.fillRect(10, 180, 100, 210);
+ g.fillRect(10, 50, 40, 180);
+
+ //Rounded top
+ g.setColor(0,0,1);
+ g.fillEllipse(10,40, 40, 60);
+
+ //Round bottom right corner
+ g.setColor(0,0,1);
+ g.fillEllipse(90,180,110,210);
+
+ //Round inner corner
+ g.setColor(0,0,1);
+ g.fillRect(40,175,45,180);
+ g.setColor(0,0,0);
+ g.fillEllipse(41,170,60,179);
+
+ //Round bottom left corner
+ g.setColor(0,0,0);
+ g.fillRect(10,205, 15, 210);
+ g.setColor(0,0,1);
+ g.fillEllipse(10,200,30,210);
+}
+
+function drawTrainingHeartRate() {
+ //Only redraw if the display is on
+ if (Bangle.isLCDOn()) {
+ renderUpperLimit();
+
+ renderCurrentHeartRate();
+
+ renderLowerLimit();
+
+ renderConfidenceBars();
+ }
+
+ buzz();
+}
+
+function renderUpperLimit() {
+ if(!upperLimitChanged) { return; }
+
+ g.setColor(1,0,0);
+ g.fillRect(125,40, 210, 70);
+
+ if(limitSetter === Setter.UPPER){
+ g.setColor(255,255, 0);
+ } else {
+ g.setColor(255,255,255);
+ }
+ g.setFontVector(13);
+ g.drawString("Upper: " + upperLimit, 125, 50);
+
+ upperLimitChanged = false;
+}
+
+function renderCurrentHeartRate() {
+ if(!hrChanged) { return; }
+
+ g.setColor(255,255,255);
+ g.fillRect(55, 110, 165, 150);
+
+ g.setColor(0,0,0);
+ g.setFontVector(24);
+ g.setFontAlign(1, -1, 0);
+ g.drawString(currentHeartRate, 130, 117);
+
+ //Reset alignment to defaults
+ g.setFontAlign(-1, -1, 0);
+
+ hrChanged = false;
+}
+
+function renderLowerLimit() {
+ if(!lowerLimitChanged) { return; }
+
+ g.setColor(0,0,1);
+ g.fillRect(10, 180, 100, 210);
+
+ if(limitSetter === Setter.LOWER){
+ g.setColor(255,255, 0);
+ } else {
+ g.setColor(255,255,255);
+ }
+ g.setFontVector(13);
+ g.drawString("Lower: " + lowerLimit, 20,190);
+
+ lowerLimitChanged = false;
+}
+
+function renderConfidenceBars(){
+ if(!confidenceChanged) { return; }
+
+ if(hrConfidence >= 85){
+ g.setColor(0, 255, 0);
+ } else if (hrConfidence >= 50) {
+ g.setColor(255, 255, 0);
+ } else if(hrConfidence >= 0){
+ g.setColor(255, 0, 0);
+ } else {
+ g.setColor(255, 255, 255);
+ }
+
+ g.fillRect(45, 110, 55, 150);
+ g.fillRect(165, 110, 175, 150);
+
+ confidenceChanged = false;
+}
+
+function renderPlusMinusIcons() {
+ if (limitSetter === Setter.NONE) {
+ g.setColor(0, 0, 0);
+ } else {
+ g.setColor(1, 1, 1);
+ }
+
+ g.setFontVector(14);
+
+ //+ for Btn1
+ g.drawString("+", 222, 50);
+
+ //- for Btn3
+ g.drawString("-", 222,165);
+
+ return;
+}
+
+function renderHomeIcon() {
+ //Home for Btn2
+ g.setColor(1, 1, 1);
+ g.drawLine(220, 118, 227, 110);
+ g.drawLine(227, 110, 234, 118);
+
+ g.drawPoly([222,117,222,125,232,125,232,117], false);
+ g.drawRect(226,120,229,125);
+}
+
+function buzz() {
+ // Do not buzz if not confident
+ if(hrConfidence < 85) { return; }
+
+ if(currentHeartRate > upperLimit)
+ {
+ Bangle.buzz(shortBuzzTimeInMs);
+ setTimeout(() => { Bangle.buzz(shortBuzzTimeInMs); }, shortBuzzTimeInMs * 2);
+ }
+
+ if(currentHeartRate < lowerLimit)
+ {
+ Bangle.buzz(longBuzzTimeInMs);
+ }
+}
+
+function onHrm(hrm){
+ if(currentHeartRate !== hrm.bpm){
+ currentHeartRate = hrm.bpm;
+ hrChanged = true;
+ }
+
+ if(hrConfidence !== hrm.confidence) {
+ hrConfidence = hrm.confidence;
+ confidenceChanged = true;
+ }
+}
+
+function setLimitSetterToLower() {
+ resetHighlightTimeout();
+
+ limitSetter = Setter.LOWER;
+
+ upperLimitChanged = true;
+ lowerLimitChanged = true;
+
+ renderUpperLimit();
+ renderLowerLimit();
+ renderPlusMinusIcons();
+}
+
+function setLimitSetterToUpper() {
+ resetHighlightTimeout();
+
+ limitSetter = Setter.UPPER;
+
+ upperLimitChanged = true;
+ lowerLimitChanged = true;
+
+ renderLowerLimit();
+ renderUpperLimit();
+ renderPlusMinusIcons();
+}
+
+function setLimitSetterToNone() {
+ limitSetter = Setter.NONE;
+
+ upperLimitChanged = true;
+ lowerLimitChanged = true;
+
+ renderLowerLimit();
+ renderUpperLimit();
+ renderPlusMinusIcons();
+}
+
+function incrementLimit() {
+ resetHighlightTimeout();
+
+ if (limitSetter === Setter.UPPER) {
+ upperLimit++;
+ renderUpperLimit();
+ upperLimitChanged = true;
+ } else if(limitSetter === Setter.LOWER) {
+ lowerLimit++;
+ renderLowerLimit();
+ lowerLimitChanged = true;
+ }
+}
+
+function decrementLimit(){
+ resetHighlightTimeout();
+
+ if (limitSetter === Setter.UPPER) {
+ upperLimit--;
+ renderUpperLimit();
+ upperLimitChanged = true;
+ } else if(limitSetter === Setter.LOWER) {
+ lowerLimit--;
+ renderLowerLimit();
+ lowerLimitChanged = true;
+ }
+}
+
+function resetHighlightTimeout() {
+ if (setterHighlightTimeout) {
+ clearTimeout(setterHighlightTimeout);
+ }
+
+ setterHighlightTimeout = setTimeout(setLimitSetterToNone, 2000);
+}
+
+// Show launcher when middle button pressed
+function switchOffApp(){
+ Bangle.setHRMPower(0);
+ Bangle.showLauncher();
+}
+
+// special function to handle display switch on
+Bangle.on('lcdPower', (on) => {
+ g.clear();
+ if (on) {
+ Bangle.drawWidgets();
+
+ renderHomeIcon();
+ renderLowerLimitBackground();
+ renderUpperLimitBackground();
+ lowerLimitChanged = true;
+ upperLimitChanged = true;
+ drawTrainingHeartRate();
+ }
+});
+
+Bangle.setHRMPower(1);
+Bangle.on('HRM', onHrm);
+
+setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
+setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
+setWatch(decrementLimit, BTN3, {edge:"rising", debounce:50, repeat:true});
+setWatch(setLimitSetterToLower, BTN4, {edge:"rising", debounce:50, repeat:true});
+setWatch(setLimitSetterToUpper, BTN5, { edge: "rising", debounce: 50, repeat: true });
+
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+//drawTrainingHeartRate();
+
+renderHomeIcon();
+renderLowerLimitBackground();
+renderUpperLimitBackground();
+
+// refesh every sec
+setInterval(drawTrainingHeartRate, 1000);
diff --git a/apps/wohrm/app.png b/apps/wohrm/app.png
new file mode 100644
index 000000000..8f9c0ea5d
Binary files /dev/null and b/apps/wohrm/app.png differ
diff --git a/browserconfig.xml b/browserconfig.xml
new file mode 100644
index 000000000..13b6c7911
--- /dev/null
+++ b/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #5755d9
+
+
+
diff --git a/css/pwa.css b/css/pwa.css
new file mode 100644
index 000000000..8e78581bf
--- /dev/null
+++ b/css/pwa.css
@@ -0,0 +1,24 @@
+.hidden {
+ display: none !important;
+}
+
+#installContainer {
+ position: absolute;
+ bottom: 1em;
+ display: flex;
+ justify-content: center;
+ width: 100%;
+}
+
+#installContainer button {
+ background-color: inherit;
+ border: 1px solid white;
+ color: white;
+ font-size: 1em;
+ padding: 0.75em;
+}
+
+.floating {
+ position: fixed;
+
+}
diff --git a/favicon.ico b/favicon.ico
index 24ae65966..8b736ee82 100644
Binary files a/favicon.ico and b/favicon.ico differ
diff --git a/img/android-chrome-192x192.png b/img/android-chrome-192x192.png
new file mode 100644
index 000000000..a4dff3bb5
Binary files /dev/null and b/img/android-chrome-192x192.png differ
diff --git a/img/android-chrome-512x512.png b/img/android-chrome-512x512.png
new file mode 100644
index 000000000..f89cbfb31
Binary files /dev/null and b/img/android-chrome-512x512.png differ
diff --git a/img/apple-touch-icon.png b/img/apple-touch-icon.png
new file mode 100644
index 000000000..2330e0fdf
Binary files /dev/null and b/img/apple-touch-icon.png differ
diff --git a/img/favicon-16x16.png b/img/favicon-16x16.png
new file mode 100644
index 000000000..cb68aa50e
Binary files /dev/null and b/img/favicon-16x16.png differ
diff --git a/img/favicon-32x32.png b/img/favicon-32x32.png
new file mode 100644
index 000000000..cc7b68d98
Binary files /dev/null and b/img/favicon-32x32.png differ
diff --git a/img/mstile-150x150.png b/img/mstile-150x150.png
new file mode 100644
index 000000000..015d36eae
Binary files /dev/null and b/img/mstile-150x150.png differ
diff --git a/img/safari-pinned-tab.svg b/img/safari-pinned-tab.svg
new file mode 100644
index 000000000..10512424f
--- /dev/null
+++ b/img/safari-pinned-tab.svg
@@ -0,0 +1,100 @@
+
+
+
diff --git a/index.html b/index.html
index 0c528c5a7..f922c7556 100644
--- a/index.html
+++ b/index.html
@@ -6,6 +6,16 @@
+
+
+
+
+
+
+
+
+
+
Bangle.js App Loader