mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into app/sonic-clock
commit
39f16b5e96
16
apps.json
16
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"},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -68,7 +68,7 @@ ${track.map(pt=>` <gx:value>${pt.distance}</gx:value>\n`).join("")}
|
|||
|
||||
function saveGPX(track, title) {
|
||||
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<metadata>
|
||||
<time>${track[0].date.toISOString()}</time>
|
||||
</metadata>
|
||||
|
|
|
@ -16,7 +16,7 @@ function saveKML(track,title) {
|
|||
<Schema id="schema">
|
||||
${track[0].Heartrate!==undefined ? `<gx:SimpleArrayField name="heartrate" type="int">
|
||||
<displayName>Heart Rate</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
</gx:SimpleArrayField>`:``}
|
||||
${track[0].Steps!==undefined ? `<gx:SimpleArrayField name="steps" type="int">
|
||||
<displayName>Step Count</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
|
@ -25,7 +25,7 @@ ${track[0].Core!==undefined ? `<gx:SimpleArrayField name="core" type="int">
|
|||
</gx:SimpleArrayField>`:``}
|
||||
${track[0].Skin!==undefined ? `<gx:SimpleArrayField name="skin" type="int">
|
||||
<displayName>Skin Temp</displayName>
|
||||
</gx:SimpleArrayField>`:``}
|
||||
</gx:SimpleArrayField>`:``}
|
||||
|
||||
</Schema>
|
||||
<Folder>
|
||||
|
@ -49,7 +49,7 @@ ${track.map(pt=>` <gx:value>${0|pt.Core}</gx:value>\n`).join("")}
|
|||
</gx:SimpleArrayData>`:``}
|
||||
${track[0].Skin!==undefined ? `<gx:SimpleArrayData name="skin">
|
||||
${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
|
||||
</gx:SimpleArrayData>`:``}
|
||||
</gx:SimpleArrayData>`:``}
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
|
@ -72,8 +72,7 @@ ${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
|
|||
|
||||
function saveGPX(track, title) {
|
||||
var gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<gpx creator="Bangle.js" version="1.1">
|
||||
<gpx creator="Bangle.js" version="1.1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
|
||||
<metadata>
|
||||
<time>${track[0].Time.toISOString()}</time>
|
||||
</metadata>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: App launched
|
|
@ -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)
|
|
@ -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);
|
||||
});
|
||||
};
|
Binary file not shown.
After Width: | Height: | Size: 602 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue