Merge branch 'espruino:master' into master

pull/1589/head
smekras 2022-03-14 12:45:50 +01:00 committed by GitHub
commit 12f86e4c83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 604 additions and 178 deletions

View File

@ -46,3 +46,4 @@
0.40: Bootloader now rebuilds for new firmware versions 0.40: Bootloader now rebuilds for new firmware versions
0.41: Add Keyboard and Mouse Bluetooth HID option 0.41: Add Keyboard and Mouse Bluetooth HID option
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js 0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
0.43: Fix Gadgetbridge handling with Programmable:off

View File

@ -38,7 +38,7 @@ LoopbackA.setConsole(true);\n`;
boot += ` boot += `
Bluetooth.line=""; Bluetooth.line="";
Bluetooth.on('data',function(d) { Bluetooth.on('data',function(d) {
var l = (Bluetooth.line + d).split("\n"); var l = (Bluetooth.line + d).split(/[\\n\\r]/);
Bluetooth.line = l.pop(); Bluetooth.line = l.pop();
l.forEach(n=>Bluetooth.emit("line",n)); l.forEach(n=>Bluetooth.emit("line",n));
}); });
@ -196,7 +196,7 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
// Append *.boot.js files // Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed
var getPriority = /.*\.(\d+)\.boot\.js$/; var getPriority = /.*\.(\d+)\.boot\.js$/;
require('Storage').list(/\.boot\.js/).sort((a,b)=>{ require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
var aPriority = a.match(getPriority); var aPriority = a.match(getPriority);
var bPriority = b.match(getPriority); var bPriority = b.match(getPriority);
if (aPriority && bPriority){ if (aPriority && bPriority){
@ -206,7 +206,7 @@ require('Storage').list(/\.boot\.js/).sort((a,b)=>{
} else if (!aPriority && bPriority){ } else if (!aPriority && bPriority){
return 1; return 1;
} }
return a > b; return a==b ? 0 : (a>b ? 1 : -1);
}).forEach(bootFile=>{ }).forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }() // we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.42", "version": "0.43",
"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 +1,2 @@
0.01: Initial upload 0.01: Initial upload
0.2: Added scrollable calendar and swipe gestures

View File

@ -3,10 +3,14 @@
This is my "Hello World". I first made this watchface almost 10 years ago for my original Pebble and Pebble Time and I missed this so much, that I had to write it for the BangleJS2. This is my "Hello World". I first made this watchface almost 10 years ago for my original Pebble and Pebble Time and I missed this so much, that I had to write it for the BangleJS2.
I know that it seems redundant because there already **is** a *time&cal*-app, but it didn't fit my style. I know that it seems redundant because there already **is** a *time&cal*-app, but it didn't fit my style.
- locked screen with only one minimal update/minute |Screenshot|description|
- ![locked screen](https://foostuff.github.io/BangleApps/apps/clockcal/screenshot.png) |:--:|:-|
- unlocked screen (twist?) with seconds |![locked screen](screenshot.png)|locked: triggers only one minimal update/min|
- ![unlocked screen](https://foostuff.github.io/BangleApps/apps/clockcal/screenshot2.png) |![unlocked screen](screenshot2.png)|unlocked: smaller clock, but with seconds|
|![big calendar](screenshot3.png)|swipe up for big calendar, (up down to scroll, left/right to exit)|
## Configurable Features ## Configurable Features
- Number of calendar rows (weeks) - Number of calendar rows (weeks)
@ -15,6 +19,14 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
- First day of the week - First day of the week
- Red Saturday - Red Saturday
- Red Sunday - Red Sunday
- Swipes (to disable all gestures)
- Swipes: music (swipe down)
- Spipes: messages (swipe right)
## Auto detects your message/music apps:
- swiping down will search your files for an app with the string "music" in its filename and launch it
- swiping right will search your files for an app with the string "message" in its filename and launch it.
- Configurable apps coming soon.
## Feedback ## Feedback
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings. The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.

View File

@ -7,15 +7,116 @@ var s = Object.assign({
FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon
REDSUN: true, // Use red color for sunday? REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday? REDSAT: true, // Use red color for saturday?
DRAGENABLED: true,
DRAGMUSIC: true,
DRAGMESSAGES: true
}, require('Storage').readJSON("clockcal.json", true) || {}); }, require('Storage').readJSON("clockcal.json", true) || {});
const h = g.getHeight(); const h = g.getHeight();
const w = g.getWidth(); const w = g.getWidth();
const CELL_W = w / 7; const CELL_W = w / 7;
const CELL2_W = w / 8;//full calendar
const CELL_H = 15; const CELL_H = 15;
const CAL_Y = h - s.CAL_ROWS * CELL_H; const CAL_Y = h - s.CAL_ROWS * CELL_H;
const DEBUG = false; const DEBUG = false;
var state = "watch";
var monthOffset = 0;
/*
* Calendar features
*/
function drawFullCalendar(monthOffset) {
addMonths = function (_d, _am) {
var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
while ((m + _am) > 11) { ay++; _am -= 12; }
while ((m + _am) < 0) { ay--; _am += 12; }
n = new Date(_d.getTime());
n.setMonth(m + _am);
n.setFullYear(y + ay);
return n;
};
monthOffset = (typeof monthOffset == "undefined") ? 0 : monthOffset;
state = "calendar";
var start = Date().getTime();
const months = ['Jan.', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec.'];
const monthclr = ['#0f0', '#f0f', '#00f', '#ff0', '#0ff', '#fff'];
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
d = addMonths(Date(), monthOffset);
tdy = Date().getDate() + "." + Date().getMonth();
newmonth=false;
c_y = 0;
g.reset();
g.setBgColor(0);
g.clear();
var prevmonth = addMonths(d, -1)
const today = prevmonth.getDate();
var rD = new Date(prevmonth.getTime());
rD.setDate(rD.getDate() - (today - 1));
const dow = (s.FIRSTDAYOFFSET + rD.getDay()) % 7;
rD.setDate(rD.getDate() - dow);
var rDate = rD.getDate();
bottomrightY = c_y - 3;
clrsun=s.REDSUN?'#f00':'#fff';
clrsat=s.REDSUN?'#f00':'#fff';
var fg=[clrsun,'#fff','#fff','#fff','#fff','#fff',clrsat];
for (var y = 1; y <= 11; y++) {
bottomrightY += CELL_H;
bottomrightX = -2;
for (var x = 1; x <= 7; x++) {
bottomrightX += CELL2_W;
rMonth = rD.getMonth();
rDate = rD.getDate();
if (tdy == rDate + "." + rMonth) {
caldrawToday(rDate);
} else if (rDate == 1) {
caldrawFirst(rDate);
} else {
caldrawNormal(rDate,fg[rD.getDay()]);
}
if (newmonth && x == 7) {
caldrawMonth(rDate,monthclr[rMonth % 6],months[rMonth],rD);
}
rD.setDate(rDate + 1);
}
}
delete addMonths;
if (DEBUG) console.log("Calendar performance (ms):" + (Date().getTime() - start));
}
function caldrawMonth(rDate,c,m,rD) {
g.setColor(c);
g.setFont("Vector", 18);
g.setFontAlign(-1, 1, 1);
drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
newmonth = false;
}
function caldrawToday(rDate) {
g.setFont("Vector", 16);
g.setFontAlign(1, 1);
g.setColor('#0f0');
g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
g.setColor('#000');
g.drawString(rDate, bottomrightX, bottomrightY);
}
function caldrawFirst(rDate) {
g.flip();
g.setFont("Vector", 16);
g.setFontAlign(1, 1);
bottomrightY += 3;
newmonth = true;
g.setColor('#0ff');
g.fillRect(bottomrightX - CELL2_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
g.setColor('#000');
g.drawString(rDate, bottomrightX, bottomrightY);
}
function caldrawNormal(rDate,c) {
g.setFont("Vector", 16);
g.setFontAlign(1, 1);
g.setColor(c);
g.drawString(rDate, bottomrightX, bottomrightY);//100
}
function drawMinutes() { function drawMinutes() {
if (DEBUG) console.log("|-->minutes"); if (DEBUG) console.log("|-->minutes");
var d = new Date(); var d = new Date();
@ -52,8 +153,10 @@ function drawSeconds() {
if (!dimSeconds) secondInterval = setTimeout(drawSeconds, 1000); if (!dimSeconds) secondInterval = setTimeout(drawSeconds, 1000);
} }
function drawCalendar() { function drawWatch() {
if (DEBUG) console.log("CALENDAR"); if (DEBUG) console.log("CALENDAR");
monthOffset = 0;
state = "watch";
var d = new Date(); var d = new Date();
g.reset(); g.reset();
g.setBgColor(0); g.setBgColor(0);
@ -91,7 +194,7 @@ function drawCalendar() {
var nextday = (3600 * 24) - (d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds() + 1); var nextday = (3600 * 24) - (d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds() + 1);
if (DEBUG) console.log("Next Day:" + (nextday / 3600)); if (DEBUG) console.log("Next Day:" + (nextday / 3600));
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval); if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
dayInterval = setTimeout(drawCalendar, nextday * 1000); dayInterval = setTimeout(drawWatch, nextday * 1000);
} }
function BTevent() { function BTevent() {
@ -103,17 +206,87 @@ function BTevent() {
} }
} }
function input(dir) {
if (s.DRAGENABLED) {
Bangle.buzz(100,1);
console.log("swipe:"+dir);
switch (dir) {
case "r":
if (state == "calendar") {
drawWatch();
} else {
if (s.DRAGMUSIC) {
l=require("Storage").list(RegExp("music.*app"));
if (l.length > 0) {
load(l[0]);
} else Bangle.buzz(3000,1);//not found
}
}
break;
case "l":
if (state == "calendar") {
drawWatch();
}
break;
case "d":
if (state == "calendar") {
monthOffset--;
drawFullCalendar(monthOffset);
} else {
if (s.DRAGMESSAGES) {
l=require("Storage").list(RegExp("message.*app"));
if (l.length > 0) {
load(l[0]);
} else Bangle.buzz(3000,1);//not found
}
}
break;
case "u":
if (state == "watch") {
state = "calendar";
drawFullCalendar(0);
} else if (state == "calendar") {
monthOffset++;
drawFullCalendar(monthOffset);
}
break;
default:
if (state == "calendar") {
drawWatch();
}
break;
}
}
}
let drag;
Bangle.on("drag", e => {
if (s.DRAGENABLED) {
if (!drag) {
drag = { x: e.x, y: e.y };
} else if (!e.b) {
const dx = e.x - drag.x, dy = e.y - drag.y;
var dir = "t";
if (Math.abs(dx) > Math.abs(dy) + 10) {
dir = (dx > 0) ? "r" : "l";
} else if (Math.abs(dy) > Math.abs(dx) + 10) {
dir = (dy > 0) ? "d" : "u";
}
drag = null;
input(dir);
}
}
});
//register events //register events
Bangle.on('lock', locked => { Bangle.on('lock', locked => {
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval); if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
dimSeconds = locked; //dim seconds if lock=on dimSeconds = locked; //dim seconds if lock=on
drawCalendar(); drawWatch();
}); });
NRF.on('connect', BTevent); NRF.on('connect', BTevent);
NRF.on('disconnect', BTevent); NRF.on('disconnect', BTevent);
dimSeconds = Bangle.isLocked(); dimSeconds = Bangle.isLocked();
drawCalendar(); drawWatch();
Bangle.setUI("clock"); Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{ {
"id": "clockcal", "id": "clockcal",
"name": "Clock & Calendar", "name": "Clock & Calendar",
"version": "0.01", "version": "0.2",
"description": "Clock with Calendar", "description": "Clock with Calendar",
"readme":"README.md", "readme":"README.md",
"icon": "app.png", "icon": "app.png",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -8,6 +8,9 @@
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday? REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday? REDSAT: true, // Use red color for saturday?
DRAGENABLED: true, //Enable drag gestures (bigger calendar etc)
DRAGMUSIC: true, //Enable drag down for music (looks for "music*app")
DRAGMESSAGES: true //Enable drag right for messages (looks for "message*app")
}, require('Storage').readJSON(FILE, true) || {}); }, require('Storage').readJSON(FILE, true) || {});
@ -67,6 +70,30 @@
writeSettings(); writeSettings();
} }
}, },
'Swipes (big cal.)?': {
value: settings.DRAGENABLED,
format: v => v ? "On" : "Off",
onchange: v => {
settings.DRAGENABLED = v;
writeSettings();
}
},
'Swipes (music)?': {
value: settings.DRAGMUSIC,
format: v => v ? "On" : "Off",
onchange: v => {
settings.DRAGMUSIC = v;
writeSettings();
}
},
'Swipes (messg)?': {
value: settings.DRAGMESSAGES,
format: v => v ? "On" : "Off",
onchange: v => {
settings.DRAGMESSAGES = v;
writeSettings();
}
},
'Load deafauls?': { 'Load deafauls?': {
value: 0, value: 0,
min: 0, max: 1, min: 0, max: 1,
@ -80,13 +107,16 @@
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday? REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday? REDSAT: true, // Use red color for saturday?
DRAGENABLED: true,
DRAGMUSIC: true,
DRAGMESSAGES: true
}; };
writeSettings(); writeSettings();
load() load();
} }
} }
}, },
} };
// Show the menu // Show the menu
E.showMenu(menu); E.showMenu(menu);
}) });

View File

@ -5,3 +5,4 @@
0.05: Add cadence sensor support 0.05: Add cadence sensor support
0.06: Now read wheel rev as well as cadence sensor 0.06: Now read wheel rev as well as cadence sensor
Improve connection code Improve connection code
0.07: Make Bangle.js 2 compatible

View File

@ -11,9 +11,9 @@ Currently the app displays the following data:
- total distance traveled - total distance traveled
- an icon with the battery status of the remote sensor - an icon with the battery status of the remote sensor
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app. Button 1 (swipe up on Bangle.js 2) resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor. If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 (swipe down on Bangle.js 2) will attempt to reconnect to the sensor.
Button 2 switches between the display for cycling speed and cadence. Button 2 (tap on Bangle.js 2) switches between the display for cycling speed and cadence.
Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app. Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app.

View File

@ -7,6 +7,11 @@ const SETTINGS_FILE = 'cscsensor.json';
const storage = require('Storage'); const storage = require('Storage');
const W = g.getWidth(); const W = g.getWidth();
const H = g.getHeight(); const H = g.getHeight();
const yStart = 48;
const rowHeight = (H-yStart)/6;
const yCol1 = W/2.7586;
const fontSizeLabel = W/12.632;
const fontSizeValue = W/9.2308;
class CSCSensor { class CSCSensor {
constructor() { constructor() {
@ -22,7 +27,6 @@ class CSCSensor {
this.speed = 0; this.speed = 0;
this.maxSpeed = 0; this.maxSpeed = 0;
this.lastSpeed = 0; this.lastSpeed = 0;
this.qUpdateScreen = true;
this.lastRevsStart = -1; this.lastRevsStart = -1;
this.qMetric = !require("locale").speed(1).toString().endsWith("mph"); this.qMetric = !require("locale").speed(1).toString().endsWith("mph");
this.speedUnit = this.qMetric ? "km/h" : "mph"; this.speedUnit = this.qMetric ? "km/h" : "mph";
@ -49,6 +53,7 @@ class CSCSensor {
toggleDisplayCadence() { toggleDisplayCadence() {
this.showCadence = !this.showCadence; this.showCadence = !this.showCadence;
this.screenInit = true; this.screenInit = true;
g.setBgColor(0, 0, 0);
} }
setBatteryLevel(level) { setBatteryLevel(level) {
@ -63,14 +68,16 @@ class CSCSensor {
} }
drawBatteryIcon() { drawBatteryIcon() {
g.setColor(1, 1, 1).drawRect(10, 55, 20, 75).fillRect(14, 53, 16, 55).setColor(0).fillRect(11, 56, 19, 74); g.setColor(1, 1, 1).drawRect(10*W/240, yStart+0.029167*H, 20*W/240, yStart+0.1125*H)
.fillRect(14*W/240, yStart+0.020833*H, 16*W/240, yStart+0.029167*H)
.setColor(0).fillRect(11*W/240, yStart+0.033333*H, 19*W/240, yStart+0.10833*H);
if (this.batteryLevel!=-1) { if (this.batteryLevel!=-1) {
if (this.batteryLevel<25) g.setColor(1, 0, 0); if (this.batteryLevel<25) g.setColor(1, 0, 0);
else if (this.batteryLevel<50) g.setColor(1, 0.5, 0); else if (this.batteryLevel<50) g.setColor(1, 0.5, 0);
else g.setColor(0, 1, 0); else g.setColor(0, 1, 0);
g.fillRect(11, 74-18*this.batteryLevel/100, 19, 74); g.fillRect(11*W/240, (yStart+0.10833*H)-18*this.batteryLevel/100, 19*W/240, yStart+0.10833*H);
} }
else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66); else g.setFontVector(W/17.143).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16*W/240, yStart+0.075*H);
} }
updateScreenRevs() { updateScreenRevs() {
@ -88,36 +95,36 @@ class CSCSensor {
for (var i=0; i<6; ++i) { for (var i=0; i<6; ++i) {
if ((i&1)==0) g.setColor(0, 0, 0); if ((i&1)==0) g.setColor(0, 0, 0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight);
if ((i&1)==1) g.setColor(0); if ((i&1)==1) g.setColor(0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight);
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1);
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H);
} }
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0);
g.drawString("Time:", 87, 66); g.drawString("Time:", yCol1, yStart+rowHeight/2+0*rowHeight);
g.drawString("Speed:", 87, 98); g.drawString("Speed:", yCol1, yStart+rowHeight/2+1*rowHeight);
g.drawString("Ave spd:", 87, 130); g.drawString("Avg spd:", yCol1, yStart+rowHeight/2+2*rowHeight);
g.drawString("Max spd:", 87, 162); g.drawString("Max spd:", yCol1, yStart+rowHeight/2+3*rowHeight);
g.drawString("Trip:", 87, 194); g.drawString("Trip:", yCol1, yStart+rowHeight/2+4*rowHeight);
g.drawString("Total:", 87, 226); g.drawString("Total:", yCol1, yStart+rowHeight/2+5*rowHeight);
this.drawBatteryIcon(); this.drawBatteryIcon();
this.screenInit = false; this.screenInit = false;
} }
g.setFontAlign(-1, 0, 0).setFontVector(26); g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue);
g.setColor(0x30cd).fillRect(88, 49, 238, 79); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*0, 238, 47+1*rowHeight);
g.setColor(0xffff).drawString(dmins+":"+dsecs, 92, 66); g.setColor(0xffff).drawString(dmins+":"+dsecs, yCol1+5, 50+rowHeight/2+0*rowHeight);
g.setColor(0).fillRect(88, 81, 238, 111); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight);
g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, 92, 98); g.setColor(0xffff).drawString(dspeed+" "+this.speedUnit, yCol1+5, 50+rowHeight/2+1*rowHeight);
g.setColor(0x30cd).fillRect(88, 113, 238, 143); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*2, 238, 47+3*rowHeight);
g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, 92, 130); g.setColor(0xffff).drawString(avespeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+2*rowHeight);
g.setColor(0).fillRect(88, 145, 238, 175); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*3, 238, 47+4*rowHeight);
g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, 92, 162); g.setColor(0xffff).drawString(maxspeed + " " + this.speedUnit, yCol1+5, 50+rowHeight/2+3*rowHeight);
g.setColor(0x30cd).fillRect(88, 177, 238, 207); g.setColor(0x30cd).fillRect(yCol1+1, 49+rowHeight*4, 238, 47+5*rowHeight);
g.setColor(0xffff).drawString(ddist + " " + this.distUnit, 92, 194); g.setColor(0xffff).drawString(ddist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+4*rowHeight);
g.setColor(0).fillRect(88, 209, 238, 238); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*5, 238, 47+6*rowHeight);
g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226); g.setColor(0xffff).drawString(tdist + " " + this.distUnit, yCol1+5, 50+rowHeight/2+5*rowHeight);
} }
updateScreenCadence() { updateScreenCadence() {
@ -125,21 +132,21 @@ class CSCSensor {
for (var i=0; i<2; ++i) { for (var i=0; i<2; ++i) {
if ((i&1)==0) g.setColor(0, 0, 0); if ((i&1)==0) g.setColor(0, 0, 0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32); g.fillRect(0, yStart+i*rowHeight, yCol1-1, yStart+(i+1)*rowHeight);
if ((i&1)==1) g.setColor(0); if ((i&1)==1) g.setColor(0);
else g.setColor(0x30cd); else g.setColor(0x30cd);
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32); g.fillRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight);
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239); g.setColor(0.5, 0.5, 0.5).drawRect(yCol1, yStart+i*rowHeight, H-1, yStart+(i+1)*rowHeight).drawLine(0, H-1, W-1, H-1);
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80); g.moveTo(0, yStart+0.13333*H).lineTo(30*W/240, yStart+0.13333*H).lineTo(30*W/240, yStart).lineTo(yCol1, yStart).lineTo(yCol1, H-1).lineTo(0, H-1).lineTo(0, yStart+0.13333*H);
} }
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0); g.setFontAlign(1, 0, 0).setFontVector(fontSizeLabel).setColor(1, 1, 0);
g.drawString("Cadence:", 87, 98); g.drawString("Cadence:", yCol1, yStart+rowHeight/2+1*rowHeight);
this.drawBatteryIcon(); this.drawBatteryIcon();
this.screenInit = false; this.screenInit = false;
} }
g.setFontAlign(-1, 0, 0).setFontVector(26); g.setFontAlign(-1, 0, 0).setFontVector(fontSizeValue);
g.setColor(0).fillRect(88, 81, 238, 111); g.setColor(0).fillRect(yCol1+1, 49+rowHeight*1, 238, 47+2*rowHeight);
g.setColor(0xffff).drawString(Math.round(this.cadence), 92, 98); g.setColor(0xffff).drawString(Math.round(this.cadence), yCol1+5, 50+rowHeight/2+1*rowHeight);
} }
updateScreen() { updateScreen() {
@ -163,7 +170,7 @@ class CSCSensor {
} }
this.lastCrankRevs = crankRevs; this.lastCrankRevs = crankRevs;
this.lastCrankTime = crankTime; this.lastCrankTime = crankTime;
} } else {
// wheel revolution // wheel revolution
var wheelRevs = event.target.value.getUint32(1, true); var wheelRevs = event.target.value.getUint32(1, true);
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0); var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
@ -189,8 +196,7 @@ class CSCSensor {
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT; this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
this.speedFailed = 0; this.speedFailed = 0;
this.movingTime += dT; this.movingTime += dT;
} } else if (!this.showCadence) {
else {
this.speedFailed++; this.speedFailed++;
qChanged = false; qChanged = false;
if (this.speedFailed>3) { if (this.speedFailed>3) {
@ -201,7 +207,8 @@ class CSCSensor {
this.lastSpeed = this.speed; this.lastSpeed = this.speed;
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed; if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
} }
if (qChanged && this.qUpdateScreen) this.updateScreen(); }
if (qChanged) this.updateScreen();
} }
} }
@ -253,9 +260,9 @@ E.on('kill',()=>{
}); });
NRF.on('disconnect', connection_setup); // restart if disconnected NRF.on('disconnect', connection_setup); // restart if disconnected
Bangle.setUI("updown", d=>{ Bangle.setUI("updown", d=>{
if (d<0) { mySensor.reset(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } if (d<0) { mySensor.reset(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
if (d==0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); } else if (d>0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }
if (d>0) { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, W, H); mySensor.updateScreen(); } else { mySensor.toggleDisplayCadence(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
}); });
Bangle.loadWidgets(); Bangle.loadWidgets();

View File

@ -2,11 +2,11 @@
"id": "cscsensor", "id": "cscsensor",
"name": "Cycling speed sensor", "name": "Cycling speed sensor",
"shortName": "CSCSensor", "shortName": "CSCSensor",
"version": "0.06", "version": "0.07",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png", "icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth", "tags": "outdoors,exercise,ble,bluetooth",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"cscsensor.app.js","url":"cscsensor.app.js"}, {"name":"cscsensor.app.js","url":"cscsensor.app.js"},

View File

@ -7,7 +7,7 @@ screen. Very useful if combined with pattern launcher ;)
![](screenshot_1.png) ![](screenshot_1.png)
![](screenshot_2.png) ![](screenshot_2.png)
![](screenshot_2.png) ![](screenshot_3.png)
## Contributors ## Contributors

View File

@ -2,3 +2,4 @@
0.02: Fix typo to Purple 0.02: Fix typo to Purple
0.03: Added dependancy on Pedometer Widget 0.03: Added dependancy on Pedometer Widget
0.04: Fixed icon and png to 48x48 pixels 0.04: Fixed icon and png to 48x48 pixels
0.05: added charging icon

View File

@ -2,7 +2,7 @@
"id": "rebble", "id": "rebble",
"name": "Rebble Clock", "name": "Rebble Clock",
"shortName": "Rebble", "shortName": "Rebble",
"version": "0.04", "version": "0.05",
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
"readme": "README.md", "readme": "README.md",
"icon": "rebble.png", "icon": "rebble.png",

View File

@ -204,6 +204,14 @@ function drawBattery(x,y,wi,hi) {
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.fillRect(x+wi-3,y+2+(((hi - 1)/2)-1),x+wi-2,y+2+(((hi - 1)/2)-1)+4); // contact g.fillRect(x+wi-3,y+2+(((hi - 1)/2)-1),x+wi-2,y+2+(((hi - 1)/2)-1)+4); // contact
g.fillRect(x+3, y+5, x +4 + E.getBattery()*(wi-12)/100, y+hi-1); // the level g.fillRect(x+3, y+5, x +4 + E.getBattery()*(wi-12)/100, y+hi-1); // the level
if( Bangle.isCharging() )
{
g.setBgColor(settings.bg);
image = ()=> { return require("heatshrink").decompress(atob("j8OwMB/4AD94DC44DCwP//n/gH//EOgE/+AdBh/gAYMH4EAvkDAYP/+/AFAX+FgfzGAnAA=="));}
g.drawImage(image(),x+3,y+4);
}
} }
function getSteps() { function getSteps() {
@ -270,3 +278,14 @@ for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
loadSettings(); loadSettings();
loadLocation(); loadLocation();
draw(); // queues the next draw for a minutes time draw(); // queues the next draw for a minutes time
Bangle.on('charging', function(charging) {
//redraw the sidebar ( with the battery )
switch(sideBar) {
case 0:
drawSideBar1();
break;
case 1:
drawSideBar2();
break;
}
});

View File

@ -7,3 +7,4 @@
0.06: Add option to record a run using the recorder app automatically 0.06: Add option to record a run using the recorder app automatically
0.07: Fix crash if an odd number of active boxes are configured (fix #1473) 0.07: Fix crash if an odd number of active boxes are configured (fix #1473)
0.08: Added support for notifications from exstats. Support all stats from exstats 0.08: Added support for notifications from exstats. Support all stats from exstats
0.09: Fix broken start/stop if recording not enabled (fix #1561)

View File

@ -66,6 +66,9 @@ function onStartStop() {
} }
} }
if (!prepPromises.length) // fix for Promise.all bug in 2v12
prepPromises.push(Promise.resolve());
Promise.all(prepPromises) Promise.all(prepPromises)
.then(() => { .then(() => {
if (running) { if (running) {

View File

@ -1,6 +1,6 @@
{ "id": "run", { "id": "run",
"name": "Run", "name": "Run",
"version":"0.08", "version":"0.09",
"description": "Displays distance, time, steps, cadence, pace and more for runners.", "description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png", "icon": "app.png",
"tags": "run,running,fitness,outdoors,gps", "tags": "run,running,fitness,outdoors,gps",

View File

@ -9,3 +9,4 @@
0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. 0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight.
0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. 0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings.
0.11: Now also runs on Bangle.js 2 with basic functionality 0.11: Now also runs on Bangle.js 2 with basic functionality
0.12: Full functionality on Bangle.js 2: Bangle.js 1 buttons mapped to touch areas.

View File

@ -2,23 +2,21 @@
You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint.
*Note for **Bangle.js 2:** Currently only the BTN3 functionality is working with the Bangle.js 2 button.*
Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed.
The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information.
## Buttons and Controls ## Buttons and Controls
BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint *(Mapping for **Bangle.js 2**: BTN2 = Touch upper right side; BTN3 = Touch lower right side; BTN4 = Touch left side)*
***Bangle.js 2:** Currently only this button function is working* BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint
### [A]ltitude mode ### [A]ltitude mode
BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded.
BTN1 : Long press > 2 secs resets the recorded maximum values. BTN1 : Long press > 2 secs resets the recorded maximum values. *(Bangle.js 2: Long press > 0.4 secs)*
### [D]istance mode ### [D]istance mode
@ -32,7 +30,7 @@ BTN1 : Select next waypoint.
BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts.
BTN3 : Long press exit and return to watch. BTN3 : Long press exit and return to watch. *(Bangle.js 2: Long press BTN > 2 secs)*
BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display.

View File

@ -349,7 +349,7 @@ function drawSecondary(n,u) {
s = 30; // Font size s = 30; // Font size
if (BANGLEJS2) s *= fontFactorB2; if (BANGLEJS2) s *= fontFactorB2;
buf.setFontVector(s); buf.setFontVector(s);
buf.drawString(u,xu - (BANGLEJS2*20),screenH_TwoThirds-25); buf.drawString(u,xu - (BANGLEJS2*xu/5),screenH_TwoThirds-25);
} }
function drawTime() { function drawTime() {
@ -391,7 +391,7 @@ function drawWP() { // from waypoints.json - see README.md
buf.setFontAlign(-1,1); //left, bottom buf.setFontAlign(-1,1); //left, bottom
if (BANGLEJS2) s *= fontFactorB2; if (BANGLEJS2) s *= fontFactorB2;
buf.setFontVector(s); buf.setFontVector(s);
buf.drawString(nm.substring(0,6),72,screenH_TwoThirds-(BANGLEJS2 * 20)); buf.drawString(nm.substring(0,6),72,screenH_TwoThirds-(BANGLEJS2 * 15));
} }
if ( cfg.modeA == 2 ) { // clock/large mode if ( cfg.modeA == 2 ) { // clock/large mode
@ -421,7 +421,7 @@ function drawSats(sats) {
buf.drawString('A',screenW,140-(BANGLEJS2 * 40)); buf.drawString('A',screenW,140-(BANGLEJS2 * 40));
if ( showMax ) { if ( showMax ) {
buf.setFontAlign(0,1); //centre, bottom buf.setFontAlign(0,1); //centre, bottom
buf.drawString('MAX',120,164); buf.drawString('MAX',screenW_Half,screenH_TwoThirds + 4);
} }
} }
if ( cfg.modeA == 0 ) buf.drawString('D',screenW,140-(BANGLEJS2 * 40)); if ( cfg.modeA == 0 ) buf.drawString('D',screenW,140-(BANGLEJS2 * 40));
@ -536,22 +536,18 @@ function onGPS(fix) {
} }
function setButtons(){
if (!BANGLEJS2) { // Buttons for Bangle.js function btn1press(longpress) {
// Spd+Dist : Select next waypoint if(emulator) console.log("Btn1, long="+longpress);
setWatch(function(e) { if ( cfg.modeA == 1 ) { // Spd+Alt mode - Switch between fix and MAX
var dur = e.time - e.lastTime; if ( !longpress ) showMax = !showMax; // Short press toggle fix/max display
if ( 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 { max.spd = 0; max.alt = 0; } // Long press resets max values.
} }
else nxtWp(1); // Spd+Dist or Clock mode - Select next waypoint else nxtWp(1); // Spd+Dist or Clock mode - Select next waypoint
onGPS(lf); onGPS(lf);
}, BTN1, { edge:"falling",repeat:true}); }
function btn2press(){
// Power saving on/off if(emulator) console.log("Btn2");
setWatch(function(e){
pwrSav=!pwrSav; pwrSav=!pwrSav;
if ( pwrSav ) { if ( pwrSav ) {
LED1.reset(); LED1.reset();
@ -564,52 +560,51 @@ if (!BANGLEJS2) { // Buttons for Bangle.js
Bangle.setLCDPower(1); Bangle.setLCDPower(1);
LED1.set(); LED1.set();
} }
}, BTN2, {repeat:true,edge:"falling"}); }
function btn3press(){
// Toggle between alt or dist if(emulator) console.log("Btn3");
setWatch(function(e){
cfg.modeA = cfg.modeA+1; cfg.modeA = cfg.modeA+1;
if ( cfg.modeA > 2 ) cfg.modeA = 0; if ( cfg.modeA > 2 ) cfg.modeA = 0;
if(emulator)console.log("cfg.modeA="+cfg.modeA);
savSettings(); savSettings();
onGPS(lf); onGPS(lf);
}, BTN3, {repeat:true,edge:"falling"}); }
function btn4press(){
// Touch left screen to toggle display if(emulator) console.log("Btn4");
setWatch(function(e){
cfg.primSpd = !cfg.primSpd; cfg.primSpd = !cfg.primSpd;
savSettings(); savSettings();
onGPS(lf); // Update display onGPS(lf); // Update display
}, BTN4, {repeat:true,edge:"falling"}); }
function setButtons(){
if (!BANGLEJS2) { // Buttons for Bangle.js 1
setWatch(function(e) {
btn1press(( e.time - e.lastTime) > 2); // > 2 sec. is long press
}, BTN1, { edge:"falling",repeat:true});
// Power saving on/off (red dot visible if off)
setWatch(btn2press, BTN2, {repeat:true,edge:"falling"});
// Toggle between alt or dist
setWatch(btn3press, BTN3, {repeat:true,edge:"falling"});
// Touch left screen to toggle display
setWatch(btn4press, BTN4, {repeat:true,edge:"falling"});
} else { // Buttons for Bangle.js 2 } else { // Buttons for Bangle.js 2
setWatch(function(e){ // Bangle.js BTN3 setWatch(function(e) {
cfg.modeA = cfg.modeA+1; btn1press(( e.time - e.lastTime) > 0.4); // > 0.4 sec. is long press
if ( cfg.modeA > 2 ) cfg.modeA = 0; }, BTN1, { edge:"falling",repeat:true});
if(emulator)console.log("cfg.modeA="+cfg.modeA);
savSettings();
onGPS(lf);
}, BTN1, {repeat:true,edge:"falling"});
/* Bangle.on('tap', function(data) { // data - {dir, double, x, y, z} Bangle.on('touch', function(btn_l_r, e) {
cfg.primSpd = !cfg.primSpd; if(e.x < screenW_Half) btn4press();
if(emulator)console.log("!cfg.primSpd");
}); */
/* Bangle.on('swipe', function(dir) {
if (dir < 0) { // left: Bangle.js BTN3
cfg.modeA = cfg.modeA+1;
if ( cfg.modeA > 2 ) cfg.modeA = 0;
if(emulator)console.log("cfg.modeA="+cfg.modeA);
}
else else
{ // right: Bangle.js BTN4 if (e.y < screenH_Half)
cfg.primSpd = !cfg.primSpd; btn2press();
if(emulator)console.log("!cfg.primSpd"); else
} btn3press();
}); });
*/
savSettings();
onGPS(lf);
} }
} }
@ -700,18 +695,6 @@ Bangle.on('lcdPower',function(on) {
else stopDraw(); else stopDraw();
}); });
/*
function onGPSraw(nmea) {
var nofGP = 0, nofBD = 0, nofGL = 0;
if (nmea.slice(3,6) == "GSV") {
// console.log(nmea.slice(1,3) + " " + nmea.slice(11,13));
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
SATinView = nofGP + nofBD + nofGL;
} }
if(BANGLEJS2) Bangle.on('GPS-raw', onGPSraw);
*/
var gpssetup; var gpssetup;
try { try {

View File

@ -2,7 +2,7 @@
"id": "speedalt", "id": "speedalt",
"name": "GPS Adventure Sports", "name": "GPS Adventure Sports",
"shortName": "GPS Adv Sport", "shortName": "GPS Adv Sport",
"version": "0.11", "version": "0.12",
"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.", "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", "icon": "app.png",
"type": "app", "type": "app",

1
apps/todolist/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial release

40
apps/todolist/README.md Normal file
View File

@ -0,0 +1,40 @@
Todo List
========
This is a simple Todo List application.
![](screenshot2.png)
The content is loaded from a JSON file.
You can mark a task as completed.
JSON file content example:
```javascript
[
{
name: "Pro",
children: [
{
name: "Read doc",
done: true,
children: [],
}
],
},
{
name: "Pers",
children: [
{
name: "Grocery",
children: [
{ name: "Milk", done: false, children: [] },
{ name: "Eggs", done: false, children: [] },
{ name: "Cheese", done: false, children: [] },
],
},
{ name: "Workout", done: false, children: [] },
{ name: "Learn Rust", done: false, children: [] },
],
},
]
```

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgmjiMRiAWTgIXUCoYZQB4IADC4YHECxkSkIECkQYLEwMSkQQBkcyCAMTmYKEiIuGif/AAIXBmciiUzC4MvBQPyC44LCC4YADBYpIFiM/BYZDBC5EhC4wKCBYKLFEYkxC5UxCwsSBYgXK/5GEmYuDC5oAKC/4XUmK5DC6PziMfC6cimTRB+bbDiSpCC5ItBaIXxbIg2CF5QqBB4IcCAAQvMCYMhdIi//X7P/X6sz+S/CkQADX8gXCif/GQIADMwS/LZ4a//BgkyJBK/ll/zmYADX54FBX9cyB4ZHEO5wPDa/7RJAAshC4xyCABacBC40SGBsxiIWEgEBW4gAKFwowCABwWGACgA=="))

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

@ -0,0 +1,129 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
// Const
let TODOLIST_FILE = "todolist.json";
let MAX_DESCRIPTION_LEN = 14;
// Clear todolist file
// require("Storage").erase(TODOLIST_FILE);
let DEFAULT_TODOLIST = [
{
name: "Pro",
children: [
{
name: "Read doc",
done: true,
children: [],
},
],
},
{
name: "Pers",
children: [
{
name: "Grocery",
children: [
{ name: "Milk", done: false, children: [] },
{ name: "Eggs", done: false, children: [] },
{ name: "Cheese", done: false, children: [] },
],
},
{ name: "Workout", done: false, children: [] },
{ name: "Learn Rust", done: false, children: [] },
],
},
];
// Load todolist
let todolist =
require("Storage").readJSON(TODOLIST_FILE, true) || DEFAULT_TODOLIST;
let menus = {};
function writeData() {
require("Storage").writeJSON(TODOLIST_FILE, todolist);
}
function getChild(todolist, indexes) {
let childData = todolist;
for (let i = 0; i < indexes.length; i++) {
childData = childData[indexes[i]];
childData = childData.children;
}
return childData;
}
function getName(item) {
let title = item.name.substr(0, MAX_DESCRIPTION_LEN);
return title;
}
function getParentTitle(todolist, indexes) {
let parentIndexes = indexes.slice(0, indexes.length - 1);
let lastIndex = indexes[indexes.length - 1];
let item = getItem(todolist, parentIndexes, lastIndex);
return getName(item);
}
function getItem(todolist, parentIndexes, index) {
let childData = getChild(todolist, parentIndexes, index);
return childData[index];
}
function toggleableStatus(todolist, indexes, index) {
const reminder = getItem(todolist, indexes, index);
return {
value: !!reminder.done, // !! converts undefined to false
format: (val) => (val ? "[X]" : "[-]"),
onchange: (val) => {
reminder.done = val;
writeData();
},
};
}
function showSubMenu(key) {
const sub_menu = menus[key];
return E.showMenu(sub_menu);
}
function createListItem(todolist, indexes, index) {
let reminder = getItem(todolist, indexes, index);
if (reminder.children.length > 0) {
let childIndexes = [];
for (let i = 0; i < indexes.length; i++) {
childIndexes.push(indexes[i]);
}
childIndexes.push(index);
createMenus(todolist, childIndexes);
return () => showSubMenu(childIndexes);
} else {
return toggleableStatus(todolist, indexes, index);
}
}
function showMainMenu() {
const mainmenu = menus[""];
return E.showMenu(mainmenu);
}
function createMenus(todolist, indexes) {
const menuItem = {};
if (indexes.length == 0) {
menuItem[""] = { title: "todolist" };
} else {
menuItem[""] = { title: getParentTitle(todolist, indexes) };
menuItem["< Back"] = () =>
showSubMenu(indexes.slice(0, indexes.length - 1));
}
for (let i = 0; i < getChild(todolist, indexes).length; i++) {
const item = getItem(todolist, indexes, i);
const name = getName(item);
menuItem[name] = createListItem(todolist, indexes, i);
}
menus[indexes] = menuItem;
}
createMenus(todolist, []);
showMainMenu();

BIN
apps/todolist/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,23 @@
{
"id": "todolist",
"name": "TodoList",
"shortName": "TodoList",
"version": "0.01",
"type": "app",
"description": "Simple Todo List",
"icon": "app.png",
"allow_emulator": true,
"tags": "tool,todo",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name": "todolist.app.js", "url": "app.js" },
{ "name": "todolist.img", "url": "app-icon.js", "evaluate": true }
],
"data": [{ "name": "todolist.json" }],
"screenshots": [
{ "url": "screenshot1.png" },
{ "url": "screenshot2.png" },
{ "url": "screenshot3.png" }
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -5,3 +5,4 @@
0.05: "Chime the time" (buzz or beep) with up/down swipe added 0.05: "Chime the time" (buzz or beep) with up/down swipe added
0.06: Redraw widgets when time is updated 0.06: Redraw widgets when time is updated
0.07: Fix problem with "Bangle.CLOCK": github.com/espruino/BangleApps/issues/1437 0.07: Fix problem with "Bangle.CLOCK": github.com/espruino/BangleApps/issues/1437
0.08: Redraw widgets only once per minute

View File

@ -81,7 +81,7 @@ function draw() {
executeCommands(); executeCommands();
Bangle.drawWidgets(); if (process.env.HWVERSION==2) Bangle.drawWidgets();
} }
var timeout; var timeout;

View File

@ -1,7 +1,7 @@
{ {
"id": "vectorclock", "id": "vectorclock",
"name": "Vector Clock", "name": "Vector Clock",
"version": "0.07", "version": "0.08",
"description": "A digital clock that uses the built-in vector font.", "description": "A digital clock that uses the built-in vector font.",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",