Merge branch 'espruino:master' into master

master
stweedo 2025-06-25 00:03:11 -05:00 committed by GitHub
commit ad54f17e30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 452 additions and 191 deletions

View File

@ -18,3 +18,4 @@
0.16: Correct date for all day events in negative timezones, improve locale display 0.16: Correct date for all day events in negative timezones, improve locale display
0.17: Fixed "Today" and "Tomorrow" labels displaying in non-current weeks 0.17: Fixed "Today" and "Tomorrow" labels displaying in non-current weeks
0.18: Correct date in clockinfo for all-day events in negative timezones 0.18: Correct date in clockinfo for all-day events in negative timezones
0.19: Change clockinfo title truncation to preserve images

View File

@ -64,7 +64,7 @@
agenda.forEach((entry, i) => { agenda.forEach((entry, i) => {
var title = entry.title.slice(0,12); var title = g.setFont("6x8").wrapString(entry.title,100)[0];
// All day events are always in UTC and always start at 00:00:00, so we // All day events are always in UTC and always start at 00:00:00, so we
// need to "undo" the timezone offsetting to make sure that the day is // need to "undo" the timezone offsetting to make sure that the day is
// correct. // correct.

View File

@ -1,7 +1,7 @@
{ {
"id": "agenda", "id": "agenda",
"name": "Agenda", "name": "Agenda",
"version": "0.18", "version": "0.19",
"description": "Simple agenda", "description": "Simple agenda",
"icon": "agenda.png", "icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],

View File

@ -2,3 +2,4 @@
0.02: Actually upload correct code 0.02: Actually upload correct code
0.03: Display sea-level pressure, too, and allow calibration 0.03: Display sea-level pressure, too, and allow calibration
0.04: Switch to using system code for pressure calibration 0.04: Switch to using system code for pressure calibration
0.05: Prompt before resetting calibration (stops long-press of button resetting calibration)

View File

@ -7,6 +7,7 @@ var R = Bangle.appRect;
var y = R.y + R.h/2; var y = R.y + R.h/2;
var MEDIANLENGTH = 20; var MEDIANLENGTH = 20;
var avr = []; var avr = [];
var updateDisplay = true;
function fmt(t) { function fmt(t) {
if ((t > -100) && (t < 1000)) if ((t > -100) && (t < 1000))
@ -19,48 +20,57 @@ function fmt(t) {
Bangle.on('pressure', function(e) { Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop(); while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude); avr.unshift(e.altitude);
let median = avr.slice().sort(); if (!updateDisplay) return;
let median = avr.slice().sort(), value;
g.reset().clearRect(0,y-30,g.getWidth()-10,R.h); g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
if (median.length>10) { if (median.length>10) {
var mid = median.length>>1; var mid = median.length>>1;
var value = E.sum(median.slice(mid-4,mid+5)) / 9; value = E.sum(median.slice(mid-4,mid+5)) / 9;
} else { } else {
var value = median[median.length>>1]; value = median[median.length>>1];
} }
t = fmt(value); var t = fmt(value);
g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y); g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
let o = Bangle.getOptions(); let o = Bangle.getOptions();
let sea = o.seaLevelPressure; let sea = o.seaLevelPressure;
t = sea.toFixed(1) + " " + e.temperature.toFixed(1); t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
if (0) { /*if (0) {
print("alt raw:", value.toFixed(1)); print("alt raw:", value.toFixed(1));
print("temperature:", e.temperature); print("temperature:", e.temperature);
print("pressure:", e.pressure); print("pressure:", e.pressure);
print("sea pressure:", sea); print("sea pressure:", sea);
} }*/
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t, 10, R.y+R.h - 35); g.setFont("Vector",25).setFontAlign(-1,0).drawString(t, 10, R.y+R.h - 35);
}); });
function setPressure(m, a) { function setPressure(m, a) {
o = Bangle.getOptions(); var o = Bangle.getOptions();
print(o); //print(o);
o.seaLevelPressure = o.seaLevelPressure * m + a; o.seaLevelPressure = o.seaLevelPressure * m + a;
Bangle.setOptions(o); Bangle.setOptions(o);
avr = []; avr = [];
} }
print(g.getFonts()); function start() {
g.reset(); g.reset();
g.setFont("Vector:15"); g.setFont("Vector:15");
g.setFontAlign(0,0); g.setFontAlign(0,0);
g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40); g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62); g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
g.flip();
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2); g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2);
updateDisplay = true;
Bangle.setUI("updown", btn => { Bangle.setUI("updown", btn => {
if (!btn) setPressure(0, 1013.25); if (!btn) {
updateDisplay = false;
E.showPrompt(/*LANG*/"Set calibration to default?",{title:/*LANG*/"Altitude"}).then(function(reset) {
start();
if (reset) setPressure(0, 1013.25);
});
}
if (btn<0) setPressure(1, 1); if (btn<0) setPressure(1, 1);
if (btn>0) setPressure(1, -1); if (btn>0) setPressure(1, -1);
}); });
}
start();

View File

@ -1,6 +1,6 @@
{ "id": "altimeter", { "id": "altimeter",
"name": "Altimeter", "name": "Altimeter",
"version":"0.04", "version":"0.05",
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.", "description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors", "tags": "tool,outdoors",

View File

@ -76,3 +76,4 @@
Bangle.loadWidgets overwritten with fast version on success Bangle.loadWidgets overwritten with fast version on success
Refuse to work on firmware <2v16 and remove old polyfills Refuse to work on firmware <2v16 and remove old polyfills
0.65: Only display interpreter errors if log is nonzero 0.65: Only display interpreter errors if log is nonzero
0.66: Ensure __FILE__ is set even after a fresh boot (fix #3857)

View File

@ -23,6 +23,7 @@ if (!_clkApp) {
require("Storage").writeJSON("setting.json", s); require("Storage").writeJSON("setting.json", s);
} }
} }
if (s.clock) __FILE__=s.clock;
delete s; delete s;
if (!_clkApp) _clkApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`; if (!_clkApp) _clkApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, global.BTN2||BTN, {repeat:false,edge:"falling"});`;
eval(_clkApp); eval(_clkApp);

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.65", "version": "0.66",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Bangle.js 2 compatibility 0.02: Bangle.js 2 compatibility
0.03: Add settings menu for showing time and battery percent with animation.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,8 +1,19 @@
var settings = Object.assign({
// default values
showBatPercent: true,
showTime: true,
}, require("Storage").readJSON("chargeAnimSettings.json", true) || {});
g.setBgColor(0, 0, 0); g.setBgColor(0, 0, 0);
g.clear().flip(); g.clear().flip();
var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa")); var imgbat = require("heatshrink").decompress(atob("nFYhBC/AH4A/AGUeACA22HEo3/G8YrTAC422HBQ2tHBI3/G/43/G/43/G/43/G/43/G/43/G+fTG+vSN+w326Q31GwI3/G9g2WG742CG/43rGwY3yGwg33RKo3bNzQ3bGwo3/G9A2GG942dG/43QGw43uGxA34IKw3VGyY3iG0I3pb8pBRG+wYPG8wYQG/42uG8oZSG/43bDKY3iDKg3cNzI3iRKo3gGyo3/G7A2WG7g2aG/43WGzA3dGzI3/G6fTGzRvcG/43/G/43/G/43/G/43/G/43/G/437HFw2IHFo2KAH4A/AH4Aa"));
var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA==")); var imgbubble = require("heatshrink").decompress(atob("i0UhAebgoAFCaYXNBocjAAIWNCYoVHCw4UFIZwqELJQWFKZQVOChYVzABwVaCx7wKCqIWNCg4WMChIXJCZgAnA=="));
require("Font8x12").add(Graphics);
var batteryPercentStr="";
var W=g.getWidth(),H=g.getHeight(); var W=g.getWidth(),H=g.getHeight();
var b2v = (W != 240)?-1:1; var b2v = (W != 240)?-1:1;
var b2rot = (W != 240)?Math.PI:0; var b2rot = (W != 240)?Math.PI:0;
@ -12,11 +23,50 @@ for (var i=0;i<10;i++) {
bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()}); bubbles.push({y:Math.random()*H,ly:0,x:(0.5+(i<5?i:i+8))*W/18,v:0.6+Math.random(),s:0.5+Math.random()});
} }
g.setFont("Vector",22);
g.setFontAlign(0,0);
var clockStr="";
var x=g.getWidth()/2;
var cy=g.getHeight()-(g.getHeight()/7)
var by=g.getHeight()-(g.getHeight()/3.500)
function calculateTime(){
var d=new Date();
clockStr = require("locale").time(d, 1); // Hour and minute
var meridian=require("locale").meridian(d);
if(meridian!=""){
//Meridian active
clockStr=clockStr+" "+meridian;
}
}
function calculate(){
if(settings.showTime==true){
calculateTime();
}
if(settings.showBatPercent==true){
batteryPercentStr=E.getBattery()+"%";
}
}
function anim() { function anim() {
/* we don't use any kind of buffering here. Just draw one image /* we don't use any kind of buffering here. Just draw one image
at a time (image contains a background) too, and there is minimal at a time (image contains a background) too, and there is minimal
flicker. */ flicker. */
var mx = W/2.0, my = H/2.0; var mx = W/2.0;
var my;
if(settings.showBatPercent){
var my = H/2.5;
}else{
var my = H/2.0;
}
bubbles.forEach(f=>{ bubbles.forEach(f=>{
f.y-=f.v * b2v; f.y-=f.v * b2v;
if (f.y<-24) if (f.y<-24)
@ -26,10 +76,25 @@ function anim() {
g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot}); g.drawImage(imgbubble,f.y,f.x,{scale:f.s * b2scale, rotate:b2rot});
}); });
g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot}); g.drawImage(imgbat, mx,my,{scale:b2scale, rotate:Math.sin(getTime()*2)*0.5-Math.PI/2 + b2rot});
if(settings.showTime==true){
g.drawString(clockStr,x,cy);
}
if(settings.showBatPercent==true){
g.drawString(batteryPercentStr,x,by,true);
}
g.flip(); g.flip();
} }
setInterval(anim,20); if(settings.showBatPercent||settings.showTime){
//Eliminate unnesccesary need for calculation
calculate();
setInterval(calculate,20000);
}
setInterval(anim,22);
Bangle.on("charging", isCharging => { Bangle.on("charging", isCharging => {
if (!isCharging) load(); if (!isCharging) load();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,16 +1,21 @@
{ {
"id": "chargeanim", "id": "chargeanim",
"name": "Charge Animation", "name": "Charge Animation",
"version": "0.02", "version": "0.03",
"description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", "description": "When charging, show a sideways charging animation and optionally, show time, or show battery percentage. When removed from the charger, clock loads again.",
"icon": "icon.png", "icon": "icon.png",
"tags": "battery", "tags": "battery",
"supports": ["BANGLEJS", "BANGLEJS2"], "supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true, "allow_emulator": true,
"screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}], "screenshots": [
{"url":"Screenshot1.png"},
{"url":"Screenshot2.png"},
{"url":"Screenshot3.png"}],
"storage": [ "storage": [
{"name":"chargeanim.app.js","url":"app.js"}, {"name":"chargeanim.app.js","url":"app.js"},
{"name":"chargeanim.boot.js","url":"boot.js"}, {"name":"chargeanim.boot.js","url":"boot.js"},
{"name":"chargeanim.settings.js","url":"settings.js"},
{"name":"chargeanim.img","url":"app-icon.js","evaluate":true} {"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
] ],
"data": [{"name":"chargeAnimSettings.json"}]
} }

View File

@ -0,0 +1,44 @@
(function(back) {
var FILE = "chargeAnimSettings.json";
// Load settings
var settings = Object.assign({
// default values
showBatPercent: true,
showTime: true,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Show the menu
E.showMenu({
"" : { "title" : "Charge Animation" },
"< Back" : () => back(),
'Show Percent Charged': {
value: !!settings.showBatPercent, // !! converts undefined to false
onchange: v => {
settings.showBatPercent = v;
writeSettings();
}
// format: ... may be specified as a function which converts the value to a string
// if the value is a boolean, showMenu() will convert this automatically, which
// keeps settings menus consistent
},
'Show Time': {
value: !!settings.showTime, // !! converts undefined to false
onchange: v => {
settings.showTime = v;
writeSettings();
}
// format: ... may be specified as a function which converts the value to a string
// if the value is a boolean, showMenu() will convert this automatically, which
// keeps settings menus consistent
}
});
})

View File

@ -15,3 +15,4 @@
0.14: Check for .clkinfocache and use that if exists (from boot 0.64) 0.14: Check for .clkinfocache and use that if exists (from boot 0.64)
0.15: Fix error when displaying a category with only one clockinfo (fix #3728) 0.15: Fix error when displaying a category with only one clockinfo (fix #3728)
0.16: Add BLE clkinfo entry 0.16: Add BLE clkinfo entry
0.17: Fix BLE icon alignment and border on some clocks

View File

@ -128,7 +128,7 @@ exports.load = function() {
get: function() { get: function() {
return { return {
text: this.isOn() ? "On" : "Off", text: this.isOn() ? "On" : "Off",
img: atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==") img: atob("GBiBAAAAAAAAAAAYAAAcAAAWAAATAAARgAMRgAGTAADWAAB8AAA4AAA4AAB8AADWAAGTAAMRgAARgAATAAAWAAAcAAAYAAAAAAAAAA==")
}; };
}, },
run: function() { run: function() {

View File

@ -1,7 +1,7 @@
{ "id": "clock_info", { "id": "clock_info",
"name": "Clock Info Module", "name": "Clock Info Module",
"shortName": "Clock Info", "shortName": "Clock Info",
"version":"0.16", "version":"0.17",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png", "icon": "app.png",
"type": "module", "type": "module",

View File

@ -113,6 +113,8 @@ Unless otherwise stated, this project is released under the MIT license.
Use of the patented method may be subject to licensing or permission. Use of the patented method may be subject to licensing or permission.
For inquiries, contact the author. For inquiries, contact the author.
Note: The patented method does not require licensing for non-commercial, research, educational, or personal use within the open-source community. Commercial or broad distribution beyond these uses may require permission or licensing. For such use cases, please contact the author.
### **Summary** ### **Summary**
The Circadian Rhythm Clock transforms the Bangle.js 2 into a **bio-aware, personalized circadian dashboard**, guiding users toward better alignment with their biological clock and modern life. The Circadian Rhythm Clock transforms the Bangle.js 2 into a **bio-aware, personalized circadian dashboard**, guiding users toward better alignment with their biological clock and modern life.

View File

@ -83,7 +83,7 @@ const STATS_FONT_SIZE = 16;
function getSleepWindowStr() { function getSleepWindowStr() {
let a = ("0"+S.sleepStart).substr(-2) + ":00"; let a = ("0"+S.sleepStart).substr(-2) + ":00";
let b = ("0"+S.sleepEnd).substr(-2) + ":00"; let b = ("0"+S.sleepEnd).substr(-2) + ":00";
return a + "" + b; return a + "-" + b;
} }
function stability(arr, key, hours) { function stability(arr, key, hours) {
@ -518,7 +518,7 @@ function confirmResetAllData() {
}); });
} }
// Menu logic: Sleep window, hydration, BT calibration, theme, notifications, bio ref, about unchanged, use as before // Menu logic: Sleep window, hydration, BT calibration, theme, notifications, bio ref, about - unchanged, use as before
function setSleepWindow() { function setSleepWindow() {
let menu = { "": { title: "Select Start Hour" } }; let menu = { "": { title: "Select Start Hour" } };
@ -610,7 +610,7 @@ function calibrateBT() {
Bangle.setUI(uiOpts); Bangle.setUI(uiOpts);
}); });
})(d); })(d);
m["" + d + "h"] = (() => () => { m["-" + d + "h"] = (() => () => {
S.phaseOffset -= d; S.phaseOffset -= d;
saveSettings(); saveSettings();
E.showAlert("Offset now: " + (S.phaseOffset>=0? "+"+S.phaseOffset : S.phaseOffset) + "h").then(() => { E.showAlert("Offset now: " + (S.phaseOffset>=0? "+"+S.phaseOffset : S.phaseOffset) + "h").then(() => {
@ -711,7 +711,7 @@ function setBioTimeReference() {
E.showMenu(m); E.showMenu(m);
function promptRefTime() { function promptRefTime() {
E.showPrompt("Hour (023)?").then(h => { E.showPrompt("Hour (0-23)?").then(h => {
if (h===undefined || h<0 || h>23) { if (h===undefined || h<0 || h>23) {
E.showAlert("Invalid hour").then(() => { E.showAlert("Invalid hour").then(() => {
drawClock(); drawClock();
@ -720,7 +720,7 @@ function setBioTimeReference() {
return; return;
} }
S.bioTimeRefHour = h; S.bioTimeRefHour = h;
E.showPrompt("Minute (059)?").then(m => { E.showPrompt("Minute (0-59)?").then(m => {
if (m===undefined || m<0 || m>59) { if (m===undefined || m<0 || m>59) {
E.showAlert("Invalid minute").then(() => { E.showAlert("Invalid minute").then(() => {
drawClock(); drawClock();
@ -743,7 +743,7 @@ function showAbout() {
E.showAlert( E.showAlert(
"Circadian Wellness Clock v" + VERSION + "\n" + "Circadian Wellness Clock v" + VERSION + "\n" +
"Displays your CRS and BioTime.\n" + "Displays your CRS and BioTime.\n" +
"© 2025" "Copyright 2025"
).then(()=>{ ).then(()=>{
drawClock(); drawClock();
Bangle.setUI(uiOpts); Bangle.setUI(uiOpts);

View File

@ -17,3 +17,4 @@
0.16: Always request Current Time service from iOS 0.16: Always request Current Time service from iOS
0.17: Default to passing full UTF8 strings into messages app (which can now process them with an international font) 0.17: Default to passing full UTF8 strings into messages app (which can now process them with an international font)
0.18: Fix UTF8 conversion (check for `font` library, not `fonts`) 0.18: Fix UTF8 conversion (check for `font` library, not `fonts`)
0.19: Convert numeric weather values to int from BangleDumpWeather shortcut

View File

@ -191,6 +191,11 @@ E.on('notify',msg=>{
wdir: d.wdir, wdir: d.wdir,
loc: d.loc loc: d.loc
} }
// Convert string fields to numbers for iOS weather shortcut
const numFields = ['code', 'wdir', 'temp', 'hi', 'lo', 'hum', 'wind', 'uv', 'rain'];
numFields.forEach(field => {
if (weatherEvent[field] != null) weatherEvent[field] = +weatherEvent[field];
});
require("weather").update(weatherEvent); require("weather").update(weatherEvent);
NRF.ancsAction(msg.uid, false); NRF.ancsAction(msg.uid, false);
return; return;

View File

@ -1,7 +1,7 @@
{ {
"id": "ios", "id": "ios",
"name": "iOS Integration", "name": "iOS Integration",
"version": "0.18", "version": "0.19",
"description": "Display notifications/music/etc from iOS devices", "description": "Display notifications/music/etc from iOS devices",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,ios,apple,messages,notifications", "tags": "tool,system,ios,apple,messages,notifications",

View File

@ -925,12 +925,6 @@ module.exports = {
"no-undef" "no-undef"
] ]
}, },
"apps/fileman/fileman.app.js": {
"hash": "f378179e7dd3655ba7e9ce03e1f7fd5a2d1768ad7d9083b22e7d740405be842a",
"rules": [
"no-undef"
]
},
"apps/flappy/app.js": { "apps/flappy/app.js": {
"hash": "e24b0c5e0469070e02dae00887bf50569c2c141a80c7c356b36987ddf68ce9cc", "hash": "e24b0c5e0469070e02dae00887bf50569c2c141a80c7c356b36987ddf68ce9cc",
"rules": [ "rules": [
@ -1051,12 +1045,6 @@ module.exports = {
"no-undef" "no-undef"
] ]
}, },
"apps/altimeter/app.js": {
"hash": "054ac328db51034aa339f1d10b4d264badd49438b95f08bc6fbfb90bd88c6ae0",
"rules": [
"no-undef"
]
},
"apps/alpinenav/app.js": { "apps/alpinenav/app.js": {
"hash": "f8e59724d282f7c5c989adf64974a3728dc521aa8fbe047b7c37dae09213095a", "hash": "f8e59724d282f7c5c989adf64974a3728dc521aa8fbe047b7c37dae09213095a",
"rules": [ "rules": [

View File

@ -7,3 +7,4 @@
0.06: Add message icon for 'molly' and 'threema libre' 0.06: Add message icon for 'molly' and 'threema libre'
0.07: Minor code improvements 0.07: Minor code improvements
0.08: Add more icons including GMail, Google Messages, Google Agenda 0.08: Add more icons including GMail, Google Messages, Google Agenda
0.09: Add Bereal, Nextcloud, Thunderbird, Davx⁵, Kleinanzeigen, Element X

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@ -102,6 +102,7 @@ exports.getColor = function(msg,options) {
*/""} */""}
"bibel": "#54342c", "bibel": "#54342c",
"bring": "#455a64", "bring": "#455a64",
"davx⁵": "#8bc34a",
"discord": "#5865f2", // https://discord.com/branding "discord": "#5865f2", // https://discord.com/branding
"etar": "#36a18b", "etar": "#36a18b",
"facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo "facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo
@ -112,6 +113,7 @@ exports.getColor = function(msg,options) {
// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background // "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background
"instagram": "#ff0069", // https://about.instagram.com/brand/gradient "instagram": "#ff0069", // https://about.instagram.com/brand/gradient
"jira": "#0052cc", // https://atlassian.design/resources/logo-library "jira": "#0052cc", // https://atlassian.design/resources/logo-library
"kleinanzeigen": "#69bd2f", // https://themen.kleinanzeigen.de/medien/mediathek/kleinanzeigen-guideline-nutzung-logo/
"leboncoin": "#fa7321", "leboncoin": "#fa7321",
"lieferando": "#ff8000", "lieferando": "#ff8000",
"linkedin": "#0a66c2", // https://brand.linkedin.com/ "linkedin": "#0a66c2", // https://brand.linkedin.com/
@ -121,6 +123,7 @@ exports.getColor = function(msg,options) {
"mattermost": "#00f", "mattermost": "#00f",
"n26": "#36a18b", "n26": "#36a18b",
"nextbike": "#00f", "nextbike": "#00f",
"nextcloud": "#0082c9", // https://nextcloud.com/brand/
"newpipe": "#f00", "newpipe": "#f00",
"nina": "#e57004", "nina": "#e57004",
"opentasks": "#409f8f", "opentasks": "#409f8f",
@ -137,6 +140,7 @@ exports.getColor = function(msg,options) {
"teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products "teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products
"telegram": "#0088cc", "telegram": "#0088cc",
"telegram foss": "#0088cc", "telegram foss": "#0088cc",
"thunderbird": "#1582e4",
"to do": "#3999e5", "to do": "#3999e5",
"twitch": "#9146ff", // https://brand.twitch.tv/ "twitch": "#9146ff", // https://brand.twitch.tv/
"twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit "twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit

View File

@ -5,6 +5,7 @@
{ "app":"alarm", "icon":"alarm.png" }, { "app":"alarm", "icon":"alarm.png" },
{ "app":"alarmclockreceiver", "icon":"alarm.png" }, { "app":"alarmclockreceiver", "icon":"alarm.png" },
{ "app":"amazon shopping", "icon":"amazon.png" }, { "app":"amazon shopping", "icon":"amazon.png" },
{ "app":"bereal.", "icon":"bereal.png" },
{ "app":"bibel", "icon":"bibel.png" }, { "app":"bibel", "icon":"bibel.png" },
{ "app":"bitwarden", "icon":"security.png" }, { "app":"bitwarden", "icon":"security.png" },
{ "app":"1password", "icon":"security.png" }, { "app":"1password", "icon":"security.png" },
@ -24,9 +25,11 @@
{ "app":"rabobank", "icon":"bank.png" }, { "app":"rabobank", "icon":"bank.png" },
{ "app":"scotiabank", "icon":"bank.png" }, { "app":"scotiabank", "icon":"bank.png" },
{ "app":"td (canada)", "icon":"bank.png" }, { "app":"td (canada)", "icon":"bank.png" },
{ "app":"davx⁵", "icon":"sync.png" },
{ "app":"discord", "icon":"discord.png" }, { "app":"discord", "icon":"discord.png" },
{ "app":"drive", "icon":"google drive.png" }, { "app":"drive", "icon":"google drive.png" },
{ "app":"element", "icon":"matrix element.png" }, { "app":"element", "icon":"matrix element.png" },
{ "app":"element x", "icon":"matrix element.png" },
{ "app":"facebook", "icon":"facebook.png" }, { "app":"facebook", "icon":"facebook.png" },
{ "app":"messenger", "icon":"facebook messenger.png" }, { "app":"messenger", "icon":"facebook messenger.png" },
{ "app":"firefox", "icon":"firefox.png" }, { "app":"firefox", "icon":"firefox.png" },
@ -47,6 +50,7 @@
{ "app":"jira", "icon":"jira.png" }, { "app":"jira", "icon":"jira.png" },
{ "app":"kalender", "icon":"kalender.png" }, { "app":"kalender", "icon":"kalender.png" },
{ "app":"keep notes", "icon":"google keep.png" }, { "app":"keep notes", "icon":"google keep.png" },
{ "app":"kleinanzeigen", "icon":"kleinanzeigen.png" },
{ "app":"leboncoin", "icon":"leboncoin.png" }, { "app":"leboncoin", "icon":"leboncoin.png" },
{ "app":"lieferando", "icon":"lieferando.png" }, { "app":"lieferando", "icon":"lieferando.png" },
{ "app":"linkedin", "icon":"linkedin.png" }, { "app":"linkedin", "icon":"linkedin.png" },
@ -69,6 +73,7 @@
{ "app":"la presse", "icon":"news.png" }, { "app":"la presse", "icon":"news.png" },
{ "app":"nbc news", "icon":"news.png" }, { "app":"nbc news", "icon":"news.png" },
{ "app":"nextbike", "icon":"nextbike.png" }, { "app":"nextbike", "icon":"nextbike.png" },
{ "app":"nextcloud", "icon":"nextcloud.png" },
{ "app":"nina", "icon":"nina.png" }, { "app":"nina", "icon":"nina.png" },
{ "app":"outlook mail", "icon":"outlook.png" }, { "app":"outlook mail", "icon":"outlook.png" },
{ "app":"paypal", "icon":"paypal.png" }, { "app":"paypal", "icon":"paypal.png" },
@ -95,6 +100,7 @@
{ "app":"telegram foss", "icon":"telegram.png" }, { "app":"telegram foss", "icon":"telegram.png" },
{ "app":"threema", "icon":"threema.png" }, { "app":"threema", "icon":"threema.png" },
{ "app":"threema libre", "icon":"threema.png" }, { "app":"threema libre", "icon":"threema.png" },
{ "app":"thunderbird", "icon":"mail.png" },
{ "app":"tiktok", "icon":"tiktok.png" }, { "app":"tiktok", "icon":"tiktok.png" },
{ "app":"to do", "icon":"task.png" }, { "app":"to do", "icon":"task.png" },
{ "app":"opentasks", "icon":"task.png" }, { "app":"opentasks", "icon":"task.png" },

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

View File

@ -4,7 +4,7 @@ exports.getImage = function(msg) {
if (msg.img) return atob(msg.img); if (msg.img) return atob(msg.img);
let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
if (msg.id=="music") s="music"; if (msg.id=="music") s="music";
let match = ",default|0,airbnb|1,agenda|2,alarm|3,alarmclockreceiver|3,amazon shopping|4,bibel|5,bitwarden|6,1password|6,lastpass|6,dashlane|6,bring|7,calendar|8,etar|8,chat|9,chrome|10,clock|3,corona-warn|11,bmo|12,desjardins|12,rbc mobile|12,nbc|12,rabobank|12,scotiabank|12,td (canada)|12,discord|13,drive|14,element|15,facebook|16,messenger|17,firefox|18,firefox beta|18,firefox nightly|18,f-droid|6,neo store|6,aurora droid|6,github|19,gitlab|20,gmail|21,gmx|22,google|23,google home|24,google play store|25,home assistant|26,instagram|27,jira|28,kalender|29,keep notes|30,leboncoin|31,lieferando|32,linkedin|33,maps|34,organic maps|34,osmand|34,mastodon|35,fedilab|35,tooot|35,tusky|35,mattermost|36,messages|37,n26|38,netflix|39,news|40,cbc news|40,rc info|40,reuters|40,ap news|40,la presse|40,nbc news|40,nextbike|41,nina|42,outlook mail|43,paypal|44,phone|45,plex|46,pocket|47,post & dhl|48,proton mail|49,reddit|50,sync pro|50,sync dev|50,boost|50,infinity|50,slide|50,signal|51,molly|51,skype|52,slack|53,snapchat|54,starbucks|55,steam|56,teams|57,telegram|58,telegram foss|58,threema|59,threema libre|59,tiktok|60,to do|61,opentasks|61,tasks|61,transit|62,twitch|63,twitter|64,uber|65,lyft|65,vlc|66,warnapp|67,whatsapp|68,wordfeud|69,youtube|70,newpipe|70,zoom|71,meet|71,music|72,sms message|0,mail|0,".match(new RegExp(`,${s}\\|(\\d+)`)) let match = ",default|0,airbnb|1,agenda|2,alarm|3,alarmclockreceiver|3,amazon shopping|4,bereal.|5,bibel|6,bitwarden|7,1password|7,lastpass|7,dashlane|7,bring|8,calendar|9,etar|9,chat|10,chrome|11,clock|3,corona-warn|12,bmo|13,desjardins|13,rbc mobile|13,nbc|13,rabobank|13,scotiabank|13,td (canada)|13,davx⁵|14,discord|15,drive|16,element|17,element x|17,facebook|18,messenger|19,firefox|20,firefox beta|20,firefox nightly|20,f-droid|7,neo store|7,aurora droid|7,github|21,gitlab|22,gmail|23,gmx|24,google|25,google home|26,google play store|27,home assistant|28,instagram|29,jira|30,kalender|31,keep notes|32,kleinanzeigen|33,leboncoin|34,lieferando|35,linkedin|36,maps|37,organic maps|37,osmand|37,mastodon|38,fedilab|38,tooot|38,tusky|38,mattermost|39,messages|40,n26|41,netflix|42,news|43,cbc news|43,rc info|43,reuters|43,ap news|43,la presse|43,nbc news|43,nextbike|44,nextcloud|45,nina|46,outlook mail|47,paypal|48,phone|49,plex|50,pocket|51,post & dhl|52,proton mail|53,reddit|54,sync pro|54,sync dev|54,boost|54,infinity|54,slide|54,signal|55,molly|55,skype|56,slack|57,snapchat|58,starbucks|59,steam|60,teams|61,telegram|62,telegram foss|62,threema|63,threema libre|63,thunderbird|64,tiktok|65,to do|66,opentasks|66,tasks|66,transit|67,twitch|68,twitter|69,uber|70,lyft|70,vlc|71,warnapp|72,whatsapp|73,wordfeud|74,youtube|75,newpipe|75,zoom|76,meet|76,music|77,sms message|0,mail|0,".match(new RegExp(`,${s}\\|(\\d+)`))
return require("Storage").read("messageicons.img", (match===null)?0:match[1]*76, 76); return require("Storage").read("messageicons.img", (match===null)?0:match[1]*76, 76);
}; };
@ -24,6 +24,7 @@ exports.getColor = function(msg,options) {
"sms message": "#0ff", "sms message": "#0ff",
"bibel": "#54342c", "bibel": "#54342c",
"bring": "#455a64", "bring": "#455a64",
"davx⁵": "#8bc34a",
"discord": "#5865f2", // https://discord.com/branding "discord": "#5865f2", // https://discord.com/branding
"etar": "#36a18b", "etar": "#36a18b",
"facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo "facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo
@ -34,6 +35,7 @@ exports.getColor = function(msg,options) {
// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background // "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background
"instagram": "#ff0069", // https://about.instagram.com/brand/gradient "instagram": "#ff0069", // https://about.instagram.com/brand/gradient
"jira": "#0052cc", // https://atlassian.design/resources/logo-library "jira": "#0052cc", // https://atlassian.design/resources/logo-library
"kleinanzeigen": "#69bd2f", // https://themen.kleinanzeigen.de/medien/mediathek/kleinanzeigen-guideline-nutzung-logo/
"leboncoin": "#fa7321", "leboncoin": "#fa7321",
"lieferando": "#ff8000", "lieferando": "#ff8000",
"linkedin": "#0a66c2", // https://brand.linkedin.com/ "linkedin": "#0a66c2", // https://brand.linkedin.com/
@ -43,6 +45,7 @@ exports.getColor = function(msg,options) {
"mattermost": "#00f", "mattermost": "#00f",
"n26": "#36a18b", "n26": "#36a18b",
"nextbike": "#00f", "nextbike": "#00f",
"nextcloud": "#0082c9", // https://nextcloud.com/brand/
"newpipe": "#f00", "newpipe": "#f00",
"nina": "#e57004", "nina": "#e57004",
"opentasks": "#409f8f", "opentasks": "#409f8f",
@ -59,6 +62,7 @@ exports.getColor = function(msg,options) {
"teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products "teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products
"telegram": "#0088cc", "telegram": "#0088cc",
"telegram foss": "#0088cc", "telegram foss": "#0088cc",
"thunderbird": "#1582e4",
"to do": "#3999e5", "to do": "#3999e5",
"twitch": "#9146ff", // https://brand.twitch.tv/ "twitch": "#9146ff", // https://brand.twitch.tv/
"twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit "twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit

View File

@ -1,7 +1,7 @@
{ {
"id": "messageicons", "id": "messageicons",
"name": "Message Icons", "name": "Message Icons",
"version": "0.08", "version": "0.09",
"description": "Library containing a list of icons and colors for apps", "description": "Library containing a list of icons and colors for apps",
"icon": "app.png", "icon": "app.png",
"type": "module", "type": "module",

1
apps/msgtwscr/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

15
apps/msgtwscr/README.md Normal file
View File

@ -0,0 +1,15 @@
# Message Twist to Scroll
Temporarily activate scroll on twist function when a new message triggers the message app. This way it's possible to scroll through a message in the message scroller hands free.
## Usage
This is a bootloader app and only needs to be installed to add the functionality to the watch.
## Requests
Mention @thyttan in an issue on the espruino/BangleApps repository.
## Creator
thyttan

BIN
apps/msgtwscr/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

63
apps/msgtwscr/boot.js Normal file
View File

@ -0,0 +1,63 @@
{
// twistThreshold How much acceleration to register a twist of the watch strap? Can be negative for opposite direction. default = 800
// twistMaxY Maximum acceleration in Y to trigger a twist (low Y means watch is facing the right way up). default = -800
// twistTimeout How little time (in ms) must a twist take from low->high acceleration? default = 1000
let onTwistEmitDrag = ()=>{
Bangle.setOptions({twistThreshold:2500, twistMaxY:-800, twistTimeout:400});
let isTwistDragging = false;
let twistHandler = ()=>{
if (!isTwistDragging) {
isTwistDragging = true;
Bangle.setLocked(false);
Bangle.setLCDPower(true);
let i = 25;
const int = setInterval(() => {
Bangle.emit("drag", {dy:-3, b:i===0?0:1})
i--;
if (i<0) {
clearInterval(int);
isTwistDragging = false;
}
}, 10);
}
}
Bangle.on("twist", twistHandler);
// Give messagegui some extra time to add its remove function to
// Bangle.uiRemove, then attach msgtwscr remove logic.
setTimeout(
()=>{if (Bangle.uiRemove) {
let showMessageUIRemove = Bangle.uiRemove;
Bangle.uiRemove = function () {
Bangle.removeListener("twist", twistHandler)
showMessageUIRemove();
// Reset twist drag logic if we go to next message.
attachAfterTimeout();
}
}},
800)
}
// If doing regular loads, not Bangle.load, this is used:
if (global.__FILE__=="messagegui.new.js") {
onTwistEmitDrag();
}
let attachAfterTimeout = ()=>{
setTimeout(()=>{
if (global.__FILE__=="messagegui.new.js") {
onTwistEmitDrag();
}
},700)
// It feels like there's a more elegant solution than checking the filename
// after 700 milliseconds. But this at least seems to work w/o sometimes
// activating when it shouldn't.
// Maybe we could add events for when fast load and/or Bangle.uiRemove occurs?
// Then that could be used similarly to boot code and/or the `kill` event.
}
// If Fastload Utils is installed this is used:
Bangle.on("message", (_, msg)=>{if (Bangle.CLOCK && msg.new) {
attachAfterTimeout();
}});
}

View File

@ -0,0 +1,13 @@
{ "id": "msgtwscr",
"name": "Message Twist to Scroll",
"version":"0.01",
"description": "Temporarily activate scroll on twist function when a new message triggers the message app.",
"icon": "app.png",
"tags": "messages,tweak,scroll",
"type": "bootloader",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"msgtwscr.boot.js","url":"boot.js"}
]
}

View File

@ -7,3 +7,4 @@
0.07: Update weather after reconnecting bluetooth if update is due, refactor code 0.07: Update weather after reconnecting bluetooth if update is due, refactor code
0.08: Undo change to One Call API 3.0 0.08: Undo change to One Call API 3.0
0.09: Fix infinite loop when settings.updated is not defined 0.09: Fix infinite loop when settings.updated is not defined
0.10: Fix settings.updated being updated even when OWM call failed

View File

@ -18,6 +18,13 @@
timeoutRef = setTimeout(loadIfDueAndReschedule, refreshMillis()); timeoutRef = setTimeout(loadIfDueAndReschedule, refreshMillis());
}; };
let onError = function(e) {
console.log("owmweather error:", e);
loading = false;
if (timeoutRef) clearTimeout(timeoutRef);
timeoutRef = setTimeout(loadIfDueAndReschedule, refreshMillis());
};
let loadIfDueAndReschedule = function () { let loadIfDueAndReschedule = function () {
// also check if the weather.json file has been updated (e.g. force refresh) // also check if the weather.json file has been updated (e.g. force refresh)
let weather = require("Storage").readJSON('weather.json') || {}; let weather = require("Storage").readJSON('weather.json') || {};
@ -30,7 +37,7 @@
if (!MillisUntilDue || MillisUntilDue <= 0) { if (!MillisUntilDue || MillisUntilDue <= 0) {
if (!loading) { if (!loading) {
loading = true; loading = true;
require("owmweather").pull(onCompleted); require("owmweather").pull(onCompleted, onError);
} }
} else { } else {
// called to early, reschedule // called to early, reschedule

View File

@ -27,13 +27,12 @@ function parseWeather(response) {
json.weather = weather; json.weather = weather;
require("Storage").writeJSON('weather.json', json); require("Storage").writeJSON('weather.json', json);
if (require("Storage").read("weather")!==undefined) require("weather").emit("update", json.weather); if (require("Storage").read("weather")!==undefined) require("weather").emit("update", json.weather);
return undefined;
} else { } else {
return /*LANG*/"Not OWM data"; throw /*LANG*/"Not OWM data";
} }
} }
exports.pull = function(completionCallback) { exports.pull = function(completionCallback, errorCallback) {
let location = require("Storage").readJSON("mylocation.json", 1) || { let location = require("Storage").readJSON("mylocation.json", 1) || {
"lat": 51.50, "lat": 51.50,
"lon": 0.12, "lon": 0.12,
@ -43,12 +42,12 @@ exports.pull = function(completionCallback) {
let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey; let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey;
if (Bangle.http){ if (Bangle.http){
Bangle.http(uri, {timeout:10000}).then(event => { Bangle.http(uri, {timeout:10000}).then(event => {
let result = parseWeather(event.resp); parseWeather(event.resp);
if (completionCallback) completionCallback(result); if (completionCallback) completionCallback();
}).catch((e)=>{ }).catch((e)=>{
if (completionCallback) completionCallback(e); if (errorCallback) errorCallback(e);
}); });
} else { } else {
if (completionCallback) completionCallback(/*LANG*/"No http method found"); if (errorCallback) errorCallback(/*LANG*/"No http method found");
} }
}; };

View File

@ -1,7 +1,7 @@
{ "id": "owmweather", { "id": "owmweather",
"name": "OpenWeatherMap weather provider", "name": "OpenWeatherMap weather provider",
"shortName":"OWM Weather", "shortName":"OWM Weather",
"version": "0.09", "version": "0.10",
"description": "Pulls weather from OpenWeatherMap (OWM) API", "description": "Pulls weather from OpenWeatherMap (OWM) API",
"icon": "app.png", "icon": "app.png",
"type": "bootloader", "type": "bootloader",

View File

@ -1,6 +1,6 @@
0.01: First release 0.01: First release
0.02: clock_info now uses app name to maintain settings specifically for this clock face 0.02: clock_info now uses app name to maintain settings specifically for this clock face
ensure clockinfo text is usppercase (font doesn't render lowercase) ensure clockinfo text is uppercase (font doesn't render lowercase)
0.03: Use smaller font if clock_info test doesn't fit in area 0.03: Use smaller font if clock_info test doesn't fit in area
0.04: Ensure we only scale down clockinfo text if it really won't fit 0.04: Ensure we only scale down clockinfo text if it really won't fit
0.05: Minor code improvements 0.05: Minor code improvements
@ -11,3 +11,5 @@
0.10: Fix size of bottom bar after 0.09 0.10: Fix size of bottom bar after 0.09
Make date toggleable with settings Make date toggleable with settings
Optional border around clockinfos (available from settings) Optional border around clockinfos (available from settings)
0.11: Make the border on clockinfos the default
Fix clockinfos when too long (previously just output '...')

View File

@ -20,7 +20,7 @@ Graphics.prototype.setFontLECO1976Regular14 = function() {
{ {
const SETTINGS_FILE = "pebblepp.json"; const SETTINGS_FILE = "pebblepp.json";
let settings = Object.assign({'theme':'System', 'showdate':true, 'clkinfoborder': false}, require("Storage").readJSON(SETTINGS_FILE,1)||{}); let settings = Object.assign({'theme':'System', 'showdate':true, 'clkinfoborder': true}, require("Storage").readJSON(SETTINGS_FILE,1)||{});
let background = require("clockbg"); let background = require("clockbg");
let theme; let theme;
let drawTimeout; let drawTimeout;
@ -102,7 +102,7 @@ let clockInfoDraw = (itm, info, options) => {
g.setFontLECO1976Regular14(); g.setFontLECO1976Regular14();
if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines
var l = g.wrapString(txt, options.w); var l = g.wrapString(txt, options.w);
txt = l.slice(0,2).join("\n") + (l.length>2)?"...":""; txt = l.slice(0,2).join("\n") + ((l.length>2)?"...":"");
} }
y = options.y+options.h-12; y = options.y+options.h-12;
if (settings.clkinfoborder) { if (settings.clkinfoborder) {

View File

@ -2,7 +2,7 @@
"id": "pebblepp", "id": "pebblepp",
"name": "Pebble++ Clock", "name": "Pebble++ Clock",
"shortName": "Pebble++", "shortName": "Pebble++",
"version": "0.10", "version": "0.11",
"description": "A Pebble style clock (based on the 'Pebble Clock' app) but with two configurable ClockInfo items at the top and custom backgrounds. Date/theme/borders be reconfigured using settings page.", "description": "A Pebble style clock (based on the 'Pebble Clock' app) but with two configurable ClockInfo items at the top and custom backgrounds. Date/theme/borders be reconfigured using settings page.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}], "screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],

View File

@ -2,7 +2,7 @@
const SETTINGS_FILE = "pebblepp.json"; const SETTINGS_FILE = "pebblepp.json";
// Initialize with default settings... // Initialize with default settings...
let settings = {'theme':'System', 'showdate':true, 'clkinfoborder':false} let settings = {'theme':'System', 'showdate':true, 'clkinfoborder':true}
// ...and overwrite them with any saved values // ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings // This way saved values are preserved if a new version adds more settings
const storage = require('Storage'); const storage = require('Storage');

View File

@ -7,3 +7,7 @@
0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/ 0.13: Improve pattern rendering by HughB http://forum.espruino.com/profiles/167235/
0.14: Update setUI to work with new Bangle.js 2v13 menu style 0.14: Update setUI to work with new Bangle.js 2v13 menu style
0.15: Update to support clocks in custom setUI mode 0.15: Update to support clocks in custom setUI mode
0.16: Fix issue adding new patterns (fix #3858)
Display message if tapping manage when there are no patterns
Speed improvements
Add widgets to app (work around E.showMenu(back) bug with no widgets in Espruino 2v27)

View File

@ -9,10 +9,10 @@ var showMainMenu = () => {
var mainmenu = { var mainmenu = {
"": { "": {
title: "Pattern Launcher", title: "Pattern Launcher",
}, back: () => {
"< Back": () => { log("showMainMenu cancel");
log("cancel");
load(); load();
}
}, },
"Add Pattern": () => { "Add Pattern": () => {
log("creating pattern"); log("creating pattern");
@ -83,11 +83,11 @@ var showMainMenu = () => {
var settingsmenu = { var settingsmenu = {
"": { "": {
title: "Pattern Settings", title: "Pattern Settings",
back: () => {
log("settings cancel");
showMainMenu();
}, },
"< Back": () => { }
log("cancel");
load();
},
}; };
if (settings.lockDisabled) { if (settings.lockDisabled) {
@ -116,12 +116,7 @@ var showMainMenu = () => {
var recognizeAndDrawPattern = () => { var recognizeAndDrawPattern = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
E.showMenu();
g.clear();
drawCirclesWithPattern([]);
var pattern = []; var pattern = [];
var isFinished = false; var isFinished = false;
var finishHandler = () => { var finishHandler = () => {
if (pattern.length === 0 || isFinished) { if (pattern.length === 0 || isFinished) {
@ -129,15 +124,14 @@ var recognizeAndDrawPattern = () => {
} }
log("Pattern is finished."); log("Pattern is finished.");
isFinished = true; isFinished = true;
Bangle.removeListener("drag", dragHandler); g.clear();
Bangle.removeListener("tap", finishHandler); require("widget_utils").show();
Bangle.setUI();
resolve(pattern.join("")); resolve(pattern.join(""));
}; };
setWatch(() => finishHandler(), BTN);
// setTimeout(() => Bangle.on("tap", finishHandler), 250);
var positions = []; var positions = [];
var getPattern = (positions) => { var getPattern = (positions) => { "ram";/*faster*/
var circles = [ var circles = [
{ x: 25, y: 25, i: 0 }, { x: 25, y: 25, i: 0 },
{ x: 87, y: 25, i: 1 }, { x: 87, y: 25, i: 1 },
@ -151,17 +145,7 @@ var recognizeAndDrawPattern = () => {
]; ];
return positions.reduce((pattern, p, i, arr) => { return positions.reduce((pattern, p, i, arr) => {
var idx = circles.findIndex((c) => { var idx = circles.findIndex((c) => {
var dx = p.x > c.x ? p.x - c.x : c.x - p.x; var dx = p.x - c.x, dy = p.y - c.y;
if (dx > CIRCLE_RADIUS) {
return false;
}
var dy = p.y > c.y ? p.y - c.y : c.y - p.y;
if (dy > CIRCLE_RADIUS) {
return false;
}
if (dx + dy <= CIRCLE_RADIUS) {
return true;
}
return dx*dx + dy*dy <= CIRCLE_RADIUS_2; return dx*dx + dy*dy <= CIRCLE_RADIUS_2;
}); });
if (idx >= 0) { if (idx >= 0) {
@ -183,7 +167,10 @@ var recognizeAndDrawPattern = () => {
positions = []; positions = [];
} }
}; };
Bangle.on("drag", dragHandler); require("widget_utils").hide();
g.clear();
drawCirclesWithPattern([]);
Bangle.setUI({mode:"custom", drag:dragHandler, btn :finishHandler});
}); });
}; };
@ -215,14 +202,14 @@ var getAppList = () => {
}; };
var getSelectedApp = () => { var getSelectedApp = () => {
E.showMessage("Loading apps..."); E.showMessage(/*LANG*/"Loading apps...");
return new Promise((resolve) => { return new Promise((resolve) => {
var selectAppMenu = { var selectAppMenu = {
"": { "": {
title: "Select App", title: /*LANG*/"Select App",
}, },
"< Cancel": () => { "< Cancel": () => {
log("cancel"); log("getSelectedApp cancel");
showMainMenu(); showMainMenu();
}, },
}; };
@ -286,6 +273,8 @@ var drawAppWithPattern = (i, r, storedPatterns) => {
var showScrollerContainingAppsWithPatterns = () => { var showScrollerContainingAppsWithPatterns = () => {
var storedPatternsArray = getStoredPatternsArray(); var storedPatternsArray = getStoredPatternsArray();
if (!storedPatternsArray.length)
return E.showAlert(/*LANG*/"No Patterns",{title:/*LANG*/"Patterns"}).then(() => ({ pattern: "back", appName:"" }));
log("drawing scroller for stored patterns"); log("drawing scroller for stored patterns");
log(storedPatternsArray); log(storedPatternsArray);
log(storedPatternsArray.length); log(storedPatternsArray.length);
@ -485,4 +474,6 @@ var log = (message) => {
// run main function // run main function
////// //////
Bangle.loadWidgets();
Bangle.drawWidgets();
showMainMenu(); showMainMenu();

View File

@ -24,17 +24,7 @@
]; ];
return positions.reduce((pattern, p, i, arr) => { return positions.reduce((pattern, p, i, arr) => {
var idx = circles.findIndex((c) => { var idx = circles.findIndex((c) => {
var dx = p.x > c.x ? p.x - c.x : c.x - p.x; var dx = p.x - c.x, dy = p.y - c.y;
if (dx > CIRCLE_RADIUS) {
return false;
}
var dy = p.y > c.y ? p.y - c.y : c.y - p.y;
if (dy > CIRCLE_RADIUS) {
return false;
}
if (dx + dy <= CIRCLE_RADIUS) {
return true;
}
return dx*dx + dy*dy <= CIRCLE_RADIUS_2; return dx*dx + dy*dy <= CIRCLE_RADIUS_2;
}); });
if (idx >= 0) { if (idx >= 0) {

View File

@ -2,7 +2,7 @@
"id": "ptlaunch", "id": "ptlaunch",
"name": "Pattern Launcher", "name": "Pattern Launcher",
"shortName": "Pattern Launcher", "shortName": "Pattern Launcher",
"version": "0.15", "version": "0.16",
"description": "Directly launch apps from the clock screen with custom patterns.", "description": "Directly launch apps from the clock screen with custom patterns.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"manage_patterns_light.png"}], "screenshots": [{"url":"manage_patterns_light.png"}],

View File

@ -56,3 +56,8 @@
0.44: List tracks in reverse chronological order. 0.44: List tracks in reverse chronological order.
0.45: Move recorder from widget into library 0.45: Move recorder from widget into library
Improve recorder ClockInfo icons Improve recorder ClockInfo icons
0.46: Ensure altitude graph draws properly (or any graph using the last column of CSV data)
Lower accuracy of barometer data to ~1cm (saves about 15b/record)
0.47: Fix 'blip' on speed map on some recordings
Ensure Battery voltage is only stored to 0.01v
Add graphs for Steps+Battery

View File

@ -197,6 +197,14 @@ function viewTrack(filename, info) {
menu[/*LANG*/'Plot HRM'] = function() { menu[/*LANG*/'Plot HRM'] = function() {
plotGraph(info, "Heartrate"); plotGraph(info, "Heartrate");
}; };
if (info.fields.includes("Steps"))
menu[/*LANG*/'Plot Steps'] = function() {
plotGraph(info, "Steps");
};
if (info.fields.includes("Battery Percentage"))
menu[/*LANG*/'Plot Battery'] = function() {
plotGraph(info, "Battery");
};
// TODO: steps, heart rate? // TODO: steps, heart rate?
menu[/*LANG*/'Erase'] = function() { menu[/*LANG*/'Erase'] = function() {
E.showPrompt(/*LANG*/"Delete Track?").then(function(v) { E.showPrompt(/*LANG*/"Delete Track?").then(function(v) {
@ -325,6 +333,7 @@ function plotGraph(info, style) { "ram"
var lt = 0; // last time var lt = 0; // last time
//var tn = 0; // count for each time period //var tn = 0; // count for each time period
var strt, dur = info.duration; var strt, dur = info.duration;
if (dur<1) dur=1;
var f = require("Storage").open(filename,"r"); var f = require("Storage").open(filename,"r");
if (f===undefined) return; if (f===undefined) return;
var l = f.readLine(f); var l = f.readLine(f);
@ -333,14 +342,14 @@ function plotGraph(info, style) { "ram"
var factor = 1; // multiplier used for values when graphing var factor = 1; // multiplier used for values when graphing
var timeIdx = info.fields.indexOf("Time"); var timeIdx = info.fields.indexOf("Time");
if (l!==undefined) { if (l!==undefined) {
c = l.split(","); c = l.trim().split(",");
strt = c[timeIdx]; strt = c[timeIdx];
} }
if (style=="Heartrate") { if (style=="Heartrate") {
title = /*LANG*/"Heartrate (bpm)"; title = /*LANG*/"Heartrate (bpm)";
var hrmIdx = info.fields.indexOf("Heartrate"); var hrmIdx = info.fields.indexOf("Heartrate");
while(l!==undefined) { while(l!==undefined) {
c=l.split(",");l = f.readLine(f); c=l.trim().split(",");l = f.readLine(f);
if (c[hrmIdx]=="") continue; if (c[hrmIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur); i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[hrmIdx]; infn[i]+=+c[hrmIdx];
@ -351,45 +360,63 @@ function plotGraph(info, style) { "ram"
var altIdx = info.fields.indexOf("Barometer Altitude"); var altIdx = info.fields.indexOf("Barometer Altitude");
if (altIdx<0) altIdx = info.fields.indexOf("Altitude"); if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
while(l!==undefined) { while(l!==undefined) {
c=l.split(",");l = f.readLine(f); c=l.trim().split(",");l = f.readLine(f);
if (c[altIdx]=="") continue; if (c[altIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur); i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[altIdx]; infn[i]+=+c[altIdx];
infc[i]++; infc[i]++;
} }
} else if (style=="Steps") {
title = /*LANG*/"Steps/min";
var stpIdx = info.fields.indexOf("Steps");
var t,lt = c[timeIdx];
while(l!==undefined) {
c=l.trim().split(",");l = f.readLine(f);
if (c[stpIdx]=="") continue;
t = c[timeIdx];
i = Math.round(80*(t - strt)/dur);
infn[i]+=60*c[stpIdx];
infc[i]+=t-lt;
lt = t;
}
} else if (style=="Battery") {
title = /*LANG*/"Battery %";
var batIdx = info.fields.indexOf("Battery Percentage");
while(l!==undefined) {
c=l.trim().split(",");l = f.readLine(f);
if (c[batIdx]=="") continue;
i = Math.round(80*(c[timeIdx] - strt)/dur);
infn[i]+=+c[batIdx];
infc[i]++;
}
} else if (style=="Speed") { } else if (style=="Speed") {
// use locate to work out units // use locate to work out units
var localeStr = require("locale").speed(1,5); // get what 1kph equates to var localeStr = require("locale").speed(1,5); // get what 1kph equates to
let units = localeStr.replace(/[0-9.]*/,""); let units = localeStr.replace(/[0-9.]*/,"");
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
// title
title = /*LANG*/"Speed"+` (${units})`; title = /*LANG*/"Speed"+` (${units})`;
var latIdx = info.fields.indexOf("Latitude"); var latIdx = info.fields.indexOf("Latitude");
var lonIdx = info.fields.indexOf("Longitude"); var lonIdx = info.fields.indexOf("Longitude");
// skip until we find our first data // skip until we find our first data
while(l!==undefined && c[latIdx]=="") { while(l!==undefined && c[latIdx]=="") {
c = l.split(","); c = l.trim().split(",");
l = f.readLine(f); l = f.readLine(f);
} }
// now iterate // now iterate
var p,lp = Bangle.project({lat:c[1],lon:c[2]}); var p,lp = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
var t,dx,dy,d,lt = c[timeIdx]; var t,dx,dy,d,lt = c[timeIdx];
while(l!==undefined) { while(l!==undefined) {
c=l.split(","); c=l.trim().split(",");
l = f.readLine(f); l = f.readLine(f);
if (c[latIdx] == "") { if (c[latIdx] == "") continue;
continue;
}
t = c[timeIdx]; t = c[timeIdx];
i = Math.round(80*(t - strt)/dur); i = Math.round(80*(t - strt)/dur);
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
dx = p.x-lp.x; dx = p.x-lp.x;
dy = p.y-lp.y; dy = p.y-lp.y;
d = Math.sqrt(dx*dx+dy*dy); d = Math.sqrt(dx*dx+dy*dy);
if (t!=lt) { infn[i]+=d; // speed
infn[i]+=d / (t-lt); // speed infc[i]+=t-lt;
infc[i]++;
}
lp = p; lp = p;
lt = t; lt = t;
} }
@ -405,6 +432,7 @@ function plotGraph(info, style) { "ram"
if (n>max) max=n; if (n>max) max=n;
if (n<min) min=n; if (n<min) min=n;
} }
if (style=="Battery") {min=0;max=100;}
// work out a nice grid value // work out a nice grid value
var heightDiff = max-min; var heightDiff = max-min;
var grid = 1; var grid = 1;

View File

@ -91,7 +91,7 @@ exports.getRecorders = function() {
name : "BAT", name : "BAT",
fields : ["Battery Percentage", "Battery Voltage", "Charging"], fields : ["Battery Percentage", "Battery Voltage", "Charging"],
getValues : () => { getValues : () => {
return [E.getBattery(), NRF.getBattery(), Bangle.isCharging()]; return [E.getBattery(), NRF.getBattery().toFixed(2), Bangle.isCharging()];
}, },
start : () => { start : () => {
}, },
@ -120,9 +120,9 @@ exports.getRecorders = function() {
recorders['baro'] = function() { recorders['baro'] = function() {
var temp="",press="",alt=""; var temp="",press="",alt="";
function onPress(c) { function onPress(c) {
temp=c.temperature; temp=c.temperature.toFixed(1);
press=c.pressure; press=c.pressure.toFixed(2);
alt=c.altitude; alt=c.altitude.toFixed(2);
} }
return { return {
name : "Baro", name : "Baro",

View File

@ -2,7 +2,7 @@
"id": "recorder", "id": "recorder",
"name": "Recorder", "name": "Recorder",
"shortName": "Recorder", "shortName": "Recorder",
"version": "0.45", "version": "0.47",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.", "description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors,gps,widget,clkinfo", "tags": "tool,outdoors,gps,widget,clkinfo",

View File

@ -87,3 +87,4 @@ of 'Select Clock'
0.76: Add altitude calibration menu (and update README after menu changed) 0.76: Add altitude calibration menu (and update README after menu changed)
0.77: Save altitude calibration when user exits via reset 0.77: Save altitude calibration when user exits via reset
0.78: Fix menu scroll restore on BangleJS1 0.78: Fix menu scroll restore on BangleJS1
0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily

View File

@ -1,7 +1,7 @@
{ {
"id": "setting", "id": "setting",
"name": "Settings", "name": "Settings",
"version": "0.78", "version": "0.79",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"icon": "settings.png", "icon": "settings.png",
"tags": "tool,system", "tags": "tool,system",

View File

@ -1033,8 +1033,8 @@ function showTouchscreenCalibration() {
// Calibrate altitude - Bangle.js2 only // Calibrate altitude - Bangle.js2 only
function showAltitude() { function showAltitude() {
function onPressure(pressure) { function onPressure(pressure) {
menuPressure.value = Math.round(pressure.pressure); menuPressure.value = Math.round(pressure.pressure).toString(); // toString stops tapping on the item bringing up an adjustment menu
menuAltitude.value = Math.round(pressure.altitude); menuAltitude.value = Math.round(pressure.altitude).toString();
m.draw(); m.draw();
} }
function altitudeDone() { function altitudeDone() {

View File

@ -3,10 +3,10 @@
"name":"Sleep Log", "name":"Sleep Log",
"shortName": "SleepLog", "shortName": "SleepLog",
"version": "0.19", "version": "0.19",
"description": "Log and view your sleeping habits. This app is using the built in movement calculation.", "description": "Log and view your sleeping habits. This app uses built in movement calculations. View data from Bangle, or from the web app.",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
"tags": "tool,boot", "tags": "tool,boot,health",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"interface": "interface.html", "interface": "interface.html",

2
core

@ -1 +1 @@
Subproject commit 7e7475ba3ab253099481a81e487aaacb9384f974 Subproject commit 0916756932699d626555171ce8e0a2989b151c89