1
0
Fork 0

Merge branch 'espruino:master' into master

master
Peer David 2021-12-17 16:28:06 +01:00 committed by GitHub
commit 0b087d86c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1039 additions and 361 deletions

View File

@ -77,7 +77,7 @@
{
"id": "messages",
"name": "Messages",
"version": "0.13",
"version": "0.14",
"description": "App to display notifications from iOS and Gadgetbridge",
"icon": "app.png",
"type": "app",
@ -132,7 +132,7 @@
{
"id": "health",
"name": "Health Tracking",
"version": "0.08",
"version": "0.09",
"description": "Logs health data and provides an app to view it (requires firmware 2v10.100 or later)",
"icon": "app.png",
"tags": "tool,system,health",
@ -167,7 +167,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.37",
"version": "0.38",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",
@ -554,7 +554,7 @@
{
"id": "impwclock",
"name": "Imprecise Word Clock",
"version": "0.03",
"version": "0.04",
"description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.",
"icon": "clock-impword.png",
"type": "clock",
@ -1590,7 +1590,7 @@
{
"id": "widpedom",
"name": "Pedometer widget",
"version": "0.19",
"version": "0.20",
"description": "Daily pedometer widget",
"icon": "widget.png",
"type": "widget",
@ -3478,8 +3478,8 @@
{
"id": "speedalt2",
"name": "GPS Adventure Sports II",
"shortName": "GPS Adv Sport II",
"version": "0.07",
"shortName":"GPS Adv Sport II",
"version":"1.10",
"description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
"icon": "app.png",
"type": "app",
@ -4014,11 +4014,12 @@
{
"id": "thermom",
"name": "Thermometer",
"version": "0.04",
"description": "Displays the current temperature in degree Celsius, updated every 20 seconds",
"version": "0.05",
"description": "Displays the current temperature in degree Celsius/Fahrenheit (depending on locale), updates every 10 seconds with average of last 5 readings.",
"icon": "app.png",
"tags": "tool",
"supports": ["BANGLEJS", "BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"allow_emulator": true,
"storage": [
{"name":"thermom.app.js","url":"app.js"},
@ -4080,7 +4081,7 @@
"id": "thermomF",
"name": "Fahrenheit Temp",
"version": "0.01",
"description": "A modification of the Thermometer App to display temprature in Fahrenheit",
"description": "[NOT RECOMMENDED] A modification of the Thermometer App to display temprature in Fahrenheit. Please use the 'Thermometer App' and install 'Languages' to get the temperature in the correct format for your locale.",
"icon": "thermf.png",
"tags": "tool",
"supports": ["BANGLEJS"],
@ -4209,7 +4210,7 @@
"id": "pastel",
"name": "Pastel Clock",
"shortName": "Pastel",
"version": "0.08",
"version": "0.09",
"description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
"icon": "pastel.png",
"dependencies": {"mylocation":"app", "widpedom":"app"},
@ -4724,8 +4725,9 @@
"id": "pebble",
"name": "Pebble Clock",
"shortName": "Pebble",
"version": "0.04",
"version": "0.06",
"description": "A pebble style clock to keep the rebellion going",
"dependencies": {"widpedom":"app"},
"readme": "README.md",
"icon": "pebble.png",
"screenshots": [{"url":"pebble_screenshot.png"}],
@ -4859,10 +4861,10 @@
"id": "ptlaunch",
"name": "Pattern Launcher",
"shortName": "Pattern Launcher",
"version": "0.10",
"version": "0.11",
"description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png",
"screenshots": [{"url":"main_menu_add.png"}, {"url":"add_pattern.png"}, {"url":"select_app.png"}, {"url":"main_menu_manage.png"}, {"url":"manage_patterns.png"}],
"screenshots": [{"url":"manage_patterns_light.png"}],
"tags": "tools",
"supports": ["BANGLEJS2"],
"readme": "README.md",
@ -4877,11 +4879,11 @@
"id": "rebble",
"name": "Rebble Clock",
"shortName": "Rebble",
"version": "0.02",
"version": "0.03",
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
"readme": "README.md",
"icon": "rebble.png",
"dependencies": {"mylocation":"app"},
"dependencies": {"mylocation":"app", "widpedom":"app"},
"screenshots": [{"url":"screenshot_rebble.png"}],
"type": "clock",
"tags": "clock",
@ -4919,6 +4921,7 @@
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"clicompleteclk.app.js","url":"app.js"},
{"name":"clicompleteclk.img","url":"app-icon.js","evaluate":true},
{"name":"clicompleteclk.settings.js","url":"settings.js"}
],
@ -4942,7 +4945,7 @@
{ "id": "pooqround",
"name": "pooq Round watch face",
"shortName":"pooq Round",
"version":"0.00",
"version":"0.01",
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
"icon": "app.png",
"type": "clock",
@ -4978,8 +4981,8 @@
"id": "showimg",
"name": "simple image viewer",
"shortName":"showImage",
"version":"0.1",
"description": "Displays the image file in showimage.user.img. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.",
"version":"0.2",
"description": "Displays the image in \"showimg.user.img\". The file has to be uploaded via the espruino IDE. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.",
"icon": "app.png",
"tags": "tool",
"supports" : ["BANGLEJS2"],
@ -5004,5 +5007,60 @@
{"name":"lapcounter.app.js","url":"app.js"},
{"name":"lapcounter.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "pebbled",
"name": "Pebble Clock with distance",
"shortName": "Pebble + distance",
"version": "0.1",
"description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).",
"readme": "README.md",
"icon": "pebbled.png",
"screenshots": [{"url":"pebble_screenshot.png"}],
"type": "clock",
"tags": "clock,distance",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"pebbled.app.js","url":"pebbled.app.js"},
{"name":"pebbled.settings.js","url":"pebbled.settings.js"},
{"name":"pebbled.img","url":"pebbled.icon.js","evaluate":true}
]
},
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
"version":"0.02",
"description": "A clock with circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"dependencies": {"widpedom":"app"},
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator":true,
"readme": "README.md",
"storage": [
{"name":"circlesclock.app.js","url":"app.js"},
{"name":"circlesclock.img","url":"app-icon.js","evaluate":true},
{"name":"circlesclock.settings.js","url":"settings.js"}
],
"data": [
{"name":"circlesclock.json"}
]
},
{
"id": "ltherm",
"name": "Localized Thermometer",
"shortName": "Thermometer",
"version": "0.01",
"description": "Displays the current temperature in localized units.",
"icon": "thermf.png",
"tags": "tool",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"ltherm.app.js","url":"app.js"},
{"name":"ltherm.img","url":"icon.js","evaluate":true}
]
}
]

View File

@ -1,4 +1,4 @@
0.04: Fix tapping at very bottom of list, exit on inactivity
0.03: Add "Calculating" placeholder, update JSON save format
0.02: Fix JSON save format
0.01: First release
0.02: Fix JSON save format
0.03: Add "Calculating" placeholder, update JSON save format
0.04: Fix tapping at very bottom of list, exit on inactivity

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAMGAAAAAAYDAAAAAAwBgAAAABgAwAAAABAAQAAAABAAQAAAABAAQAAAABAAQAAAABAAQAAAABgAwAAAAAwBgAAAAAYDAAAAAAMGAAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAP+AAAAAAf/AAAAAA//gAAAAB//wAAAAB//wAAAAB//wAAAAB//wAAAAB//wAAAAB//wAAAAB//wAAAAA//gAAAAAf/AAAAAAP+AAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
require("heatshrink").decompress(atob("mEwgIurg/wAocMjAFDjEMIAkGAodggYFDoBLEAq4jFF4o7FI4pTFOLsP/AFDj/8Aoc//wFDv//As4vFHYpHFOLoAPA=="))

View File

@ -0,0 +1,2 @@
0.01: New clock
0.02: Fix icon & add battery warn functionality

View File

@ -0,0 +1,21 @@
# Circles clock
A clock with circles for different data at the bottom in a probably familiar style
It shows besides time, date and day of week the following information:
* Steps (requires [pedometer widget](https://banglejs.com/apps/#pedometer))
* Heart rate (when screen is on and unlocked)
* Battery (including charging and battery low)
## Screenshot
![Screenshot](screenshot.png)
## TODO
* Show weather information
## Creator
Marco ([myxor](https://github.com/myxor))
## Icons
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcCIf4ALv///gFCv0Agf+CJP/wAODAwPAEpAjCCIX8h4RMj/+g/8gP4CA4LBDoP/GpkH8EP4/8LIIRMAQIOCCJU/CgQOBEwMPI5ARCR4YRJgP/gB3CI5Z0CCIiABfHRfEj+BAoN+n4FBLIkP/8chwRBx5cC//8v4REhytDgYRCv//8fxEYwRFgfxA4I1FRgI1D+JHE/7FINZzCBAAc4CRU4/kB44FCjgRKLQRlBPQ4RHgYCB/jpBABB6BPoKzBCJYAGuD/vAB1JkgLJm3bAgUCpMnwDdCPwIFChu27dgAoMSCIP+FAQRB+AFBtoRBtgFByQCBRIIoBAocDtonBAQWQdgXAgVIAocDEAUNwEEyEHBYUSoE//gRCsI7BxvACIILDCIcBCIYFCCJ3/wIRCIIYRBI4h6CAoJrDLJYRDDwJ9LAoKhBoMDUIcEgFwUIQREgUBaAcIkhPCAAQzBAAUBdIhhDAAMGCIkAkAFEdAQAFA=="))

235
apps/circlesclock/app.js Normal file
View File

@ -0,0 +1,235 @@
const locale = require("locale");
const heatshrink = require("heatshrink");
const shoesIcon = heatshrink.decompress(atob("h0OwYJGgmAAgUBkgECgVJB4cSoAUDyEBkARDpADBhMAyQRBgVAkgmDhIUDAAuQAgY1DAAYA="));
const heartIcon = heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
const powerIcon = heatshrink.decompress(atob("h0OwYQNsAED7AEDmwEDtu2AgUbtuABwXbBIUN23AAoYOCgEDFIgODABI"));
const powerIconGreen = heatshrink.decompress(atob("h0OwYQNkAEDpAEDiQEDkmSAgUJkmABwVJBIUEyVAAoYOCgEBFIgODABI"));
const powerIconRed = heatshrink.decompress(atob("h0OwYQNoAEDyAEDkgEDpIFDiVJBweSAgUJkmAAoYZDgQpEBwYAJA"));
const SETTINGS_FILE = "circlesclock.json";
let settings;
function loadSettings() {
settings = require("Storage").readJSON(SETTINGS_FILE, 1) || {
'maxHR': 200,
'stepGoal': 10000,
'batteryWarn': 30
};
}
const colorFg = '#fff';
const colorBg = '#000';
const colorGrey = '#808080';
const colorRed = '#ff0000';
const colorGreen = '#00ff00';
let hrtValue;
const h = g.getHeight();
const w = g.getWidth();
const hOffset = 30;
const h1 = Math.round(1 * h / 5 - hOffset);
const h2 = Math.round(3 * h / 5 - hOffset);
const h3 = Math.round(8 * h / 8 - hOffset);
const w1 = Math.round(w / 6);
const w2 = Math.round(3 * w / 6);
const w3 = Math.round(5 * w / 6);
const radiusOuter = 22;
const radiusInner = 16;
function draw() {
g.reset();
g.setColor(colorBg);
g.fillRect(0, 0, w, h);
// time
g.setFont("Vector:50");
g.setFontAlign(-1, -1);
g.setColor(colorFg);
g.drawString(locale.time(new Date(), 1), w / 10, h1 + 8);
// date & dow
g.setFont("Vector:20");
g.setFontAlign(-1, 0);
g.drawString(locale.date(new Date()), w / 10, h2);
g.drawString(locale.dow(new Date()), w / 10, h2 + 22);
// Steps circle
drawSteps();
// Heart circle
drawHeartRate();
// Battery circle
drawBattery();
}
function drawSteps() {
const steps = getSteps();
const blue = '#0000ff';
g.setColor(colorGrey);
g.fillCircle(w1, h3, radiusOuter);
const stepGoal = settings.stepGoal;
if (stepGoal > 0) {
let percent = steps / stepGoal;
if (stepGoal < steps) percent = 1;
drawGauge(w1, h3, percent, blue);
}
g.setColor(colorBg);
g.fillCircle(w1, h3, radiusInner);
g.fillPoly([w1, h3, w1 - 15, h3 + radiusOuter + 5, w1 + 15, h3 + radiusOuter + 5]);
g.setFont("Vector:12");
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(shortValue(steps), w1 + 2, h3);
g.drawImage(shoesIcon, w1 - 6, h3 + radiusOuter - 6);
}
function drawHeartRate() {
g.setColor(colorGrey);
g.fillCircle(w2, h3, radiusOuter);
if (hrtValue != undefined) {
const percent = hrtValue / settings.maxHR;
drawGauge(w2, h3, percent, colorRed);
}
g.setColor(colorBg);
g.fillCircle(w2, h3, radiusInner);
g.fillPoly([w2, h3, w2 - 15, h3 + radiusOuter + 5, w2 + 15, h3 + radiusOuter + 5]);
g.setFont("Vector:12");
g.setFontAlign(0, 0);
g.setColor(colorFg);
g.drawString(hrtValue != undefined ? hrtValue : "-", w2, h3);
g.drawImage(heartIcon, w2 - 6, h3 + radiusOuter - 6);
}
function drawBattery() {
const battery = E.getBattery();
const yellow = '#ffff00';
g.setColor(colorGrey);
g.fillCircle(w3, h3, radiusOuter);
if (battery > 0) {
const percent = battery / 100;
drawGauge(w3, h3, percent, yellow);
}
g.setColor(colorBg);
g.fillCircle(w3, h3, radiusInner);
g.fillPoly([w3, h3, w3 - 15, h3 + radiusOuter + 5, w3 + 15, h3 + radiusOuter + 5]);
g.setFont("Vector:12");
g.setFontAlign(0, 0);
let icon = powerIcon;
let color = colorFg;
if (Bangle.isCharging()) {
color = colorGreen;
icon = powerIconGreen;
}
else {
if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) {
color = colorRed;
icon = powerIconRed;
}
}
g.setColor(color);
g.drawString(battery + '%', w3, h3);
g.drawImage(icon, w3 - 6, h3 + radiusOuter - 6);
}
function radians(a) {
return a * Math.PI / 180;
}
function drawGauge(cx, cy, percent, color) {
let offset = 30;
let end = 300;
var i = 0;
var r = radiusInner + 3;
if (percent > 1) percent = 1;
var startrot = -offset;
var endrot = startrot - ((end - offset) * percent);
g.setColor(color);
// draw gauge
for (i = startrot; i > endrot; i -= 4) {
x = cx + r * Math.sin(radians(i));
y = cy + r * Math.cos(radians(i));
g.fillCircle(x, y, 4);
}
}
function shortValue(v) {
if (isNaN(v)) return '-';
if (v <= 999) return v;
if (v >= 1000 && v < 10000) {
v = Math.floor(v / 100) * 100;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
if (v >= 10000) {
v = Math.floor(v / 1000) * 1000;
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
}
}
function getSteps() {
if (WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps();
}
return 0;
}
Bangle.on('lock', function(isLocked) {
if (!isLocked) {
Bangle.setHRMPower(1, "watch");
} else {
Bangle.setHRMPower(0, "watch");
}
drawHeartRate();
drawSteps();
});
Bangle.on('HRM', function(hrm) {
//if(hrm.confidence > 90){
hrtValue = hrm.bpm;
if (Bangle.isLCDOn())
drawHeartRate();
//} else {
// hrtValue = undefined;
//}
});
g.clear();
Bangle.loadWidgets();
/*
* we are not drawing the widgets as we are taking over the whole screen
* so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared.
*/
for (let wd of WIDGETS) {
wd.draw = () => {};
wd.area = "";
}
loadSettings();
setInterval(draw, 60000);
draw();
Bangle.setUI("clock");

BIN
apps/circlesclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,43 @@
(function(back) {
const SETTINGS_FILE = "circlesclock.json";
const storage = require('Storage');
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
function save(key, value) {
settings[key] = value;
storage.write(SETTINGS_FILE, settings);
}
E.showMenu({
'': { 'title': 'circlesclock' },
'max heartrate': {
value: "maxHR" in settings ? settings.maxHR : 200,
min: 20,
max : 250,
step: 10,
format: x => {
return x;
},
onchange: x => save('maxHR', x),
},
'step goal': {
value: "stepGoal" in settings ? settings.stepGoal : 10000,
min: 2000,
max : 50000,
step: 2000,
format: x => {
return x;
},
onchange: x => save('stepGoal', x),
},
'battery warn lvl': {
value: "batteryWarn" in settings ? settings.batteryWarn : 30,
min: 10,
max : 100,
step: 10,
format: x => {
return x + '%';
},
onchange: x => save('batteryWarn', x),
},
'< Back': back,
});
});

View File

@ -1,2 +1,3 @@
0.01: New clock!
0.02: Load steps from Health Tracking app (if installed)
0.03: ...

View File

@ -1 +1 @@
0.1: New app
0.01: New app

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("/wA/AH4A/AH4A/ACmsAEQuMlcAAD0rGBQKBFr4ADGBOsqwvjqwvJRsCRFF/8Gg4ADEZYQEgwvWg8+AAgwKCJgvQDgoABF5IRMF5xEBJpBhGCJwvNDQM4AYMNAAQaBnCAFCJ4vNIwQeBAAkxQAwGCmIRFFwIRDF64dDgwGBgwRNF/4v/F/4v/F/4v/F/4dJmIdECIkxF7MHFwUHhoACg4eCAYIACCJ4vNDQIgCAAgICKwoROF5yAEAAgtFCKAvQJpAAICJgvQgEGg4ADFxIwCAAcGBYovRADov6qwvjqwvJ1gvjEoIvHGASRgRoIuJGAYAhFxQA/AH4A/AH4A/ABQ"))
require("heatshrink").decompress(atob("mEw4UA///A4N551ulxL/ACkK1QAG0ALBlNVAA1oBYOlBY9aBYO1BY9eBYOVBY9WBbf/+oIBr//BYlX//9BYN///VC599qtX6oBBqt9BYYRBr/1AIIdBBf4L/BY6bLZcb7MBau1BY9eBYOlBY9aBYMpBY9oBYMK1QAG0ALBAH4ASA"))

View File

@ -4,4 +4,4 @@
0.04: reset to clock after 2 mins of inactivity
0.05: add Bangle 2 version
0.06: Adds settings page (hide clocks or launchers)
0.06: Adds setting for directly launching app on touch for Bangle 2
0.07: Adds setting for directly launching app on touch for Bangle 2

File diff suppressed because one or more lines are too long

View File

@ -7,3 +7,4 @@
0.06: Fix daily health summary for movement (a line got deleted!)
0.07: Added coloured bar charts
0.08: Suppress bleed through of E.showMenu's when displaying bar charts
0.09: Fix file naming so months are 1-based (not 0) (fix #1119)

View File

@ -27,7 +27,7 @@ Bangle.on("health", health => {
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
function getRecordFN(d) {
return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw";
return "health-"+d.getFullYear()+"-"+(d.getMonth()+1)+".raw";
}
function getRecordIdx(d) {
return (DB_RECORDS_PER_DAY*(d.getDate()-1)) +

View File

@ -6,7 +6,7 @@ const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
function getRecordFN(d) {
return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw";
return "health-"+d.getFullYear()+"-"+(d.getMonth()+1)+".raw";
}
function getRecordIdx(d) {
return (DB_RECORDS_PER_DAY*(d.getDate()-1)) +

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -154,7 +154,8 @@ var locales = {
month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus", "< Back": "< Zurück" }
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
},
"en_US": {
lang: "en_US",
@ -327,13 +328,15 @@ var locales = {
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%y" }, // Sonntag, 1. März 2020 // 01.03.20
abmonth: "Jän,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
month: "Jänner,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
},
"en_IL": {
lang: "en_IL",
@ -369,7 +372,8 @@ var locales = {
month: "enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre",
abday: "dom,lun,mar,mié,jue,vie,sáb",
day: "domingo,lunes,martes,miércoles,jueves,viernes,sábado",
trans: { yes: "sí", Yes: "Sí", no: "no", No: "No", ok: "ok", on: "on", off: "off" }
trans: { yes: "sí", Yes: "Sí", no: "no", No: "No", ok: "ok", on: "on", off: "off",
"< Back": "< Atrás", "Delete": "Borrar ", "Mark Unread": "Marcar como no leído" }
},
"fr_BE": {
lang: "fr_BE",

3
apps/ltherm/README.md Normal file
View File

@ -0,0 +1,3 @@
# Thermometer
Localized Bangle.js 2 thermometer app. It also starts maintaining an average of the temperature to help lower the margin of error after 10 consecutive readings; due to the low quality die-thermometer.

25
apps/ltherm/app.js Normal file
View File

@ -0,0 +1,25 @@
function drawTemperature() {
g.reset(1).clearRect(0,24,g.getWidth(),g.getHeight());
g.setFont("6x8",2).setFontAlign(0,0);
var x = g.getWidth()/2;
var y = g.getHeight()/2 + 10;
g.drawString("Temp", x, y - 45);
g.setFontVector(70).setFontAlign(0,0);
var h = E.getTemperature();
if (avg.length < 10) {
avg[avg.length] = h;
} else {
avg.shift();
avg[avg.length] = h;
h = ((avg[0] + avg[1] + avg[2] + avg[3] + avg[4] + avg[5] + avg[6] + avg[7] + avg[8] + avg[9]) / 10);
}
var t = require('locale').temp(h);
g.drawString(t, x, y);
}
const avg = [];
setInterval(function() {
drawTemperature();
}, 2000);
E.showMessage("Loading...");
Bangle.loadWidgets();
Bangle.drawWidgets();

1
apps/ltherm/icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4AChGIxGAC6eIAQgARFgUIC9ReCAYJgSC7BHDF6gUBC6ovWI/5Hga/6P/ABsCkABDC/4XxkQXDkQuSAQwXPDQkAC6BBCkQDDC6MCmczFoIXQCQQXBDgQXP2EA2YXBncAhYXR3YXB3YXRCQWznYcCC6ICBAYYXPhYrBApAwPFyQqCIoYuRLwZgDAH4A/"))

BIN
apps/ltherm/thermf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -20,3 +20,4 @@
0.13: Add /*LANG*/ comments for internationalisation
Add 'Delete All' option to message options
Now update correctly when 'require("messages").clearAll()' is called
0.14: Hide widget when all unread notifications are dismissed from phone

View File

@ -30,6 +30,10 @@ exports.pushMessage = function(event) {
require("Storage").writeJSON("messages.json",messages);
// if in app, process immediately
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
// if we've removed the last new message, hide the widget
if (event.t=="remove" && !messages.some(m=>m.new)) {
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.hide();
}
// ok, saved now - we only care if it's new
if (event.t!="add") {
return;

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
require("heatshrink").decompress(atob("mEwwIHEgPAAocP+AFDv4FDgf/Aoc/AocB/4FDh4FEv4FEAgIFIDgQFR+FwAoeAAof8gAFDLoIFC/wyBAoQ4CAoXgAoh0CAtybCAoJPBAoahDAoMHAoicBAoM54EfAoJqCAoQUBAoYUBAoYCBAoXgZAIFC4AFCCgOAYYI1CZIRHB/AFDcwmAAoj9Dj6mCdoQaBAAYWDgA"))

View File

@ -6,3 +6,4 @@
0.06: Converted fonts to font modules
0.07: Added info line that cycles on BTN1/BTN3 (or vitual buttons on a bangle 2)
0.08: Added dependancy on MyLocation
0.09: Added dependancy on Pedometer Widget

View File

@ -3,3 +3,4 @@
0.03: Changed time+calendar font to LECO1976Regular, changed to slanting boot
0.04: Fix widget hiding code (fix #1046)
0.05: Fix typo in settings - Purple
0.06: Added dependancy on Pedometer Widget

1
apps/pebbled/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.1: first release

Binary file not shown.

22
apps/pebbled/README.md Normal file
View File

@ -0,0 +1,22 @@
# Pebble with distance and steps
- Forked from [Pebble](https://github.com/espruino/BangleApps/tree/master/apps/pebble)
- Added distance in km (kilometers) based on step length (can be changed in settings and is equal 0.75m by default)
- Battery warning changed to 15% instead of 30%
![](pebble_screenshot.png)
![](photo.jpg)
Initially written by: [Hugh Barney](https://github.com/hughbarney)
Forked and changed by [RomanistHere](https://github.com/RomanistHere)
For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
## How to measure step length
It's much easier than you think. When you're walking, just note number of current steps at two points and then see the distance in any map service. For example, your route from bus station to home. Write number of steps at bus station (let's say 3451) and when you entered your home (3921). You passed 3921 - 3451 = 470 steps. Then see the actual distance in Google maps. Let's say it shows 300 meters. So your step length (in settings) used in app should be 300 / 470 = 0.64. After you have set it, the displayed distance at the main screen should be more accurate.
## Plans
Make step length depend on height/sex/age for lazy ones who don't want to measure it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

129
apps/pebbled/pebbled.app.js Normal file
View File

@ -0,0 +1,129 @@
Graphics.prototype.setFontLECO1976Regular42 = function(scale) {
// Actual height 42 (41 - 0)
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAA/AAAAAAAAH/AAAAAAAA//AAAAAAAP//AAAAAAB///AAAAAAP///AAAAAB////AAAAAf////AAAAD////4AAAAf////AAAAH////4AAAA////+AAAAA////wAAAAA///+AAAAAA///gAAAAAA//8AAAAAAA//gAAAAAAA/4AAAAAAAA/AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////gD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4B/gH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAH+AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ERkmHyYmJiYmJCYmEQ=="), 60+(scale<<8)+(1<<16));
};
Graphics.prototype.setFontLECO1976Regular22 = function(scale) {
// Actual height 22 (21 - 0)
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22+(scale<<8)+(1<<16));
};
const SETTINGS_FILE = "pebbleDistance.json";
let settings;
function loadSettings() {
settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75};
}
var img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA=="));
const h = g.getHeight();
const w = g.getWidth();
const ha = 2*h/5 - 11;
const h2 = 3*h/5 - 19;
const h3 = 7*h/8 - 10;
let batteryWarning = false;
function draw() {
let date = new Date();
let da = date.toString().split(" ");
let timeStr = da[4].substr(0,5);
const t = 6;
const stps = getSteps();
// turn the warning on once we have dipped below 15%
if (E.getBattery() < 15)
batteryWarning = true;
// turn the warning off once we have dipped above 20%
if (E.getBattery() > 20)
batteryWarning = false;
g.reset();
g.setColor(settings.bg);
g.fillRect(0, 0, w, h2 - t);
// contrast bar
g.setColor(g.theme.fg);
g.fillRect(0, h2 - t, w, h2);
// day and steps
if (settings.color == 'Blue' || settings.color == 'Red')
g.setColor('#fff'); // white on blue or red best contrast
else
g.setColor('#000'); // otherwise black regardless of theme
g.setFontLECO1976Regular22();
g.setFontAlign(0, -1);
g.drawString(da[0].toUpperCase(), w/4, ha); // day of week
g.drawString(stps, 3*w/4, ha);
// time
// white on red for battery warning
g.setColor(!batteryWarning ? g.theme.bg : '#f00');
g.fillRect(0, h2, w, h3);
g.setFontLECO1976Regular42();
g.setFontAlign(0, -1);
g.setColor(!batteryWarning ? g.theme.fg : '#fff');
g.drawString(timeStr, w/2, h2 + 8);
// contrast bar
g.setColor(g.theme.fg);
g.fillRect(0, h3, w, h3 + t);
// the bottom
g.setColor(settings.bg);
g.fillRect(0, h3 + t, w, h);
g.setColor(settings.bg);
g.drawImage(img, w/2 + ((w/2) - 64)/2, -2, { scale: 1 });
drawCalendar(((w/2) - 42)/2, 11, 42, 4, da[2]);
// distance
if (settings.color == 'Blue' || settings.color == 'Red')
g.setColor('#fff'); // white on blue or red best contrast
else
g.setColor('#000'); // otherwise black regardless of theme
g.drawString((stps / 1000 * settings.avStep).toFixed(2) + ' KM', w/2, ha + 107);
}
// at x,y width:wi thicknes:th
function drawCalendar(x,y,wi,th,str) {
g.setColor(g.theme.fg);
g.fillRect(x, y, x + wi, y + wi);
g.setColor(g.theme.bg);
g.fillRect(x + th, y + th, x + wi - th, y + wi - th);
g.setColor(g.theme.fg);
let hook_t = 6;
// first calendar hook, one third in
g.fillRect(x + (wi/3) - (th/2), y - hook_t, x + wi/3 + th - (th/2), y + hook_t);
// second calendar hook, two thirds in
g.fillRect(x + (2*wi/3) -(th/2), y - hook_t, x + 2*wi/3 + th - (th/2), y + hook_t);
g.setFontLECO1976Regular22();
g.setFontAlign(0, 0);
g.drawString(str, x + wi/2, y + wi/2 + th);
}
function getSteps() {
if (WIDGETS.wpedom !== undefined) {
return WIDGETS.wpedom.getSteps();
}
return '0';
}
g.clear();
Bangle.loadWidgets();
/*
* we are not drawing the widgets as we are taking over the whole screen
* so we will blank out the draw() functions of each widget and change the
* area to the top bar doesn't get cleared.
*/
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
loadSettings();
setInterval(draw, 15000); // refresh every 15s
draw();
Bangle.setUI("clock");

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4kB78A/4ACj/fn8Rz/Or987+M38hv8Rn++z9Cn8y/9rj9Tr+diIAHiAxDgIOICpYADCywyNCxQYMSxouVGBIWODBDgRC7hGQJAwWRGAguSC7JICCyYXYJAQXWLyhICC94LKu8Gqk1gGD+AjQ//C6Ei2HS0AXQ/U2gEquFVC6Pyk8AlssmwXK3oGF+fi2Et0v2C4ONoAPFhsiwBfFhU0lvS2wXBsVUC4vSkwHF90Etlso0L6CSBkowEFwNgM5sImQwEFw4AJGAguQGAsQ6UvxAAQ2UloMYkQAU0MZmUjmYAQmUi+MRSowAM4UmiMRSowALhiKBC4MQGCIuBgAXCGCAuCgIXBiLGCFyQXCGAJINsToBC4cQgGMI50AIwYwCACAWEGAQAOFwowRCwwwPFw4wPCxAYNCxRJLCxYxKCxwyGORI"))

BIN
apps/pebbled/pebbled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,48 @@
(function(back) {
const SETTINGS_FILE = "pebbleDistance.json";
// initialize with default settings...
let s = {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75};
// ...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);
}
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
E.showMenu({
'': { 'title': 'Pebble Clock' },
'< Back': back,
'Color': {
value: 0 | color_options.indexOf(s.color),
min: 0, max: 5,
format: v => color_options[v],
onchange: v => {
s.color = color_options[v];
s.bg = bg_code[v];
save();
},
},
'Step length': {
value: 0.75 || s.avStep,
min: 0.2,
max: 1.5,
step: 0.01,
onchange : v => {
s.avStep = v;
save();
}
}
});
});

BIN
apps/pebbled/photo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@ -16,8 +16,8 @@ the ability to check the _exact_ time, hands free, without the impact on battery
Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
You can also override the system 12/24 hour setting just for this face here, since it's, well, a rather different experience than with numeric displays.
By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be
annoying in an intentionally dark environment, so there is an option to disable it.
In some previous versions of the Bangle.js firmware, the backlight doesn't come on automatically when you twist your wrist. There's currently a
workaround for this integrated into the watchface; you can disable it in the menu, if you prefer.
One other thing: there's some integration with system timers and alarms; they will show as small pips at the appropriate places
in the day around the display. When they come within an hour, the pips turn to crosses relating to the minute hand, and the minute

View File

@ -1 +1,2 @@
0.00: Initial check-in.
0.01: Add tap-to-decorate feature. Bugfixes.

View File

@ -10,16 +10,18 @@ Either you'll like that, or you won't.
## Options
Because sometimes I don't want to burn what I'm cooking and others I'm lazy and just want to know if it's afternoon yet,
you can alter the number of hands on the display. When the watch is unlocked, slide up to add dots representing the minute and second,
or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky,
in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
We don't observe the system 12/24 setting, since it the design of the face is equally good in either interpretation.
By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be
annoying in an intentionally dark environment, so there is an option to disable it.
If you like an uncluttered display style, you can still bring up the day, date and minute hand transiently with a tap on the watchface (when unlocked).
Similarly, because sometimes I don't want to burn what I'm cooking and others I'm lazy and just want to know if it's afternoon yet,
you can quickly alter the number of hands on the display. When the watch is unlocked, slide up to add dots representing the minute and second,
or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky,
in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
In some versions of the Bangle.js firmware, the backlight doesn't come on automatically when you twist your wrist. There's currently a workaround
for this integrated into the watchface; you can disable it in the menu, if you prefer.
## Limitations

View File

@ -83,7 +83,6 @@ class Options {
Bangle.removeListener('drag', this.reactivator);
this.emit('done');
}
g.clear(true);
E.showMenu(m);
}
@ -309,7 +308,7 @@ class Round {
buffer: this.c.buffer, transparent: 0
};
this.options = new RoundOptions();
this.timescales = [1000, 0, 60000, 900000];
this.timescales = [1000, [1000, 60000], 60000, 900000];
this.state = {};
// Precomputed polygons for the border areas.
this.tl = [0, 0, 58, 0, 0, 58];
@ -323,13 +322,15 @@ class Round {
this.r = this.xc - this.minR;
}
reset() {this.state = {}; this.g.clear(true);}
reset(clear) {this.state = {}; clear && this.g.clear(true);}
doIcons(which) {
this.state[which] = null;
this.render(new Date()); // Not quite right, I think.
}
enhanceUntil(t) {this.enhance = t;}
pie(f, a0, a1, invert) {
if (!invert) return this.pie(f, a1, a0 + 1, true);
let t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI);
@ -369,17 +370,18 @@ class Round {
const g = this.g;
const b = this.b, bI = this.bI;
const c = this.c, cI = this.cI;
const e = d < this.enhance;
const state = this.state;
const options = this.options;
const cal = options.calendric;
const res = options.resolution;
const dow = (cal == 1 || cal > 2) && d.getDay();
const dow = (e || cal == 1 || cal > 2) && d.getDay();
const ts = res < 2 && d.getSeconds();
const tm = res < 3 && d.getMinutes() + ts / 60;
const tm = (e || res < 3) && d.getMinutes() + ts / 60;
const th = d.getHours() + d.getMinutes() / 60;
const dd = cal > 1 && d.getDate();
const dm = cal > 3 && d.getMonth();
const dy = cal > 4 && d.getFullYear();
const dd = (e || cal > 1) && d.getDate();
const dm = (e || cal > 3) && d.getMonth();
const dy = (e || cal > 4) && d.getFullYear();
const xc = this.xc, yc = this.yc, r = this.r;
const dlr = xc * 3/4, dlw = 8, dlhw = 4;
@ -475,7 +477,6 @@ class Clock {
this.timescales = face.timescales;
this.options = face.options;
this.rates = {};
this.faceUp = null;
this.options.on('done', () => this.start());
@ -485,7 +486,6 @@ class Clock {
lock: () => {face.doIcons('locked'); this.active();},
faceUp: up => {
this.conservative = !up;
this.faceUp = up;
this.active();
},
twist: _ => this.options.autolight && Bangle.setLCDPower(true),
@ -504,9 +504,15 @@ class Clock {
this.options.resolution++;
this.rates.clock = this.timescales[this.options.resolution];
this.active();
} else if (this.yX - this.yN < 20 && Date.now() - this.t0 > 500) {
this.stop();
this.options.interact();
} else if (this.yX - this.yN < 20) {
const now = new Date();
if (now - this.t0 < 250) {
face.enhanceUntil(now + 30000);
face.render(now);
} else if (now - this.t0 > 500) {
this.stop();
this.options.interact();
}
}
this.t0 = null;
}
@ -520,7 +526,7 @@ class Clock {
redraw(rate) {
const now = this.updated = new Date();
if (this.refresh) this.face.reset();
if (this.refresh) this.face.reset(true);
this.refresh = false;
rate = this.face.render(now, rate);
if (rate !== this.rates.face) {
@ -535,7 +541,7 @@ class Clock {
this.exception && clearTimeout(this.exception);
this.interval && clearInterval(this.interval);
this.timeout = this.exception = this.interval = this.rate = null;
this.face.reset(); // Cancel any ongoing background rendering
this.face.reset(false); // Cancel any ongoing background rendering
return this;
}

View File

@ -1,4 +1,5 @@
0.01: Initial creation of the pattern launch app
0.02: Turn on lcd when launching an app if the lock screen was disabled in the settings
0.03: Make tap to confirm new pattern more reliable. Also allow for easier creation of single circle patterns.
0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.
0.10: Improve the management of existing patterns: Draw the linked pattern on the left hand side of the app name within a scroller, similar to the default launcher. Slighlty clean up the code to make it less horrible.
0.11: Respect theme colors. Fix: Do not pollute global space with internal variables ans functions in boot.js

View File

@ -10,14 +10,21 @@ Then launch the linked apps directly from the clock screen by simply drawing the
## Add Pattern Screenshots
![](main_menu_add.png)
![](add_pattern.png)
![](select_app.png)
![](main_menu_add_light.png)
![](add_pattern_light.png)
![](select_app_light.png)
![](main_menu_add_dark.png)
![](add_pattern_dark.png)
![](select_app_dark.png)
## Manage Pattern Screenshots
![](main_menu_manage.png)
![](manage_patterns.png)
![](main_menu_manage_light.png)
![](manage_patterns_light.png)
![](main_menu_manage_dark.png)
![](manage_patterns_dark.png)
## Detailed Steps

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -119,8 +119,7 @@ var recognizeAndDrawPattern = () => {
return new Promise((resolve) => {
E.showMenu();
g.clear();
g.setColor(0, 0, 0);
CIRCLES.forEach((circle) => drawCircle(circle));
drawCirclesWithPattern([]);
var pattern = [];
@ -369,7 +368,6 @@ var drawAppWithPattern = (i, r, storedPatterns) => {
offset: { x: 1, y: 3 + r.y },
});
g.setColor(0, 0, 0);
if (!storedPattern.wrappedAppName) {
storedPattern.wrappedAppName = g
.wrapString(app.name, g.getWidth() - 64)
@ -490,7 +488,7 @@ var drawCircle = (circle, drawBuffer, scale) => {
log("drawing circle");
log({ x: x, y: y, r: r });
drawBuffer.fillCircle(x, y, r);
drawBuffer.drawCircle(x, y, r);
};
var cachedCirclesDrawings = {};
@ -535,17 +533,15 @@ var drawCirclesWithPattern = (pattern, options) => {
{ msb: true }
);
drawBuffer.setColor(1);
CIRCLES.forEach((circle) => drawCircle(circle, drawBuffer, scale));
drawBuffer.setColor(0);
drawBuffer.setFontAlign(0, 0);
drawBuffer.setFont("6x8", 4 * scale);
drawBuffer.setFont("Vector", 40 * scale);
pattern.forEach((circleIndex, patternIndex) => {
var circle = CIRCLES[circleIndex];
drawBuffer.drawString(
patternIndex + 1,
circle.x * scale,
(circle.x + (scale === 1 ? 1 : 5)) * scale,
circle.y * scale
);
});

View File

@ -1,167 +1,167 @@
var DEBUG = true;
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var storedPatterns;
var positions = [];
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
log(positions.length);
var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var circles = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
{ x: 25, y: 87, i: 3 },
{ x: 87, y: 87, i: 4 },
{ x: 150, y: 87, i: 5 },
{ x: 25, y: 150, i: 6 },
{ x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 },
];
var pattern = [];
var step = Math.floor(positions.length / 100) + 1;
var p, a, b, circle;
for (var i = 0; i < positions.length; i += step) {
p = positions[i];
circle = circles[0];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(0, 1);
}
}
circle = circles[1];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(1, 1);
}
}
circle = circles[2];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(2, 1);
}
}
circle = circles[3];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(3, 1);
}
}
circle = circles[4];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(4, 1);
}
}
circle = circles[5];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(5, 1);
}
}
circle = circles[6];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(6, 1);
}
}
circle = circles[7];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(7, 1);
}
}
circle = circles[8];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(8, 1);
}
}
}
positions = [];
pattern = pattern.join("");
if (pattern) {
if (storedPatterns[pattern]) {
var app = storedPatterns[pattern].app;
if (!!app && !!app.src) {
if (storedPatterns.settings) {
if (storedPatterns.settings.lockDisabled) {
Bangle.setLCDPower(true);
}
}
Bangle.removeListener("drag", dragHandler);
load(app.src);
}
}
}
});
};
var debounceTimeoutId;
var debounce = (delay) => {
if (debounceTimeoutId) {
clearTimeout(debounceTimeoutId);
}
return new Promise((resolve) => {
debounceTimeoutId = setTimeout(() => {
debounceTimeoutId = undefined;
resolve();
}, delay || 500);
});
};
(function () {
var DEBUG = false;
var log = (message) => {
if (DEBUG) {
console.log(JSON.stringify(message));
}
};
var storedPatterns;
var positions = [];
var dragHandler = (position) => {
positions.push(position);
debounce().then(() => {
log(positions.length);
var CIRCLE_RADIUS = 25;
var CIRCLE_RADIUS_2 = CIRCLE_RADIUS * CIRCLE_RADIUS;
var circles = [
{ x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 },
{ x: 150, y: 25, i: 2 },
{ x: 25, y: 87, i: 3 },
{ x: 87, y: 87, i: 4 },
{ x: 150, y: 87, i: 5 },
{ x: 25, y: 150, i: 6 },
{ x: 87, y: 150, i: 7 },
{ x: 150, y: 150, i: 8 },
];
var pattern = [];
var step = Math.floor(positions.length / 100) + 1;
var p, a, b, circle;
for (var i = 0; i < positions.length; i += step) {
p = positions[i];
circle = circles[0];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(0, 1);
}
}
circle = circles[1];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(1, 1);
}
}
circle = circles[2];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(2, 1);
}
}
circle = circles[3];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(3, 1);
}
}
circle = circles[4];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(4, 1);
}
}
circle = circles[5];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(5, 1);
}
}
circle = circles[6];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(6, 1);
}
}
circle = circles[7];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(7, 1);
}
}
circle = circles[8];
if (circle) {
a = p.x - circle.x;
b = p.y - circle.y;
if (CIRCLE_RADIUS_2 - (a * a + b * b) >= 0) {
pattern.push(circle.i);
circles.splice(8, 1);
}
}
}
positions = [];
pattern = pattern.join("");
if (pattern) {
if (storedPatterns[pattern]) {
var app = storedPatterns[pattern].app;
if (!!app && !!app.src) {
if (storedPatterns.settings) {
if (storedPatterns.settings.lockDisabled) {
Bangle.setLCDPower(true);
}
}
Bangle.removeListener("drag", dragHandler);
load(app.src);
}
}
}
});
};
var debounceTimeoutId;
var debounce = (delay) => {
if (debounceTimeoutId) {
clearTimeout(debounceTimeoutId);
}
return new Promise((resolve) => {
debounceTimeoutId = setTimeout(() => {
debounceTimeoutId = undefined;
resolve();
}, delay || 500);
});
};
var sui = Bangle.setUI;
Bangle.setUI = function (mode, cb) {
sui(mode, cb);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,2 +1,3 @@
0.01: First release
0.02: Fix dependancies, fix type to Purple
0.02: Fix typo to Purple
0.03: Added dependancy on Pedometer Widget

View File

@ -40,3 +40,4 @@
0.35: Change App/Widget settings to 'App Settings' so it fits on Bangle screen
0.36: Added 'Utils' menu with helpful utilities for restoring Bangle.js
0.37: Going into passkey menu now saves settings with passkey
0.38: Restructed menus as per forum discussion

View File

@ -61,6 +61,37 @@ if (!settings) resetSettings();
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
function showMainMenu() {
const mainmenu = {
'': { 'title': 'Settings' },
'< Back': ()=>load(),
/*LANG*/'Apps': ()=>showAppSettingsMenu(),
/*LANG*/'Bluetooth': ()=>showBLEMenu(),
/*LANG*/'System': ()=>showSystemMenu(),
/*LANG*/'Alerts': ()=>showAlertsMenu(),
/*LANG*/'Utils': ()=>showUtilMenu(),
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }
};
return E.showMenu(mainmenu);
}
function showSystemMenu() {
const mainmenu = {
'': { 'title': 'System' },
'< Back': ()=>showMainMenu(),
/*LANG*/'Theme': ()=>showThemeMenu(),
/*LANG*/'LCD': ()=>showLCDMenu(),
/*LANG*/'Locale': ()=>showLocaleMenu(),
/*LANG*/'Select Clock': ()=>showClockMenu(),
/*LANG*/'Set Time': ()=>showSetTimeMenu()
};
return E.showMenu(mainmenu);
}
function showAlertsMenu() {
var beepMenuItem;
if (BANGLEJS2) {
beepMenuItem = {
@ -91,12 +122,9 @@ function showMainMenu() {
};
}
const mainmenu = {
'': { 'title': 'Settings' },
'< Back': ()=>load(),
/*LANG*/'App Settings': ()=>showAppSettingsMenu(),
/*LANG*/'BLE': ()=>showBLEMenu(),
'': { 'title': 'Alerts' },
'< Back': ()=>showMainMenu(),
/*LANG*/'Beep': beepMenuItem,
/*LANG*/'Vibration': {
value: settings.vibrate,
@ -119,23 +147,18 @@ function showMainMenu() {
updateOptions();
if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
},
},
/*LANG*/'Locale': ()=>showLocaleMenu(),
/*LANG*/'Select Clock': ()=>showClockMenu(),
/*LANG*/'Set Time': ()=>showSetTimeMenu(),
/*LANG*/'LCD': ()=>showLCDMenu(),
/*LANG*/'Theme': ()=>showThemeMenu(),
/*LANG*/'Utils': ()=>showUtilMenu(),
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() },
}
};
return E.showMenu(mainmenu);
}
function showBLEMenu() {
var hidV = [false, "kbmedia", "kb", "joy"];
var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"];
E.showMenu({
'': { 'title': 'Bluetooth' },
'< Back': ()=>showMainMenu(),
'Make Connectable': ()=>makeConnectable(),
'BLE': {
@ -190,7 +213,7 @@ function showThemeMenu() {
}
var m = E.showMenu({
'':{title:'Theme'},
'< Back': ()=>showMainMenu(),
'< Back': ()=>showSystemMenu(),
'Dark BW': ()=>{
upd({
fg:cl("#fff"), bg:cl("#000"),
@ -335,7 +358,7 @@ function showWhitelistMenu() {
function showLCDMenu() {
const lcdMenu = {
'': { 'title': 'LCD' },
'< Back': ()=>showMainMenu(),
'< Back': ()=>showSystemMenu(),
'LCD Brightness': {
value: settings.brightness,
min: 0.1,
@ -447,7 +470,7 @@ function showLCDMenu() {
function showLocaleMenu() {
const localemenu = {
'': { 'title': 'Locale' },
'< Back': ()=>showMainMenu(),
'< Back': ()=>showSystemMenu(),
'Time Zone': {
value: settings.timezone,
min: -11,
@ -551,7 +574,7 @@ function showClockMenu() {
'': {
'title': 'Select Clock',
},
'< Back': ()=>showMainMenu(),
'< Back': ()=>showSystemMenu(),
};
clockApps.forEach((app, index) => {
var label = app.name;
@ -578,7 +601,7 @@ function showSetTimeMenu() {
'': { 'title': 'Set Time' },
'< Back': function () {
setTime(d.getTime() / 1000);
showMainMenu();
showSystemMenu();
},
'Hour': {
value: d.getHours(),

2
apps/showimg/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.1: Initial release
0.2: Fixed launcher image

View File

@ -1,3 +1,3 @@
Displays an image. I use this app to show my vaccination certificate.
The image is read from the file "showimage.user.img".
The image is read from the file "showimg.user.img".
Returns to watch face after 60s/button push.

View File

@ -1 +1 @@
E.toArrayBuffer(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAf////AHP/////AH//////AP/8AAAHAP4AAAAHAOAAMAAHAOAAeAAHAOAA+cAHAOAA/+AHAOAA/+AHAOAf/+AHAOA//+AHgOA///AHgOA///gDgOA/z/gDgOAfz/gDgOA///gDgOA///gDgOA//uADgOAf3+ADgOAP/+ADgOAD/8ADwOAB+4ADwOAA8AABwOAAAABBwOA8DgPxwOB/Dg/xwOB/jh/xwOB3zj5xwOB57nzxwOA4/njhwOA4/vHhwOA8f+PB4OAef+PB4OAef8eB4OAPP58A4OAHv/4A4OAH//wA4OAD//AA4OAA/8fn4PDgP///4P//////4P////9/wD///4AAA"))
require("heatshrink").decompress(atob("mEwwkBIf4Aah//BRQAMDowUNC5AARC4YKKL5gTC+B3TCpAyIC5oNBEA4XNJwS4GC55pHC8TEHC57QHC4wSEC5YpEC6YwEC5oEEC5x3DC6ZHbC7PwcYxfNAYYXPJA4XQDAwKEBYQXJIoReHC5gMFAAojBC5QUIC5Y5JMgYXIUQYJFPggXMAwICCBAYXMCAQJDDwQUCC5QOCUwQdEC5QqFDghNFC5wrEC5gQDPgoTCDYYXFMAgXaCQoXJEwZ4FLQbhFC4imDAAglFC5QAGBgYXKIoYWIC5YYFG4ZkDC4YjCYYwAJC4gASC6THFH5pqGAAY"))

View File

@ -1,2 +1,4 @@
0.01: Initial import.
0.07: Add swipe to change screens.
1.06: Misc memory and screen optimisations.
1.10: ...

View File

@ -1,11 +1,9 @@
/*
Speed and Altitude [speedalt2]
Mike Bennett mike[at]kereru.com
0.01 : Initial
0.06 : Add Posn screen
0.07 : Add swipe to change screens same as BTN3
1.10 : add inverted colours
*/
var v = '1.05';
var v = '1.10';
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
var KalmanFilter = (function () {
@ -173,6 +171,15 @@ var KalmanFilter = (function () {
var buf = Graphics.createArrayBuffer(240,160,2,{msb:true});
let LED = // LED as minimal and only definition (as instance / singleton)
{ isOn: false // status on / off, not needed if you don't need to ask for it
, set: function(v) { // turn on w/ no arg or truey, else off
g.setColor((this.isOn=(v===undefined||!!v))?1:0,0,0).fillCircle(40,10,10); }
, reset: function() { this.set(false); } // turn off
, write: function(v) { this.set(v); } // turn on w/ no arg or truey, else off
, toggle: function() { this.set( ! this.isOn); } // toggle the LED
}, LED1 = LED; // LED1 as 'synonym' for LED
// Load fonts
//require("Font7x11Numeric7Seg").add(Graphics);
@ -183,17 +190,16 @@ var canDraw = 1;
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
var tmrLP; // Timer for delay in switching to low power after screen turns off
var max = {};
max.spd = 0;
max.alt = 0;
max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data.
var maxSpd = 0;
var maxAlt = 0;
var maxN = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data.
var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values;
var wp = {}; // Waypoint to use for distance from cur position.
function nxtWp(inc){
cfg.wp+=inc;
function nxtWp(){
cfg.wp++;
loadWp();
}
@ -227,7 +233,8 @@ function drawScrn(dat) {
if (!canDraw) return;
buf.clear();
buf.setBgColor(0);
var n;
n = dat.val.toString();
@ -252,29 +259,21 @@ function drawScrn(dat) {
buf.setFontVector(35);
buf.drawString(dat.unit,5,164);
if ( dat.max ) drawMax(); // MAX display indicator
if ( dat.wp ) drawWP(); // Waypoint name
//Sats
if ( dat.sat ) {
if ( dat.age > 10 ) {
if ( dat.age > 90 ) dat.age = '>90';
drawSats('Age:'+dat.age);
}
else drawSats('Sats:'+dat.sats);
}
drawMax(dat.max); // MAX display indicator
drawWP(dat.wp); // Waypoint name
drawSats(dat.sats);
g.reset();
g.drawImage(img,0,40);
if ( pwrSav ) LED1.reset();
else LED1.set();
LED1.write(!pwrSav);
}
function drawPosn(dat) {
if (!canDraw) return;
buf.clear();
buf.setBgColor(0);
var x, y;
x=210;
@ -293,20 +292,12 @@ function drawPosn(dat) {
buf.drawString(dat.ew,x,y+70);
//Sats
if ( dat.sat ) {
if ( dat.age > 10 ) {
if ( dat.age > 90 ) dat.age = '>90';
drawSats('Age:'+dat.age);
}
else drawSats('Sats:'+dat.sats);
}
drawSats(dat.sats);
g.reset();
g.drawImage(img,0,40);
if ( pwrSav ) LED1.reset();
else LED1.set();
LED1.write(!pwrSav);
}
@ -314,6 +305,8 @@ function drawClock() {
if (!canDraw) return;
buf.clear();
buf.setBgColor(0);
var x, y;
x=185;
y=0;
@ -329,19 +322,14 @@ function drawClock() {
g.reset();
g.drawImage(img,0,40);
if ( pwrSav ) LED1.reset();
else LED1.set();
LED1.write(!pwrSav);
}
function drawWP() {
var nm = wp.name;
if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = '';
buf.setColor(2);
function drawWP(wp) {
buf.setColor(3);
buf.setFontAlign(0,1); //left, bottom
buf.setFontVector(48);
buf.drawString(nm.substring(0,8),120,140);
buf.drawString(wp,120,140);
}
function drawSats(sats) {
@ -351,16 +339,15 @@ function drawSats(sats) {
buf.drawString(sats,240,160);
}
function drawMax() {
function drawMax(max) {
buf.setFontVector(30);
buf.setColor(2);
buf.setFontAlign(0,1); //centre, bottom
buf.drawString('MAX',120,164);
buf.drawString(max,120,164);
}
function onGPS(fix) {
if ( emulator ) {
if ( emulator ) {
fix.fix = 1;
fix.speed = 10 + (Math.random()*5);
fix.alt = 354 + (Math.random()*50);
@ -382,10 +369,15 @@ function onGPS(fix) {
var ns = '';
var ew = '';
var lon = '---.--';
var sats = '---';
// Waypoint name
var wpName = wp.name;
if ( wpName == undefined || wpName == 'NONE' ) wpName = '';
wpName = wpName.substring(0,8);
if (fix.fix) lf = fix;
if (lf.fix) {
// Smooth data
@ -393,10 +385,9 @@ function onGPS(fix) {
if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed);
if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt);
lf.smoothed = 1;
if ( max.n <= 15 ) max.n++;
if ( maxN <= 15 ) maxN++;
}
// Speed
if ( cfg.spd == 0 ) {
m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
@ -407,18 +398,19 @@ function onGPS(fix) {
if ( sp < 10 ) sp = sp.toFixed(1);
else sp = Math.round(sp);
if (isNaN(sp)) sp = '---';
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp;
if (parseFloat(sp) > parseFloat(maxSpd) && maxN > 15 ) maxSpd = sp;
// Altitude
al = lf.alt;
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al;
if (parseFloat(al) > parseFloat(maxAlt) && maxN > 15 ) maxAlt = al;
if (isNaN(al)) al = '---';
// Distance to waypoint
di = distance(lf,wp);
if (isNaN(di)) di = 0;
if (isNaN(di)) di = '--------';
// Age of last fix (secs)
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
@ -431,6 +423,13 @@ function onGPS(fix) {
ew = 'E';
if ( lf.lon < 0 ) ew = 'W';
lon = Math.abs(lf.lon.toFixed(2));
// Sats
if ( age > 10 ) {
sats = 'Age:'+Math.round(age);
if ( age > 90 ) sats = 'Age:>90';
}
else sats = 'Sats:'+lf.satellites;
}
@ -438,23 +437,21 @@ function onGPS(fix) {
// Speed
if ( showMax )
drawScrn({
val:max.spd,
val:maxSpd,
unit:cfg.spd_unit,
sats:lf.satellites,
sats:sats,
age:age,
max:true,
wp:false,
sat:true
max:'MAX',
wp:''
}); // Speed maximums
else
drawScrn({
val:sp,
unit:cfg.spd_unit,
sats:lf.satellites,
sats:sats,
age:age,
max:false,
wp:false,
sat:true
max:'SPD',
wp:''
});
}
@ -462,23 +459,21 @@ function onGPS(fix) {
// Alt
if ( showMax )
drawScrn({
val:max.alt,
val:maxAlt,
unit:cfg.alt_unit,
sats:lf.satellites,
sats:sats,
age:age,
max:true,
wp:false,
sat:true
max:'MAX',
wp:''
}); // Alt maximums
else
drawScrn({
val:al,
unit:cfg.alt_unit,
sats:lf.satellites,
sats:sats,
age:age,
max:false,
wp:false,
sat:true
max:'ALT',
wp:''
});
}
@ -487,24 +482,22 @@ function onGPS(fix) {
drawScrn({
val:di,
unit:cfg.dist_unit,
sats:lf.satellites,
sats:sats,
age:age,
max:false,
wp:true,
sat:true
max:'DST',
wp:wpName
});
}
if ( cfg.modeA == 3 ) {
// Position
drawPosn({
sats:lf.satellites,
drawPosn({
sats:sats,
age:age,
lat:lat,
lon:lon,
ns:ns,
ew:ew,
sat:true
ew:ew
});
}
@ -534,9 +527,9 @@ function nextFunc(dur) {
if ( cfg.modeA == 0 || cfg.modeA == 1 ) {
// Spd+Alt mode - Switch between fix and MAX
if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display
else { max.spd = 0; max.alt = 0; } // Long press resets max values.
else { maxSpd = 0; maxAlt = 0; } // Long press resets max values.
}
else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint
else if ( cfg.modeA == 2) nxtWp(); // Dist mode - Select next waypoint
onGPS(lf);
}
@ -545,7 +538,7 @@ function updateClock() {
if (!canDraw) return;
if ( cfg.modeA != 4 ) return;
drawClock();
if ( emulator ) {max.spd++;max.alt++;}
if ( emulator ) {maxSpd++;maxAlt++;}
}
function startDraw(){
@ -585,7 +578,6 @@ function setButtons(){
setWatch(function(e){
pwrSav=!pwrSav;
if ( pwrSav ) {
LED1.reset();
var s = require('Storage').readJSON('setting.json',1)||{};
var t = s.timeout||10;
Bangle.setLCDTimeout(t);
@ -593,8 +585,8 @@ function setButtons(){
else {
Bangle.setLCDTimeout(0);
// Bangle.setLCDPower(1);
LED1.set();
}
LED1.write(!pwrSav);
}, BTN2, {repeat:true,edge:"falling"});
// BTN3 - next screen
@ -690,7 +682,8 @@ var img = {
};
if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]);
if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]);
if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xF800,0xFAE0,0xF813]);
if ( cfg.colour == 3 ) img.palette = new Uint16Array([0xFFFF,0x007F,0x0054,0x0054]);
var SCREENACCESS = {
withApp:true,

View File

@ -65,7 +65,8 @@
'< Back': function() { E.showMenu(appMenu); },
'Default' : function() { setColour(0); },
'Hi Contrast' : function() { setColour(1); },
'Night' : function() { setColour(2); }
'Night' : function() { setColour(2); },
'Inverted' : function() { setColour(3); }
};
const kalMenu = {

View File

@ -31,3 +31,6 @@ Which one is which ?
![](A.jpg)
![](B.jpg)
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("AH4A/ACXd7vQC6vUpoBBDaQXEDaQXIDZwXMAIQZHC4R6BAAIZJDAwXIDY4XHAAodJ7oXMDpQXSAAiRHhoWN7zFLDY/e9ve9zeMhvQCIIBFC5ARIC5oVNC5EOCpwABC4vuCZYXPCIwXOCJAAFC5gAJ8AXFCpwuHgDjCFqQXC6lN6gbFf5gXEAInd6AXVDYndhoXKBoIbMC5QZLC44AFDpIXNDpQXdhoYMAAbwIC6oZQbxhOKC5gbKC6BUGC6oA/AHgA=="))
require("heatshrink").decompress(atob("mEw4UA///sH8ov+8GyJf4AIgt8BZV9voNIBYQNIBYgNGBYwMEBYNVqoMEoALGBoYLDBQILCAQVQBYoOEBZIABBYUAgILGsBiEBodWy2gN4soywACBYcI1QJDBYoJFBYkCBQ2qBYUKBIoLHBAQLHBAYACBYwAEwALBgwKG1S/DC4wWCa4Y3Efa19mALKvrLDfY7XGBwjvVBYjuHfYgLLBg4LEAAMVBZQNEBZBPCBZQA+A"))

View File

@ -1,3 +1,6 @@
0.02: New App!
0.03: Improved messages and added Celsius sign
0.04: Make temperature value readable on smaller screens
0.05: Use temperature from current locale
Update every 10s, average last 5 readings
Changes based on #1092

View File

@ -1,13 +1,27 @@
// history of temperature readings
var history = [];
// When we get temperature...
function onTemperature(p) {
g.reset(1).clearRect(0,24,g.getWidth(),g.getHeight());
var rect = Bangle.appRect;
g.reset(1).clearRect(rect.x, rect.y, rect.x2, rect.y2);
g.setFont("6x8",2).setFontAlign(0,0);
var x = g.getWidth()/2;
var y = g.getHeight()/2 + 10;
var x = (rect.x+rect.x2)/2;
var y = (rect.y+rect.y2)/2 + 10;
g.drawString("Temperature:", x, y - 45);
g.setFontVector(g.getWidth() > 200 ? 70 : 40).setFontAlign(0,0);
g.drawString(p.temperature.toFixed(1) + " °C", x, y);
g.setFontVector(g.getWidth() > 200 ? 70 : 50).setFontAlign(0,0);
// Average the last 5 temperature readings
while (history.length>4) history.shift();
history.push(p.temperature);
var avrTemp = E.sum(history) / history.length;
// Draw the temperature
var t = require('locale').temp(avrTemp).replace("'","°");
g.drawString(t, x, y);
}
// Gets the temperature in the most accurate way (pressure sensor or inbuilt thermistor)
function drawTemperature() {
if (Bangle.getPressure) {
Bangle.getPressure().then(onTemperature);
@ -18,11 +32,10 @@ function drawTemperature() {
}
}
setInterval(function() {
drawTemperature();
}, 20000);
drawTemperature();
}, 10000);
E.showMessage("Reading temperature...");
drawTemperature();
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/thermom/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -293,9 +293,9 @@ Bangle.on('swipe', dir => {
else next();
});
// close launcher when lcd is off
Bangle.on('lcdPower', on => {
if(!on) return load();
// close launcher when screen is locked
Bangle.on('lock', on => {
if(on) return load();
});
if (process.env.HWVERSION == 1) {

View File

@ -0,0 +1,3 @@
0.01: New watch face
0.02: Use Bangle.setUI for button/launcher handling
0.03: Bangle.js 2 support

View File

@ -1,3 +0,0 @@
0.1: New watch face
0.2: Use Bangle.setUI for button/launcher handling
0.3: Bangle.js 2 support

View File

@ -19,3 +19,4 @@
Stop goal drawing outside widget area
Fix issue with widget overwrite in large font mode
Memory usage enhancements
0.20: Fix issue where step count would randomly reset

View File

@ -53,7 +53,7 @@
E.on('kill', () => {
if (!settings) { loadSettings() }
let d = {
lastUpdate : lastUpdate.toISOString(),
lastUpdate : lastUpdate.valueOf(),
stepsToday : stp_today,
settings : settings,
};

View File

@ -3,6 +3,7 @@
*/
var fs = require("fs");
var heatshrink = require("../core/lib/heatshrink");
var acorn;
try {
acorn = require("acorn");
@ -59,6 +60,7 @@ const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'suppo
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
function globToRegex(pattern) {
const ESCAPE = '.*+-?^${}()|[]\\';
@ -175,6 +177,23 @@ apps.forEach((app,appIdx) => {
for (const key in file) {
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`);
}
// warn if JS icon is the wrong size
if (file.name == app.id+".img") {
let icon;
let match = fileContents.match(/E\.toArrayBuffer\(atob\(\"([^"]*)\"\)\)/);
if (match) icon = Buffer.from(match[1], 'base64');
else {
match = fileContents.match(/require\(\"heatshrink\"\)\.decompress\(\s*atob\(\s*\"([^"]*)\"\s*\)\s*\)/);
if (match) icon = heatshrink.decompress(Buffer.from(match[1], 'base64'));
else ERROR(`JS icon ${file.name} does not match the pattern 'require("heatshrink").decompress(atob("..."))'`);
}
if (match) {
if (icon[0] > 48 || icon[0] < 24 || icon[1] > 48 || icon[1] < 24) {
if (GRANDFATHERED_ICONS.includes(app.id)) WARN(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`);
else ERROR(`JS icon ${file.name} should be 48x48px (or slightly under) but is instead ${icon[0]}x${icon[1]}px`);
}
}
}
});
let dataNames = [];
(app.data||[]).forEach((data)=>{

View File

@ -11,7 +11,7 @@ if (window.location.host=="banglejs.com") {
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
}
var RECOMMENDED_VERSION = "2v10";
var RECOMMENDED_VERSION = "2v11";
// could check http://www.espruino.com/json/BANGLEJS.json for this
// We're only interested in Bangles