diff --git a/apps.json b/apps.json
index 040ee6e22..7369516f6 100644
--- a/apps.json
+++ b/apps.json
@@ -845,7 +845,7 @@
{
"id": "weather",
"name": "Weather",
- "version": "0.13",
+ "version": "0.14",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
@@ -5374,5 +5374,19 @@
{"name":"sonicclk.app.js","url":"app.js"},
{"name":"sonicclk.img","url":"app-icon.js","evaluate":true}
]
+ },
+ {
+ "id": "touchmenu",
+ "name": "TouchMenu",
+ "version": "0.01",
+ "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2",
+ "screenshots": [{"url":"touchmenu.gif"}],
+ "icon": "touchmenu.png",
+ "type": "bootloader",
+ "tags": "tool",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"touchmenu.boot.js","url":"touchmenu.js"},
+ ]
}
]
diff --git a/apps/banglerun/interface.html b/apps/banglerun/interface.html
index 403f28258..6388d3b65 100644
--- a/apps/banglerun/interface.html
+++ b/apps/banglerun/interface.html
@@ -68,7 +68,7 @@ ${track.map(pt=>` ${pt.distance}\n`).join("")}
function saveGPX(track, title) {
var gpx = `
-
+
diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html
index 42aa4e16d..0535b2d51 100644
--- a/apps/recorder/interface.html
+++ b/apps/recorder/interface.html
@@ -16,7 +16,7 @@ function saveKML(track,title) {
${track[0].Heartrate!==undefined ? `
Heart Rate
- `:``}
+ `:``}
${track[0].Steps!==undefined ? `
Step Count
`:``}
@@ -25,7 +25,7 @@ ${track[0].Core!==undefined ? `
`:``}
${track[0].Skin!==undefined ? `
Skin Temp
- `:``}
+ `:``}
@@ -49,7 +49,7 @@ ${track.map(pt=>` ${0|pt.Core}\n`).join("")}
`:``}
${track[0].Skin!==undefined ? `
${track.map(pt=>` ${0|pt.Skin}\n`).join("")}
- `:``}
+ `:``}
@@ -72,8 +72,7 @@ ${track.map(pt=>` ${0|pt.Skin}\n`).join("")}
function saveGPX(track, title) {
var gpx = `
-
-
+
diff --git a/apps/touchmenu/ChangeLog b/apps/touchmenu/ChangeLog
new file mode 100644
index 000000000..c5277e465
--- /dev/null
+++ b/apps/touchmenu/ChangeLog
@@ -0,0 +1 @@
+0.01: App launched
diff --git a/apps/touchmenu/README.md b/apps/touchmenu/README.md
new file mode 100644
index 000000000..0e81f3755
--- /dev/null
+++ b/apps/touchmenu/README.md
@@ -0,0 +1,40 @@
+# TouchMenu
+
+A redesign of the built-in `E.showMenu()` to take advantage of the full touch screen on the Bangle.js 2.
+
+
+
+## Features
+
+- All of the features of the built-in `E.showMenu()`
+- Icon support for menu items:
+ ```javascript
+ menu.items[0].icon = Graphics.createImage(...);
+ ```
+- Custom accent colors:
+ ```javascript
+ E.showMenu({
+ "": {
+ cAB: g.theme.bg2, // Accent background
+ cAF: g.theme.fg2 // Accent foreground
+ }
+ })
+ ```
+- Automatic back button detection - name a button `< Back` and it will be given a special position and icon
+
+## Controls
+
+- Scroll through the options
+- Tap on an option to select it
+- Tap on a button again to use it
+- Tap on a selected Boolean to toggle it
+- Tap on a selected number to change - tap the right side of the screen to decrease, left side to increase
+- If detected, tap on the back button in the upper left to go back
+
+## Requests
+
+Contact information is on my website: [kyleplo](https://kyleplo.com)
+
+## Creator
+
+[kyleplo](https://kyleplo.com)
diff --git a/apps/touchmenu/touchmenu.boot.js b/apps/touchmenu/touchmenu.boot.js
new file mode 100644
index 000000000..93a0ba1c8
--- /dev/null
+++ b/apps/touchmenu/touchmenu.boot.js
@@ -0,0 +1,197 @@
+E.showMenu = function(items) {
+ const gw = g.getWidth();
+ const gh = g.getHeight();
+ Bangle.removeAllListeners("drag");
+ if(!items){
+ delete m;
+ g.clearRect(0, 30, gw, gh - 30);
+ return false;
+ }
+ var loc = require("locale");
+ var m = {
+ info: {
+ title: "Menu",
+ cB: g.theme.bg,
+ cF: g.theme.fg,
+ cHB: g.theme.bgH,
+ cHF: g.theme.fgH,
+ cAB: g.theme.bg2,
+ cAF: g.theme.fg2,
+ predraw : () => {},
+ preflip : () => {}
+ },
+ scroll: 0,
+ items: [],
+ selected: -1,
+ draw: () => {
+ g.reset().setFont('12x20');
+ m.info.predraw(g);
+ g.setColor(m.info.cB).fillRect(0, 50, gw, gh - 30).setColor(m.info.cF);
+ m.items.forEach((e, i) => {
+ const s = (i * 48) - m.scroll + 50;
+ if(s < 30 || s > gh - 74){
+ return false;
+ }
+ if(i == m.selected){
+ g.setColor(m.info.cHB).fillRect(0, s, gw, Math.min(s + 48, gh - 30)).setColor(m.info.cHF);
+ }else{
+ g.setColor(m.info.cF);
+ }
+ g.drawString(e.title, (e.icon ? 30 : 10), s + 5);
+ if(e.icon){
+ g.drawImage(e.icon, 5, s + 5);
+ }
+ if(e.type && s < gh - 72){
+ if(e.format){
+ g.setFontAlign(1, -1, 0).drawString(e.format(e.value), gw - 10, s + 25).setFontAlign(-1, -1, 0);
+ }else{
+ g.setFontAlign(1, -1, 0).drawString(e.value, gw - 10, s + 25).setFontAlign(-1, -1, 0);
+ }
+ }
+ });
+ g.setColor(m.info.cAB).fillRect(0, 30, gw, 50);
+ g.setColor(m.info.cAF).drawString(m.info.title, (m.back ? 30 : 10), 32);
+ if(m.back){
+ g.drawLine(5, 40, 20, 40);
+ g.drawLine(5, 40, 15, 33);
+ g.drawLine(5, 40, 15, 47);
+ }
+ m.info.preflip(g, m.scroll > 0, m.scroll < (m.items.length - 1) * 48);
+ },
+ select: (x, y) => {
+ if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0)){
+ if(y){
+ if(y < 50 || y > gh - 30){
+ return false;
+ }else{
+ m.selected = Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0);
+ }
+ }else{
+ m.selected = Math.floor(m.scroll / 48);
+ }
+ m.draw();
+ }else{
+ if(m.items[m.selected].type && m.items[m.selected].type === "boolean"){
+ m.items[m.selected].value = !m.items[m.selected].value;
+ m.items[m.selected].onchange(m.items[m.selected].value);
+ m.draw();
+ }else if(m.items[m.selected].type && m.items[m.selected].type === "number"){
+ if(x && x < (gw / 2)){
+ m.items[m.selected].value = m.items[m.selected].value - (m.items[m.selected].step ? m.items[m.selected].step : 1);
+ }else{
+ m.items[m.selected].value = m.items[m.selected].value + (m.items[m.selected].step ? m.items[m.selected].step : 1);
+ }
+ if(m.items[m.selected].value > (m.items[m.selected].max ? m.items[m.selected].max : Infinity)){
+ m.items[m.selected].value = m.items[m.selected].min ? m.items[m.selected].min : 0;
+ }
+ if(m.items[m.selected].value < (m.items[m.selected].min ? m.items[m.selected].min : 0)){
+ m.items[m.selected].value = m.items[m.selected].max ? m.items[m.selected].max : 10;
+ }
+ m.items[m.selected].onchange(m.items[m.selected].value);
+ m.draw();
+ }else{
+ if(m.items[m.selected]){
+ m.items[m.selected]();
+ }
+ }
+ }
+ },
+ move: d => {
+ m.scroll += (d * 48);
+ m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48);
+ m.selected = Math.max(Math.min(Math.floor((m.scroll - 50) / 48), m.items.length - 1), 0);
+ m.draw();
+ },
+ };
+ Object.keys(items).forEach(i => {
+ if(i == ""){
+ m.info = Object.assign(m.info, items[i]);
+ }else if(i === "< Back" && items[i]){
+ m.back = items[i];
+ }else if(items[i]){
+ m.items.push(items[i]);
+ m.items[m.items.length - 1].title = loc.translate(i);
+ if(items[i].hasOwnProperty("value")){
+ if(typeof items[i].value === "boolean"){
+ m.items[m.items.length - 1].type = "boolean";
+ }else{
+ m.items[m.items.length - 1].type = "number";
+ }
+ }
+ }
+ });
+ m.info.title = loc.translate(m.info.title);
+ m.draw();
+ Bangle.on("drag", d => {
+ if(!d.b){
+ return false;
+ }
+ if(d.dx == 0 && d.dy == 0){
+ if(d.x < 30 && d.y < 50){
+ m.back();
+ return false;
+ }
+ m.select(d.x, d.y);
+ }else{
+ m.selected = -1;
+ m.scroll -= d.dy;
+ m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48);
+ m.draw();
+ }
+ });
+ return m;
+};
+
+E.showAlert = function (e, t){
+ if(!e){
+ E.showMenu();
+ return false;
+ }
+ return new Promise(r => {
+ const menu = {
+ "": {
+ "title": (t ? t : "Alert")
+ },
+ Ok: () => {
+ E.showMenu();
+ r();
+ }
+ };
+ menu[e] = () => {};
+ E.showMenu(menu);
+ });
+};
+E.showMessage = E.showAlert;
+
+E.showPrompt = function (e, t){
+ if(!e){
+ E.showMenu();
+ return false;
+ }
+ return new Promise(r => {
+ const menu = {
+ "": {
+ "title": (t && t.title ? t.title : "Choose")
+ }
+ };
+ menu[e] = () => {};
+ if(t && t.buttons){
+ Object.keys(t.buttons).forEach(b => {
+ menu[b] = () => {
+ E.showMenu();
+ r(t.buttons[b]);
+ };
+ });
+ }else{
+ menu.Yes = () => {
+ E.showMenu();
+ r(true);
+ };
+ menu.No = () => {
+ E.showMenu();
+ r(false);
+ };
+ }
+ E.showMenu(menu);
+ });
+};
diff --git a/apps/touchmenu/touchmenu.gif b/apps/touchmenu/touchmenu.gif
new file mode 100644
index 000000000..3df4b3462
Binary files /dev/null and b/apps/touchmenu/touchmenu.gif differ
diff --git a/apps/touchmenu/touchmenu.png b/apps/touchmenu/touchmenu.png
new file mode 100644
index 000000000..58733cbc7
Binary files /dev/null and b/apps/touchmenu/touchmenu.png differ
diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog
index fb6b28bf6..910cd4658 100644
--- a/apps/weather/ChangeLog
+++ b/apps/weather/ChangeLog
@@ -10,3 +10,4 @@
0.11: Bangle.js 2 support
0.12: Allow hiding the widget
0.13: Tweak Bangle.js 2 light theme colors
+0.14: Use weather condition code for icon selection
diff --git a/apps/weather/app.js b/apps/weather/app.js
index 8c8526fbd..efd9b0209 100644
--- a/apps/weather/app.js
+++ b/apps/weather/app.js
@@ -9,7 +9,7 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
{filly: 1},
{type: "h", filly: 0, c: [
{type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon",
- render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
+ render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
{type: "v", fillx: 1, c: [
{type: "h", pad: 2, c: [
{type: "txt", font: "18%", id: "temp", label: "000"},
@@ -47,6 +47,7 @@ function formatDuration(millis) {
function draw() {
layout.icon.txt = current.txt;
+ layout.icon.code = current.code;
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
layout.temp.label = temp[1];
layout.tempUnit.label = temp[2];
diff --git a/apps/weather/lib.js b/apps/weather/lib.js
index 7cb9a9f9b..8afdfe6df 100644
--- a/apps/weather/lib.js
+++ b/apps/weather/lib.js
@@ -16,7 +16,7 @@ function scheduleExpiry(json) {
function update(weatherEvent) {
let json = storage.readJSON('weather.json')||{};
-
+
if (weatherEvent) {
let weather = weatherEvent.clone();
delete weather.t;
@@ -55,7 +55,7 @@ scheduleExpiry(storage.readJSON('weather.json')||{});
exports.drawIcon = function(cond, x, y, r) {
var palette;
-
+
if (B2) {
if (g.theme.dark) {
palette = {
@@ -101,7 +101,7 @@ exports.drawIcon = function(cond, x, y, r) {
};
}
}
-
+
function drawSun(x, y, r) {
g.setColor(palette.sun);
g.fillCircle(x, y, r);
@@ -280,5 +280,44 @@ exports.drawIcon = function(cond, x, y, r) {
return drawUnknown;
}
- chooseIcon(cond)(x, y, r);
+ /*
+ * Choose weather icon to display based on weather conditition code
+ * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
+ */
+ function chooseIconByCode(code) {
+ const codeGroup = Math.round(code / 100);
+ switch (codeGroup) {
+ case 2: return drawThunderstorm;
+ case 3: return drawRain;
+ case 5:
+ switch (code) {
+ case 511: return drawSnow;
+ case 520: return drawShowerRain;
+ case 521: return drawShowerRain;
+ case 522: return drawShowerRain;
+ case 531: return drawShowerRain;
+ default: return drawRain;
+ }
+ break;
+ case 6: return drawSnow;
+ case 7: return drawMist;
+ case 8:
+ switch (code) {
+ case 800: return drawSun;
+ case 801: return drawFewClouds;
+ case 802: return drawCloud;
+ default: return drawBrokenClouds;
+ }
+ break;
+ default: return drawUnknown;
+ }
+ }
+
+ if (cond.code && cond.code > 0) {
+ chooseIconByCode(cond.code)(x, y, r);
+ } else {
+ chooseIcon(cond.txt)(x, y, r);
+ }
+
+
};