diff --git a/.eslintignore b/.eslintignore
index 4af79d129..38bc5b5bf 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,4 +4,5 @@ apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js
apps/qrcode/qr-scanner.umd.min.js
apps/gipy/pkg/gpconv.js
+apps/health/chart.min.js
*.test.js
diff --git a/.github/ISSUE_TEMPLATE/bangle-bug-report-custom-form.yaml b/.github/ISSUE_TEMPLATE/bangle-bug-report-custom-form.yaml
index 484b3ba85..045a6e18a 100644
--- a/.github/ISSUE_TEMPLATE/bangle-bug-report-custom-form.yaml
+++ b/.github/ISSUE_TEMPLATE/bangle-bug-report-custom-form.yaml
@@ -58,3 +58,7 @@ body:
validations:
required: true
+ - type: textarea
+ id: apps
+ attributes:
+ label: Installed apps
\ No newline at end of file
diff --git a/README.md b/README.md
index fed13a358..aa8afdbca 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ This is the best way to test...
**Note:** It's a great idea to get a local copy of the repository on your PC,
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
-that there might be.
+that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
diff --git a/apps/90sclk/ChangeLog b/apps/90sclk/ChangeLog
index 057d6ff73..9718a652d 100644
--- a/apps/90sclk/ChangeLog
+++ b/apps/90sclk/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Fullscreen settings.
0.03: Tell clock widgets to hide.
+0.04: Use widget_utils.
diff --git a/apps/90sclk/app.js b/apps/90sclk/app.js
index 351c235e0..63a48b27a 100644
--- a/apps/90sclk/app.js
+++ b/apps/90sclk/app.js
@@ -1,6 +1,7 @@
const SETTINGS_FILE = "90sclk.setting.json";
const locale = require('locale');
const storage = require('Storage');
+const widget_utils = require('widget_utils');
/*
@@ -109,7 +110,7 @@ function draw() {
// Draw widgets if not fullscreen
if(settings.fullscreen){
- for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+ widget_utils.hide();
} else {
Bangle.drawWidgets();
}
diff --git a/apps/90sclk/metadata.json b/apps/90sclk/metadata.json
index 59b627427..bfbb6b080 100644
--- a/apps/90sclk/metadata.json
+++ b/apps/90sclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "90sclk",
"name": "90s Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/a_clock_timer/ChangeLog b/apps/a_clock_timer/ChangeLog
index c01ad2077..cfb53c432 100644
--- a/apps/a_clock_timer/ChangeLog
+++ b/apps/a_clock_timer/ChangeLog
@@ -1 +1,4 @@
0.01: Beta version for Bangle 2 (2021/11/28)
+0.02: Shows night time on the map (2022/12/28)
+0.03: Add 1 minute timer with upper taps (2023/01/05)
+1.00: Page to set up custom time zones (2023/01/06)
\ No newline at end of file
diff --git a/apps/a_clock_timer/README.md b/apps/a_clock_timer/README.md
index e8e2647a9..4e199344a 100644
--- a/apps/a_clock_timer/README.md
+++ b/apps/a_clock_timer/README.md
@@ -2,14 +2,17 @@
* Works with Bangle 2
* Timer
- * Right tap: start/increase by 10 minutes; Left tap: decrease by 5 minutes
+ * Top Right tap: increase by 1 minute
+ * Top Left tap: decrease by 1 minute
+ * Bottom Right tap: increase by 10 minutes
+ * Bottom Left tap: decrease by 5 minutes
* Short buzz at T-30, T-20, T-10 ; Double buzz at T
* Other time zones
- * Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
+ * Showing Paris and Tokyo by default, but you can customize this using the dedicated configuration page on the app store
* World Map
- * The yellow line shows the position of the sun
+ * The map shows day and night on Earth and the position of the Sun (yellow line)
-
+ 
## Creator
[@alainsaas](https://github.com/alainsaas)
diff --git a/apps/a_clock_timer/app.js b/apps/a_clock_timer/app.js
index 5f9a3a468..b01cec59b 100644
--- a/apps/a_clock_timer/app.js
+++ b/apps/a_clock_timer/app.js
@@ -18,19 +18,29 @@ var timervalue = 0;
var istimeron = false;
var timertick;
-Bangle.on('touch',t=>{
- if (t == 1) {
+Bangle.on('touch',(touchside, touchdata)=>{
+ if (touchside == 1) {
Bangle.buzz(30);
- if (timervalue < 5*60) { timervalue = 1 ; }
- else { timervalue -= 5*60; }
+ var changevalue = 0;
+ if(touchdata.y > 88) {
+ changevalue += 60*5;
+ } else {
+ changevalue += 60*1;
+ }
+ if (timervalue < changevalue) { timervalue = 1 ; }
+ else { timervalue -= changevalue; }
}
- else if (t == 2) {
+ else if (touchside == 2) {
Bangle.buzz(30);
if (!istimeron) {
istimeron = true;
timertick = setInterval(countDown, 1000);
}
- timervalue += 60*10;
+ if(touchdata.y > 88) {
+ timervalue += 60*10;
+ } else {
+ timervalue += 60*1;
+ }
}
});
@@ -73,12 +83,13 @@ function countDown() {
function showWelcomeMessage() {
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
g.setFontAlign(0, 0).setFont("6x8");
- g.drawString("Touch right to", 44, 80);
+ g.drawString("Tap right to", 44, 80);
g.drawString("start timer", 44, 88);
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
}
// time
+var offsets = require("Storage").readJSON("a_clock_timer.settings.json") || [ ["PAR",1], ["TYO",9] ];
var drawTimeout;
function getGmt() {
@@ -102,20 +113,34 @@ function queueNextDraw() {
function draw() {
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
-
- var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
+
+ var gmtHours = getGmt().getHours();
+
+ var x_sun = 176 - (gmtHours / 24 * 176 + 4);
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
g.reset();
+ var x_night_start = (176 - (((gmtHours-6)%24) / 24 * 176 + 4)) % 176;
+ var x_night_end = 176 - (((gmtHours+6)%24) / 24 * 176 + 4);
+ g.setColor('#000');
+ for (let x = x_night_start; x < (x_night_end < x_night_start ? 176 : x_night_end); x+=2) {
+ g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
+ }
+ if (x_night_end < x_night_start) {
+ for (let x = 0; x < x_night_end; x+=2) {
+ g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
+ }
+ }
+
var locale = require("locale");
-
+
var date = new Date();
g.setFontAlign(0,0);
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);
g.setFont("6x8");
g.drawString(locale.date(new Date(),1), 125, 68);
- g.drawString("PAR "+locale.time(getTimeFromTimezone(1),1), 125, 80);
- g.drawString("TYO "+locale.time(getTimeFromTimezone(9),1), 125, 88);
+ g.drawString(offsets[0][0]+" "+locale.time(getTimeFromTimezone(offsets[0][1]),1), 125, 80);
+ g.drawString(offsets[1][0]+" "+locale.time(getTimeFromTimezone(offsets[1][1]),1), 125, 88);
queueNextDraw();
}
diff --git a/apps/a_clock_timer/custom.html b/apps/a_clock_timer/custom.html
new file mode 100644
index 000000000..b62226340
--- /dev/null
+++ b/apps/a_clock_timer/custom.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
You can set the 2 additional timezones displayed by the clock.
+
+
+
Name
+
UTC Offset (Hours)
+
+
+
Click
+
+
+
+
diff --git a/apps/a_clock_timer/metadata.json b/apps/a_clock_timer/metadata.json
index cc61fc57b..6507857f1 100644
--- a/apps/a_clock_timer/metadata.json
+++ b/apps/a_clock_timer/metadata.json
@@ -1,17 +1,19 @@
{
"id": "a_clock_timer",
"name": "A Clock with Timer",
- "version": "0.01",
+ "version": "1.00",
"description": "A Clock with Timer, Map and Time Zones",
"icon": "app.png",
- "screenshots": [{"url":"screenshot.png"}],
+ "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
+ "custom": "custom.html",
"storage": [
{"name":"a_clock_timer.app.js","url":"app.js"},
{"name":"a_clock_timer.img","url":"app-icon.js","evaluate":true}
- ]
+ ],
+ "data": [{"name":"a_clock_timer.settings.json"}]
}
diff --git a/apps/a_clock_timer/screenshot-1.png b/apps/a_clock_timer/screenshot-1.png
new file mode 100644
index 000000000..ede6439de
Binary files /dev/null and b/apps/a_clock_timer/screenshot-1.png differ
diff --git a/apps/a_clock_timer/screenshot.png b/apps/a_clock_timer/screenshot.png
index 4fb3dd9f2..893daa9d0 100644
Binary files a/apps/a_clock_timer/screenshot.png and b/apps/a_clock_timer/screenshot.png differ
diff --git a/apps/a_speech_timer/ChangeLog b/apps/a_speech_timer/ChangeLog
index b3aa9e0dd..73e8a67da 100644
--- a/apps/a_speech_timer/ChangeLog
+++ b/apps/a_speech_timer/ChangeLog
@@ -1,2 +1,3 @@
1.00: Release (2021/12/01)
1.01: Grey font when timer is frozen (2021/12/04)
+1.02: Force light theme, since the app is not designed for dark theme (2022/12/28)
diff --git a/apps/a_speech_timer/app.js b/apps/a_speech_timer/app.js
index 440cd92c6..cbed2ac00 100644
--- a/apps/a_speech_timer/app.js
+++ b/apps/a_speech_timer/app.js
@@ -166,6 +166,7 @@ function draw() {
g.drawRect(88+8,138-24, 176-10, 138+22);
}
+g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
require("FontHaxorNarrow7x17").add(Graphics);
g.clear();
Bangle.loadWidgets();
diff --git a/apps/a_speech_timer/metadata.json b/apps/a_speech_timer/metadata.json
index 6255a6b92..fd8813991 100644
--- a/apps/a_speech_timer/metadata.json
+++ b/apps/a_speech_timer/metadata.json
@@ -2,7 +2,7 @@
"id":"a_speech_timer",
"name":"Speech Timer",
"icon": "app.png",
-"version":"1.01",
+"version":"1.02",
"description": "A timer designed to help keeping your speeches and presentations to time.",
"tags": "tool,timer",
"readme":"README.md",
diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog
index cb928213e..b53e657fd 100644
--- a/apps/agenda/ChangeLog
+++ b/apps/agenda/ChangeLog
@@ -8,3 +8,6 @@
0.08: Fix error in clkinfo (didn't require Storage & locale)
Fix clkinfo icon
0.09: Ensure Agenda supplies an image for clkinfo items
+0.10: Update clock_info to avoid a redraw
+0.11: Setting to use "Today" and "Yesterday" instead of dates
+ Added dynamic, short and range fields to clkinfo
\ No newline at end of file
diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js
index 54677327b..d203119f4 100644
--- a/apps/agenda/agenda.clkinfo.js
+++ b/apps/agenda/agenda.clkinfo.js
@@ -1,7 +1,14 @@
(function() {
+ function getPassedSec(date) {
+ var now = new Date();
+ var passed = (now-date)/1000;
+ if(passed<0) return 0;
+ return passed;
+ }
var agendaItems = {
name: "Agenda",
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
+ dynamic: true,
items: []
};
var locale = require("locale");
@@ -15,12 +22,16 @@
var title = entry.title.slice(0,12);
var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
+ var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
agendaItems.items.push({
name: "Agenda "+i,
- get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }),
- show: function() { agendaItems.items[i].emit("redraw"); },
+ hasRange: true,
+ get: () => ({ text: title + "\n" + dateStr,
+ img: agendaItems.img, short: shortStr.trim(),
+ v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
+ show: function() {},
hide: function () {}
});
});
diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js
index 9cffe0265..8afca95a9 100644
--- a/apps/agenda/agenda.js
+++ b/apps/agenda/agenda.js
@@ -33,16 +33,32 @@ CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
function getDate(timestamp) {
return new Date(timestamp*1000);
}
+function formatDay(date) {
+ if (!settings.useToday) {
+ return Locale.date(date);
+ }
+ const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
+ const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
+ if (dateformatted == today) {
+ return /*LANG*/"Today ";
+ } else {
+ const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
+ if (dateformatted == tomorrow) {
+ return /*LANG*/"Tomorrow ";
+ }
+ return Locale.date(date);
+ }
+}
function formatDateLong(date, includeDay, allDay) {
let shortTime = Locale.time(date,1)+Locale.meridian(date);
if(allDay) shortTime = "";
- if(includeDay || allDay)
- return Locale.date(date)+" "+shortTime;
+ if(includeDay || allDay) {
+ return formatDay(date)+" "+shortTime;
+ }
return shortTime;
}
function formatDateShort(date, allDay) {
- return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
- "" : Locale.time(date,1)+Locale.meridian(date));
+ return formatDay(date).replace(/\d\d\d\d/,"")+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
}
var lines = [];
diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json
index 58a5091cd..b5b7c1582 100644
--- a/apps/agenda/metadata.json
+++ b/apps/agenda/metadata.json
@@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
- "version": "0.09",
+ "version": "0.11",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js
index 4220fcb63..62e0c6dbd 100644
--- a/apps/agenda/settings.js
+++ b/apps/agenda/settings.js
@@ -43,6 +43,13 @@
updateSettings();
}
},
+ /*LANG*/"Use 'Today',..." : {
+ value : !!settings.useToday,
+ onchange: v => {
+ settings.useToday = v;
+ updateSettings();
+ }
+ },
};
E.showMenu(mainmenu);
})
diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog
index 8ada244d7..303fc7583 100644
--- a/apps/agpsdata/ChangeLog
+++ b/apps/agpsdata/ChangeLog
@@ -3,3 +3,6 @@
0.03: Do not load AGPS data on boot
Increase minimum interval to 6 hours
0.04: Write AGPS data chunks with delay to improve reliability
+0.05: Show last success date
+ Do not start A-GPS update automatically
+0.06: Switch off gps after updating
\ No newline at end of file
diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js
index 4a6d2ba5c..48714d6d2 100644
--- a/apps/agpsdata/app.js
+++ b/apps/agpsdata/app.js
@@ -23,12 +23,26 @@ Bangle.drawWidgets();
let waiting = false;
-function start() {
+function start(restart) {
g.reset();
g.clear();
waiting = false;
- display("Retry?", "touch to retry");
+ if (!restart) {
+ display("Start?", "touch to start");
+ }
+ else {
+ display("Retry?", "touch to retry");
+ }
Bangle.on("touch", () => { updateAgps(); });
+
+ const file = "agpsdata.json";
+ let data = require("Storage").readJSON(file, 1) || {};
+ if (data.lastUpdate) {
+ g.setFont("Vector", 11);
+ g.drawString("last success:", 5, g.getHeight() - 22);
+ g.drawString(new Date(data.lastUpdate).toISOString(), 5, g.getHeight() - 11);
+ }
+
}
function updateAgps() {
@@ -36,7 +50,7 @@ function updateAgps() {
g.clear();
if (!waiting) {
waiting = true;
- display("Updating A-GPS...", "takes ~ 10 seconds");
+ display("Updating A-GPS...", "takes ~10 seconds");
require("agpsdata").pull(function() {
waiting = false;
display("A-GPS updated.", "touch to close");
@@ -45,10 +59,10 @@ function updateAgps() {
function(error) {
waiting = false;
E.showAlert(error, "Error")
- .then(() => { start(); });
+ .then(() => { start(true); });
});
} else {
display("Waiting...");
}
}
-updateAgps();
+start(false);
diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js
index 34608a5c6..4610331f6 100644
--- a/apps/agpsdata/lib.js
+++ b/apps/agpsdata/lib.js
@@ -10,20 +10,24 @@ readSettings();
function setAGPS(b64) {
return new Promise(function(resolve, reject) {
- var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
const gnsstype = settings.gnsstype || 1; // default GPS
- initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
// What about:
// NAV-TIMEUTC (0x01 0x10)
// NAV-PV (0x01 0x03)
// or AGPS.zip uses AID-INI (0x0B 0x01)
-
- eval(initCommands);
+ Bangle.setGPSPower(1,"agpsdata"); // turn GPS on
+ Serial1.println(CASIC_CHECKSUM("$PCAS04," + gnsstype)); // set GNSS mode
try {
- writeChunks(atob(b64), resolve);
+ writeChunks(atob(b64), ()=>{
+ setTimeout(()=>{
+ Bangle.setGPSPower(0,"agpsdata");
+ resolve();
+ }, 1000);
+ });
} catch (e) {
console.log("error:", e);
+ Bangle.setGPSPower(0,"agpsdata");
reject();
}
});
@@ -36,9 +40,8 @@ function writeChunks(bin, resolve) {
setTimeout(function() {
if (chunkI < bin.length) {
var chunk = bin.substr(chunkI, chunkSize);
- js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
- eval(js);
-
+ Serial1.write(atob(btoa(chunk)));
+
chunkI += chunkSize;
writeChunks(bin, resolve);
} else {
diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json
index 203a00f72..446661045 100644
--- a/apps/agpsdata/metadata.json
+++ b/apps/agpsdata/metadata.json
@@ -2,7 +2,7 @@
"name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data",
"icon": "agpsdata.png",
- "version":"0.04",
+ "version":"0.06",
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"tags": "boot,tool,assisted,gps,agps,http",
"allow_emulator":true,
diff --git a/apps/aiclock/ChangeLog b/apps/aiclock/ChangeLog
index fb5aed3e3..6d6eeb55e 100644
--- a/apps/aiclock/ChangeLog
+++ b/apps/aiclock/ChangeLog
@@ -2,4 +2,6 @@
0.02: Design improvements and fixes.
0.03: Indicate battery level through line occurrence.
0.04: Use widget_utils module.
-0.05: Support for clkinfo.
\ No newline at end of file
+0.05: Support for clkinfo.
+0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
+0.07: Use clock_info.addInteractive instead of a custom implementation
\ No newline at end of file
diff --git a/apps/aiclock/README.md b/apps/aiclock/README.md
index 31dd5aa29..521bd2c5e 100644
--- a/apps/aiclock/README.md
+++ b/apps/aiclock/README.md
@@ -11,8 +11,7 @@ The original output of stable diffusion is shown here:
My implementation is shown below. Note that horizontal lines occur randomly, but the
probability is correlated with the battery level. So if your screen contains only
a few lines its time to charge your bangle again ;) Also note that the upper text
-implementes the clkinfo module and can be configured via touch left/right/up/down.
-Touch at the center to trigger the selected action.
+implements the clkinfo module and can be configured via touch and swipe left/right and up/down.

diff --git a/apps/aiclock/aiclock.app.js b/apps/aiclock/aiclock.app.js
index b5bb30b9d..350832367 100644
--- a/apps/aiclock/aiclock.app.js
+++ b/apps/aiclock/aiclock.app.js
@@ -1,7 +1,6 @@
/************************************************
* AI Clock
*/
- const storage = require('Storage');
const clock_info = require("clock_info");
@@ -21,147 +20,14 @@ Graphics.prototype.setFontGochiHand = function(scale) {
return this;
}
-/************************************************
- * Set some important constants such as width, height and center
- */
-var W = g.getWidth(),R=W/2;
-var H = g.getHeight();
-var cx = W/2;
-var cy = H/2;
-var drawTimeout;
-var lock_input = false;
-
-/************************************************
- * SETTINGS
- */
-const SETTINGS_FILE = "aiclock.setting.json";
-let settings = {
- menuPosX: 0,
- menuPosY: 0,
-};
-let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
-for (const key in saved_settings) {
- settings[key] = saved_settings[key]
-}
-
-
-/************************************************
- * Menu
- */
-function getDate(){
- var date = new Date();
- return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2)
-}
-
-
-// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file.
-var clockItems = {
- name: getDate(),
- img: null,
- items: [
- { name: "Week",
- get: () => ({ text: "Week " + weekOfYear(), img: null}),
- show: function() { clockItems.items[0].emit("redraw"); },
- hide: function () {}
- },
- ]
- };
-
-function weekOfYear() {
- var date = new Date();
- date.setHours(0, 0, 0, 0);
- // Thursday in current week decides the year.
- date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
- // January 4 is always in week 1.
- var week1 = new Date(date.getFullYear(), 0, 4);
- // Adjust to Thursday in week 1 and count number of weeks from date to week1.
- return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
- - 3 + (week1.getDay() + 6) % 7) / 7);
-}
-
-
-
-// Load menu
-var menu = clock_info.load();
-menu = menu.concat(clockItems);
-
-
- // Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
- if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
- settings.menuPosX = 0;
- settings.menuPosY = 0;
- }
-
- // Set draw functions for each item
- menu.forEach((menuItm, x) => {
- menuItm.items.forEach((item, y) => {
- function drawItem() {
- // For the clock, we have a special case, as we don't wanna redraw
- // immediately when something changes. Instead, we update data each minute
- // to save some battery etc. Therefore, we hide (and disable the listener)
- // immedeately after redraw...
- item.hide();
-
- // After drawing the item, we enable inputs again...
- lock_input = false;
-
- var info = item.get();
- drawMenuItem(info.text, info.img);
- }
-
- item.on('redraw', drawItem);
- })
- });
-
-
- function canRunMenuItem(){
- if(settings.menuPosY == 0){
- return false;
- }
-
- var menuEntry = menu[settings.menuPosX];
- var item = menuEntry.items[settings.menuPosY-1];
- return item.run !== undefined;
- }
-
-
- function runMenuItem(){
- if(settings.menuPosY == 0){
- return;
- }
-
- var menuEntry = menu[settings.menuPosX];
- var item = menuEntry.items[settings.menuPosY-1];
- try{
- var ret = item.run();
- if(ret){
- Bangle.buzz(300, 0.6);
- }
- } catch (ex) {
- // Simply ignore it...
- }
- }
-
-
-/*
- * Based on the great multi clock from https://github.com/jeffmer/BangleApps/
- */
-Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
- angle = angle % 360;
- var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
- return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
- {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
-};
-
-
-function drawBackground() {
+function drawBackground(start, end) {
g.setFontAlign(0,0);
- g.setColor(g.theme.fg);
+ g.setColor("#000");
var bat = E.getBattery() / 100.0;
- var y = 0;
- while(y < H){
+ var y = start;
+ while(y < end){
// Show less lines in case of small battery level.
if(Math.random() > bat){
y += 5;
@@ -177,6 +43,30 @@ function drawBackground() {
}
+/************************************************
+ * Set some important constants such as width, height and center
+ */
+var W = g.getWidth(),R=W/2;
+var H = g.getHeight();
+var cx = W/2;
+var cy = H/2;
+var drawTimeout;
+
+var clkInfoY = 60;
+
+
+/*
+ * Based on the great multi clock from https://github.com/jeffmer/BangleApps/
+ */
+Graphics.prototype.drawRotRect = function(w, r1, r2, angle) {
+ angle = angle % 360;
+ var w2=w/2, h=r2-r1, theta=angle*Math.PI/180;
+ return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0],
+ {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta}));
+};
+
+
+
function drawCircle(isLocked){
g.setColor(g.theme.fg);
g.fillCircle(cx, cy, 12);
@@ -186,56 +76,6 @@ function drawCircle(isLocked){
g.fillCircle(cx, cy, 6);
}
-function toAngle(a){
- if (a < 0){
- return 360 + a;
- }
-
- if(a > 360) {
- return 360 - a;
- }
-
- return a
-}
-
-
-function drawMenuItem(text, image){
- if(text == null){
- drawTime();
- return
- }
- // image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
-
- text = String(text);
-
- g.reset().setBgColor("#fff").setColor("#000");
- g.setFontAlign(0,0);
- g.setFont("Vector", 20);
-
- var imgWidth = image == null ? 0 : 24;
- var strWidth = g.stringWidth(text);
- var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
- var w = imgWidth + strWidth;
-
- g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
-
- // Draw right line as designed by stable diffusion
- g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
- g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
- g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
-
- // And finally the text
- g.drawString(text, cx+imgWidth/2, 42);
- g.drawString(text, cx+1+imgWidth/2, 41);
-
- if(image != null) {
- var scale = image.width ? imgWidth / image.width : 1;
- g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
- }
-
- drawTime();
-}
-
function drawTime(){
// Draw digital time first
@@ -292,35 +132,23 @@ function drawDigits(){
}
-function drawDate(){
- var menuEntry = menu[settings.menuPosX];
-
- // The first entry is the overview...
- if(settings.menuPosY == 0){
- drawMenuItem(menuEntry.name, menuEntry.img);
- return;
- }
-
- // Draw item if needed
- lock_input = true;
- var item = menuEntry.items[settings.menuPosY-1];
- item.show();
+function draw(){
+ // Note that we force a redraw also of the clock info as
+ // we want to ensure (for design purpose) that the hands
+ // are above the clkinfo section.
+ clockInfoMenu.redraw();
}
-
-
-
-function draw(){
+function drawMainClock(){
// Queue draw in one minute
queueDraw();
- g.reset();
- g.clearRect(0, 0, g.getWidth(), g.getHeight());
- g.setColor(1,1,1);
+ g.setColor("#fff");
+ g.reset().clearRect(0, clkInfoY, g.getWidth(), g.getHeight());
- drawBackground();
- drawDate();
+ drawBackground(clkInfoY, H);
+ drawTime();
drawCircle(Bangle.isLocked());
}
@@ -330,7 +158,7 @@ function draw(){
*/
Bangle.on('lcdPower',on=>{
if (on) {
- draw(true);
+ draw();
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
@@ -341,66 +169,10 @@ Bangle.on('lock', function(isLocked) {
drawCircle(isLocked);
});
-Bangle.on('touch', function(btn, e){
- var left = parseInt(g.getWidth() * 0.22);
- var right = g.getWidth() - left;
- var upper = parseInt(g.getHeight() * 0.22);
- var lower = g.getHeight() - upper;
-
- var is_upper = e.y < upper;
- var is_lower = e.y > lower;
- var is_left = e.x < left && !is_upper && !is_lower;
- var is_right = e.x > right && !is_upper && !is_lower;
- var is_center = !is_upper && !is_lower && !is_left && !is_right;
-
- if(lock_input){
- return;
- }
-
- if(is_lower){
- Bangle.buzz(40, 0.6);
- settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
-
- draw();
- }
-
- if(is_upper){
- Bangle.buzz(40, 0.6);
- settings.menuPosY = settings.menuPosY-1;
- settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
-
- draw();
- }
-
- if(is_right){
- Bangle.buzz(40, 0.6);
- settings.menuPosX = (settings.menuPosX+1) % menu.length;
- settings.menuPosY = 0;
- draw();
- }
-
- if(is_left){
- Bangle.buzz(40, 0.6);
- settings.menuPosY = 0;
- settings.menuPosX = settings.menuPosX-1;
- settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
- draw();
- }
-
- if(is_center){
- if(canRunMenuItem()){
- runMenuItem();
- }
- }
-});
-
E.on("kill", function(){
- try{
- storage.write(SETTINGS_FILE, settings);
- } catch(ex){
- // If this fails, we still kill the app...
- }
+ clockInfoMenu.remove();
+ delete clockInfoMenu;
});
@@ -416,6 +188,55 @@ function queueDraw() {
}
+/************************************************
+ * Clock Info
+ */
+let clockInfoItems = clock_info.load();
+let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
+ x : 0,
+ y: 0,
+ w: W,
+ h: clkInfoY,
+ draw : (itm, info, options) => {
+ g.setFontAlign(0,0);
+ g.setFont("Vector", 20);
+
+ g.setColor("#fff");
+ g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
+ drawBackground(0, clkInfoY+2);
+
+ // Set text and font
+ var image = info.img;
+ var text = String(info.text);
+
+ var imgWidth = image == null ? 0 : 24;
+ var strWidth = g.stringWidth(text);
+ var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
+ var w = imgWidth + strWidth;
+
+ // Draw right line as designed by stable diffusion
+ g.setColor(options.focus ? "#0f0" : "#fff");
+ g.fillRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
+
+ g.setColor("#000");
+ g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
+ g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
+ g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
+
+ // Draw text and image
+ g.drawString(text, cx+imgWidth/2, 42);
+ g.drawString(text, cx+1+imgWidth/2, 41);
+
+ if(image != null) {
+ var scale = image.width ? imgWidth / image.width : 1;
+ g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
+ }
+
+ drawMainClock();
+ }
+});
+
+
/*
* Lets start widgets, listen for btn etc.
*/
@@ -430,7 +251,7 @@ Bangle.loadWidgets();
require('widget_utils').hide();
// Clear the screen once, at startup and draw clock
-g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
+g.setTheme({bg:"#fff",fg:"#000",dark:false});
draw();
// After drawing the watch face, we can draw the widgets
diff --git a/apps/aiclock/metadata.json b/apps/aiclock/metadata.json
index 1dcda427f..4c01ecaa9 100644
--- a/apps/aiclock/metadata.json
+++ b/apps/aiclock/metadata.json
@@ -3,7 +3,7 @@
"name": "AI Clock",
"shortName":"AI Clock",
"icon": "aiclock.png",
- "version":"0.05",
+ "version":"0.07",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 9994d33d9..bb8a292a0 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -37,3 +37,4 @@
0.34: Add "Confirm" option to alarm/timer edit menus
0.35: Add automatic translation of more strings
0.36: alarm widget moved out of app
+0.37: add message input and dated Events
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
index 741946b0c..0298e0836 100644
--- a/apps/alarm/README.md
+++ b/apps/alarm/README.md
@@ -1,15 +1,18 @@
# Alarms & Timers
-This app allows you to add/modify any alarms and timers.
+This app allows you to add/modify any alarms, timers and events.
+
+Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
## Menu overview
- `New...`
- - `New Alarm` → Configure a new alarm
+ - `New Alarm` → Configure a new alarm (triggered based on time and day of week)
- `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
- - `New Timer` → Configure a new timer
+ - `New Timer` → Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
+ - `New Event` → Configure a new event (triggered based on time and date)
- `Advanced`
- `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
- `Enable All` → Enable _all_ disabled alarms & timers
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 1414c0b90..74007d04b 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -48,9 +48,10 @@ function showMainMenu() {
};
alarms.forEach((e, index) => {
- var label = e.timer
+ var label = (e.timer
? require("time_utils").formatDuration(e.timer)
- : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
+ : (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
+ ) + (e.msg ? " " + e.msg : "");
menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
@@ -67,11 +68,12 @@ function showNewMenu() {
"": { "title": /*LANG*/"New..." },
"< Back": () => showMainMenu(),
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
- /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
+ /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
+ /*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
});
}
-function showEditAlarmMenu(selectedAlarm, alarmIndex) {
+function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
@@ -82,11 +84,16 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
}
var time = require("time_utils").decodeTime(alarm.t);
+ if (withDate && !alarm.date) alarm.date = new Date().toLocalISOString().slice(0,10);
+ var date = alarm.date ? new Date(alarm.date) : undefined;
+ var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
+ var keyboard = "textinput";
+ try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = {
- "": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
+ "": { "title": title },
"< Back": () => {
- prepareAlarmForSave(alarm, alarmIndex, time);
+ prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload();
showMainMenu();
},
@@ -106,6 +113,36 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
wrap: true,
onchange: v => time.m = v
},
+ /*LANG*/"Day": {
+ value: date ? date.getDate() : null,
+ min: 1,
+ max: 31,
+ wrap: true,
+ onchange: v => date.setDate(v)
+ },
+ /*LANG*/"Month": {
+ value: date ? date.getMonth() + 1 : null,
+ format: v => require("date_utils").month(v),
+ onchange: v => date.setMonth((v+11)%12)
+ },
+ /*LANG*/"Year": {
+ value: date ? date.getFullYear() : null,
+ min: new Date().getFullYear(),
+ max: 2100,
+ onchange: v => date.setFullYear(v)
+ },
+ /*LANG*/"Message": {
+ value: alarm.msg,
+ onchange: () => {
+ setTimeout(() => {
+ keyboard.input({text:alarm.msg}).then(result => {
+ alarm.msg = result;
+ prepareAlarmForSave(alarm, alarmIndex, time, date, true);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
+ });
+ }, 100);
+ }
+ },
/*LANG*/"Enabled": {
value: alarm.on,
onchange: v => alarm.on = v
@@ -115,8 +152,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
alarm.rp = repeat;
alarm.dow = dow;
- alarm.t = require("time_utils").encodeTime(time);
- setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
+ prepareAlarmForSave(alarm, alarmIndex, time, date, true);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
})
},
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
@@ -136,6 +173,15 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
}
};
+ if (!keyboard) delete menu[/*LANG*/"Message"];
+ if (alarm.date || withDate) {
+ delete menu[/*LANG*/"Repeat"];
+ } else {
+ delete menu[/*LANG*/"Day"];
+ delete menu[/*LANG*/"Month"];
+ delete menu[/*LANG*/"Year"];
+ }
+
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
@@ -145,7 +191,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
showMainMenu();
} else {
alarm.t = require("time_utils").encodeTime(time);
- setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
}
});
};
@@ -154,14 +200,17 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
E.showMenu(menu);
}
-function prepareAlarmForSave(alarm, alarmIndex, time) {
+function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
alarm.t = require("time_utils").encodeTime(time);
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
+ if(date) alarm.date = date.toLocalISOString().slice(0,10);
- if (alarmIndex === undefined) {
- alarms.push(alarm);
- } else {
- alarms[alarmIndex] = alarm;
+ if(!temp) {
+ if (alarmIndex === undefined) {
+ alarms.push(alarm);
+ } else {
+ alarms[alarmIndex] = alarm;
+ }
}
}
@@ -255,6 +304,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
}
var time = require("time_utils").decodeTime(timer.timer);
+ var keyboard = "textinput";
+ try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = {
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
@@ -285,6 +336,18 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
wrap: true,
onchange: v => time.s = v
},
+ /*LANG*/"Message": {
+ value: timer.msg,
+ onchange: () => {
+ setTimeout(() => {
+ keyboard.input({text:timer.msg}).then(result => {
+ timer.msg = result;
+ prepareTimerForSave(timer, timerIndex, time, true);
+ setTimeout(showEditTimerMenu, 10, timer, timerIndex);
+ });
+ }, 100);
+ }
+ },
/*LANG*/"Enabled": {
value: timer.on,
onchange: v => timer.on = v
@@ -306,6 +369,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
}
};
+ if (!keyboard) delete menu[/*LANG*/"Message"];
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
@@ -324,15 +388,17 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
E.showMenu(menu);
}
-function prepareTimerForSave(timer, timerIndex, time) {
+function prepareTimerForSave(timer, timerIndex, time, temp) {
timer.timer = require("time_utils").encodeTime(time);
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.last = 0;
- if (timerIndex === undefined) {
- alarms.push(timer);
- } else {
- alarms[timerIndex] = timer;
+ if (!temp) {
+ if (timerIndex === undefined) {
+ alarms.push(timer);
+ } else {
+ alarms[timerIndex] = timer;
+ }
}
}
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index dbf090774..29e71b3d9 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.36",
+ "version": "0.37",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",
diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog
index 86dbdb649..db5c0b057 100644
--- a/apps/android/ChangeLog
+++ b/apps/android/ChangeLog
@@ -18,3 +18,5 @@
0.18: Use new message library
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
0.19: Add automatic translation for a couple of strings.
+0.20: Fix wrong event used for forwarded GPS data from Gadgetbridge and add mapper to map longitude value correctly.
+0.21: Fix broken 'Messages' button in menu
diff --git a/apps/android/boot.js b/apps/android/boot.js
index e1e5b028b..c5a9dd746 100644
--- a/apps/android/boot.js
+++ b/apps/android/boot.js
@@ -134,7 +134,11 @@
event.satellites = NaN;
event.course = NaN;
event.fix = 1;
- Bangle.emit('gps', event);
+ if (event.long!==undefined) {
+ event.lon = event.long;
+ delete event.long;
+ }
+ Bangle.emit('GPS', event);
},
"is_gps_active": function() {
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
@@ -208,7 +212,7 @@
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
Bangle.setGPSPower = (isOn, appID) => {
// if not connected, use old logic
- if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
+ if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
// Emulate old GPS power logic
if (!Bangle._PWR) Bangle._PWR={};
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
diff --git a/apps/android/metadata.json b/apps/android/metadata.json
index d5a45edb7..63fd7759a 100644
--- a/apps/android/metadata.json
+++ b/apps/android/metadata.json
@@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
- "version": "0.19",
+ "version": "0.21",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",
diff --git a/apps/android/settings.js b/apps/android/settings.js
index 3e04e0f9d..0abb32249 100644
--- a/apps/android/settings.js
+++ b/apps/android/settings.js
@@ -1,6 +1,6 @@
(function(back) {
-
+
function gb(j) {
Bluetooth.println(JSON.stringify(j));
@@ -36,7 +36,7 @@
updateSettings();
}
},
- /*LANG*/"Messages" : ()=>require("message").openGUI(),
+ /*LANG*/"Messages" : ()=>require("messages").openGUI(),
};
E.showMenu(mainmenu);
})
diff --git a/apps/assistedgps/ChangeLog b/apps/assistedgps/ChangeLog
index 739ccf915..ff2de6f67 100644
--- a/apps/assistedgps/ChangeLog
+++ b/apps/assistedgps/ChangeLog
@@ -1,3 +1,4 @@
0.01: New App!
0.02: Update to work with Bangle.js 2
0.03: Select GNSS systems to use for Bangle.js 2
+0.04: Now turns GPS off after upload
diff --git a/apps/assistedgps/custom.html b/apps/assistedgps/custom.html
index 80d68a71f..75a4ecf32 100644
--- a/apps/assistedgps/custom.html
+++ b/apps/assistedgps/custom.html
@@ -133,7 +133,7 @@
function jsFromBase64(b64) {
var bin = atob(b64);
var chunkSize = 128;
- var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
+ var js = "\x10Bangle.setGPSPower(1,'agps');\n"; // turn GPS on
if (isB1) { // UBLOX
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
@@ -158,6 +158,7 @@
var chunk = bin.substr(i,chunkSize);
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
}
+ js = "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
return js;
}
diff --git a/apps/assistedgps/metadata.json b/apps/assistedgps/metadata.json
index 4c91dcd35..ac9fe5725 100644
--- a/apps/assistedgps/metadata.json
+++ b/apps/assistedgps/metadata.json
@@ -1,7 +1,7 @@
{
"id": "assistedgps",
"name": "Assisted GPS Updater (AGPS)",
- "version": "0.03",
+ "version": "0.04",
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
"sortorder": -1,
"icon": "app.png",
diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog
index 88f4eaf00..96ee0141e 100644
--- a/apps/barclock/ChangeLog
+++ b/apps/barclock/ChangeLog
@@ -14,3 +14,4 @@
0.14: Use ClockFace_menu.addItems
0.15: Add Power saving option
0.16: Support Fast Loading
+0.17: Hide widgets instead of not loading them at all
diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json
index 785c228b0..010852083 100644
--- a/apps/barclock/metadata.json
+++ b/apps/barclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "barclock",
"name": "Bar Clock",
- "version": "0.16",
+ "version": "0.17",
"description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
diff --git a/apps/barclock/settings.js b/apps/barclock/settings.js
index 7b88b7021..04f0a38ba 100644
--- a/apps/barclock/settings.js
+++ b/apps/barclock/settings.js
@@ -1,5 +1,10 @@
(function(back) {
let s = require("Storage").readJSON("barclock.settings.json", true) || {};
+ // migrate "don't load widgets" to "hide widgets"
+ if (!("hideWidgets" in s) && ("loadWidgets" in s) && !s.loadWidgets) {
+ s.hideWidgets = 1;
+ }
+ delete s.loadWidgets;
function save(key, value) {
s[key] = value;
@@ -19,7 +24,7 @@
};
let items = {
showDate: s.showDate,
- loadWidgets: s.loadWidgets,
+ hideWidgets: s.hideWidgets,
};
// Power saving for Bangle.js 1 doesn't make sense (no updates while screen is off anyway)
if (process.env.HWVERSION>1) {
diff --git a/apps/berlinc/ChangeLog b/apps/berlinc/ChangeLog
index 9e9c1a6aa..b6ed1a5b4 100644
--- a/apps/berlinc/ChangeLog
+++ b/apps/berlinc/ChangeLog
@@ -4,3 +4,4 @@
0.05: Update *on* the minute rather than every 15 secs
Now show widgets
Make compatible with themes, and Bangle.js 2
+0.06: Enable fastloading
\ No newline at end of file
diff --git a/apps/berlinc/berlin-clock.js b/apps/berlinc/berlin-clock.js
index 0dd8ff8ee..cd0f12fa3 100644
--- a/apps/berlinc/berlin-clock.js
+++ b/apps/berlinc/berlin-clock.js
@@ -1,3 +1,4 @@
+{
// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr
// https://github.com/eska-muc/BangleApps
const fields = [4, 4, 11, 4];
@@ -6,18 +7,18 @@ const width = g.getWidth() - 2 * offset;
const height = g.getHeight() - 2 * offset;
const rowHeight = height / 4;
-var show_date = false;
-var show_time = false;
-var yy = 0;
+let show_date = false;
+let show_time = false;
+let yy = 0;
-var rowlights = [];
-var time_digit = [];
+let rowlights = [];
+let time_digit = [];
// timeout used to update every minute
-var drawTimeout;
+let drawTimeout;
// schedule a draw for the next minute
-function queueDraw() {
+let queueDraw = () => {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
@@ -25,7 +26,7 @@ function queueDraw() {
}, 60000 - (Date.now() % 60000));
}
-function draw() {
+let draw = () => {
g.reset().clearRect(0,24,g.getWidth(),g.getHeight());
var now = new Date();
@@ -84,28 +85,39 @@ function draw() {
queueDraw();
}
-function toggleDate() {
+let toggleDate = () => {
show_date = ! show_date;
draw();
}
-function toggleTime() {
+let toggleTime = () => {
show_time = ! show_time;
draw();
}
-// Stop updates when LCD is off, restart when on
-Bangle.on('lcdPower',on=>{
+let clear = () => {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+}
+
+let onLcdPower = on => {
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
- if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = undefined;
+ clear();
}
-});
+}
+
+let cleanup = () => {
+ clear();
+ Bangle.removeListener("lcdPower", onLcdPower);
+}
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',onLcdPower);
// Show launcher when button pressed, handle up/down
-Bangle.setUI("clockupdown", dir=> {
+Bangle.setUI({mode: "clockupdown", remove: cleanup}, dir=> {
if (dir<0) toggleTime();
if (dir>0) toggleDate();
});
@@ -114,3 +126,4 @@ g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();
+}
\ No newline at end of file
diff --git a/apps/berlinc/metadata.json b/apps/berlinc/metadata.json
index 85c42fc47..7f63f96cd 100644
--- a/apps/berlinc/metadata.json
+++ b/apps/berlinc/metadata.json
@@ -1,7 +1,7 @@
{
"id": "berlinc",
"name": "Berlin Clock",
- "version": "0.05",
+ "version": "0.06",
"description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)",
"icon": "berlin-clock.png",
"type": "clock",
diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog
index 546c83894..8b82f6843 100644
--- a/apps/bwclk/ChangeLog
+++ b/apps/bwclk/ChangeLog
@@ -20,4 +20,13 @@
0.20: Better handling of async data such as getPressure.
0.21: On the default menu the week of year can be shown.
0.22: Use the new clkinfo module for the menu.
-0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
\ No newline at end of file
+0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
+0.24: Update clock_info to avoid a redraw
+0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16.
+ ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
+0.26: Use clkinfo.addInteractive instead of a custom implementation
+0.27: Clean out some leftovers in the remove function after switching to
+clkinfo.addInteractive that would cause ReferenceError.
+0.28: Option to show (1) time only and (2) week of year.
+0.29: use setItem of clockInfoMenu to change the active item
+0.30: Use widget_utils.
diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md
index d869fa2cf..5e2a7b55f 100644
--- a/apps/bwclk/README.md
+++ b/apps/bwclk/README.md
@@ -5,16 +5,12 @@ A very minimalistic clock.
## Features
The BW clock implements features that are exposed by other apps through the `clkinfo` module.
-For example, if you install the HomeAssistant app, this menu item will be shown if you click right
-and additionally allows you to send triggers directly from the clock (select triggers via up/down and
-send via click center). Here are examples of other apps that are integrated:
+For example, if you install the HomeAssistant app, this menu item will be shown if you first
+touch the bottom of the screen and then swipe left/right to the home assistant menu. To select
+sub-items simply swipe up/down. To run an action (e.g. trigger home assistant), simply select the clkinfo (border) and touch on the item again. See also the screenshot below:
-- Bangle data such as steps, heart rate, battery or charging state.
-- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
-- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
-- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
+
-Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
## Settings
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
@@ -22,25 +18,6 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
- Your bangle uses the sys color settings so you can change the color too.
-## Menu structure
-2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant.
-
-Simply click left / right to go through the menu entries such as Bangle, Weather etc.
-and click up/down to move into this sub-menu. You can then click in the middle of the screen
-to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend
-on the app that provide this sub-menu through the `clkinfo` module.
-
-```
- Bangle -- Agenda -- Weather -- HomeAssistant
- | | | |
- Battery Entry 1 Temperature Trigger1
- | | | |
- Steps ... ... ...
- |
- ...
-```
-
-
## Thanks to
- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
- Icons created by Flaticon
diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js
index 7dcca9d75..de7c7d510 100644
--- a/apps/bwclk/app.js
+++ b/apps/bwclk/app.js
@@ -1,10 +1,12 @@
+{ // must be inside our own scope here so that when we are unloaded everything disappears
+
/************************************************
* Includes
*/
const locale = require('locale');
const storage = require('Storage');
const clock_info = require("clock_info");
-
+const widget_utils = require("widget_utils");
/************************************************
* Globals
@@ -12,8 +14,6 @@ const clock_info = require("clock_info");
const SETTINGS_FILE = "bwclk.setting.json";
const W = g.getWidth();
const H = g.getHeight();
-var lock_input = false;
-
/************************************************
* Settings
@@ -28,7 +28,20 @@ let settings = {
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
- settings[key] = saved_settings[key]
+ settings[key] = saved_settings[key];
+}
+
+let isFullscreen = function() {
+ var s = settings.screen.toLowerCase();
+ if(s == "dynamic"){
+ return Bangle.isLocked();
+ } else {
+ return s == "full";
+ }
+};
+
+let getLineY = function(){
+ return H/5*2 + (isFullscreen() ? 0 : 8);
}
/************************************************
@@ -74,32 +87,22 @@ Graphics.prototype.setMiniFont = function(scale) {
return this;
};
-function imgLock(){
+let imgLock = function() {
return {
width : 16, height : 16, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
- }
-}
+ };
+};
/************************************************
- * Menu
+ * Clock Info
*/
-// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
-var bwItems = {
- name: null,
- img: null,
- items: [
- { name: "WeekOfYear",
- get: () => ({ text: "Week " + weekOfYear(), img: null}),
- show: function() { bwItems.items[0].emit("redraw"); },
- hide: function () {}
- },
- ]
-};
+let clockInfoItems = clock_info.load();
-function weekOfYear() {
+// Add some custom clock-infos
+let weekOfYear = function() {
var date = new Date();
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
@@ -111,87 +114,98 @@ function weekOfYear() {
- 3 + (week1.getDay() + 6) % 7) / 7);
}
+clockInfoItems[0].items.unshift({ name : "weekofyear",
+ get : function() { return { text : "Week " + weekOfYear(),
+ img : null}},
+ show : function() {},
+ hide : function() {},
+})
-// Load menu
-var menu = clock_info.load();
-menu = menu.concat(bwItems);
+// Empty for large time
+clockInfoItems[0].items.unshift({ name : "nop",
+ get : function() { return { text : null,
+ img : null}},
+ show : function() {},
+ hide : function() {},
+})
-// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
-if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
- settings.menuPosX = 0;
- settings.menuPosY = 0;
-}
-// Set draw functions for each item
-menu.forEach((menuItm, x) => {
- menuItm.items.forEach((item, y) => {
- function drawItem() {
- // For the clock, we have a special case, as we don't wanna redraw
- // immediately when something changes. Instead, we update data each minute
- // to save some battery etc. Therefore, we hide (and disable the listener)
- // immedeately after redraw...
- item.hide();
+let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
+ x : 0,
+ y: 135,
+ w: W,
+ h: H-135,
+ draw : (itm, info, options) => {
+ var hideClkInfo = info.text == null;
- // After drawing the item, we enable inputs again...
- lock_input = false;
+ g.setColor(g.theme.fg);
+ g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
- var info = item.get();
- drawMenuItem(info.text, info.img);
+ g.setFontAlign(0,0);
+ g.setColor(g.theme.bg);
+
+ if (options.focus){
+ var y = hideClkInfo ? options.y+20 : options.y+2;
+ var h = hideClkInfo ? options.h-20 : options.h-2;
+ g.drawRect(options.x, y, options.x+options.w-2, y+h-1); // show if focused
+ g.drawRect(options.x+1, y+1, options.x+options.w-3, y+h-2); // show if focused
}
- item.on('redraw', drawItem);
- })
+ // In case we hide the clkinfo, we show the time again as the time should
+ // be drawn larger.
+ if(hideClkInfo){
+ drawTime();
+ return;
+ }
+
+ // Set text and font
+ var image = info.img;
+ var text = String(info.text);
+ if(text.split('\n').length > 1){
+ g.setMiniFont();
+ } else {
+ g.setSmallFont();
+ }
+
+ // Compute sizes
+ var strWidth = g.stringWidth(text);
+ var imgWidth = image == null ? 0 : 24;
+ var midx = options.x+options.w/2;
+
+ // Draw
+ if (image) {
+ var scale = imgWidth / image.width;
+ g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale});
+ }
+ g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20);
+
+ // In case we are in focus and the focus box changes (fullscreen yes/no)
+ // we draw the time again. Otherwise it could happen that a while line is
+ // not cleared correctly.
+ if(options.focus) drawTime();
+ }
});
-function canRunMenuItem(){
- if(settings.menuPosY == 0){
- return false;
- }
-
- var menuEntry = menu[settings.menuPosX];
- var item = menuEntry.items[settings.menuPosY-1];
- return item.run !== undefined;
-}
-
-
-function runMenuItem(){
- if(settings.menuPosY == 0){
- return;
- }
-
- var menuEntry = menu[settings.menuPosX];
- var item = menuEntry.items[settings.menuPosY-1];
- try{
- var ret = item.run();
- if(ret){
- Bangle.buzz(300, 0.6);
- }
- } catch (ex) {
- // Simply ignore it...
- }
-}
-
-
/************************************************
* Draw
*/
-function draw() {
+let draw = function() {
// Queue draw again
queueDraw();
// Draw clock
drawDate();
- drawMenuAndTime();
+ drawTime();
drawLock();
drawWidgets();
-}
+};
-function drawDate(){
+let drawDate = function() {
// Draw background
- var y = H/5*2 + (isFullscreen() ? 0 : 8);
+ var y = getLineY()
g.reset().clearRect(0,0,W,y);
// Draw date
@@ -216,17 +230,17 @@ function drawDate(){
g.setMediumFont();
g.setColor(g.theme.fg);
g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
-}
+};
-function drawTime(y, smallText){
+let drawTime = function() {
+ var hideClkInfo = clockInfoMenu.menuA == 0 && clockInfoMenu.menuB == 0;
+
// Draw background
+ var y1 = getLineY();
+ var y = y1;
var date = new Date();
- // Draw time
- g.setColor(g.theme.bg);
- g.setFontAlign(0,0);
-
var hours = String(date.getHours());
var minutes = date.getMinutes();
minutes = minutes < 10 ? String("0") + minutes : minutes;
@@ -236,212 +250,93 @@ function drawTime(y, smallText){
// Set y coordinates correctly
y += parseInt((H - y)/2) + 5;
- // Show large or small time depending on info entry
- if(smallText){
+ if (hideClkInfo){
+ g.setLargeFont();
+ } else {
y -= 15;
g.setMediumFont();
- } else {
- g.setLargeFont();
}
- g.drawString(timeStr, W/2, y);
-}
-
-function drawMenuItem(text, image){
- // First clear the time region
- var y = H/5*2 + (isFullscreen() ? 0 : 8);
+ // Clear region and draw time
g.setColor(g.theme.fg);
- g.fillRect(0,y,W,H);
+ g.fillRect(0,y1,W,y+20 + (hideClkInfo ? 1 : 0) + (isFullscreen() ? 3 : 0));
- // Draw menu text
- var hasText = (text != null && text != "");
- if(hasText){
- g.setFontAlign(0,0);
-
- // For multiline text we show an even smaller font...
- text = String(text);
- if(text.split('\n').length > 1){
- g.setMiniFont();
- } else {
- g.setSmallFont();
- }
-
- var imgWidth = image == null ? 0 : 24;
- var strWidth = g.stringWidth(text);
- g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
- g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
-
- if(image != null){
- var scale = imgWidth / image.width;
- g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
- }
- }
-
- // Draw time
- drawTime(y, hasText);
-}
+ g.setColor(g.theme.bg);
+ g.setFontAlign(0,0);
+ g.drawString(timeStr, W/2, y);
+};
-function drawMenuAndTime(){
- var menuEntry = menu[settings.menuPosX];
-
- // The first entry is the overview...
- if(settings.menuPosY == 0){
- drawMenuItem(menuEntry.name, menuEntry.img);
- return;
- }
-
- // Draw item if needed
- lock_input = true;
- var item = menuEntry.items[settings.menuPosY-1];
- item.show();
-}
-
-
-function drawLock(){
+let drawLock = function() {
if(settings.showLock && Bangle.isLocked()){
g.setColor(g.theme.fg);
g.drawImage(imgLock(), W-16, 2);
}
-}
+};
-function drawWidgets(){
+let drawWidgets = function() {
if(isFullscreen()){
- for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+ widget_utils.hide();
} else {
Bangle.drawWidgets();
}
-}
-
-
-function isFullscreen(){
- var s = settings.screen.toLowerCase();
- if(s == "dynamic"){
- return Bangle.isLocked()
- } else {
- return s == "full"
- }
-}
-
+};
/************************************************
* Listener
*/
// timeout used to update every minute
-var drawTimeout;
+let drawTimeout;
// schedule a draw for the next minute
-function queueDraw() {
+let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
-}
+};
// Stop updates when LCD is off, restart when on
-Bangle.on('lcdPower',on=>{
+let lcdListenerBw = function(on) {
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
-});
+};
+Bangle.on('lcdPower', lcdListenerBw);
-Bangle.on('lock', function(isLocked) {
+let lockListenerBw = function(isLocked) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
// If we have to show the widgets again, we load it from our
// cache and not through Bangle.loadWidgets as its much faster!
- for (let wd of WIDGETS) {wd.draw=wd._draw;wd.area=wd._area;}
+ widget_utils.show();
}
draw();
-});
-
-Bangle.on('charging',function(charging) {
- if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = undefined;
+};
+Bangle.on('lock', lockListenerBw);
+let charging = function(charging){
// Jump to battery
- settings.menuPosX = 0;
- settings.menuPosY = 1;
- draw();
-});
-
-Bangle.on('touch', function(btn, e){
- var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
- var left = parseInt(g.getWidth() * 0.22);
- var right = g.getWidth() - left;
- var upper = parseInt(g.getHeight() * 0.22) + widget_size;
- var lower = g.getHeight() - upper;
-
- var is_upper = e.y < upper;
- var is_lower = e.y > lower;
- var is_left = e.x < left && !is_upper && !is_lower;
- var is_right = e.x > right && !is_upper && !is_lower;
- var is_center = !is_upper && !is_lower && !is_left && !is_right;
-
- if(lock_input){
- return;
- }
-
- if(is_lower){
- Bangle.buzz(40, 0.6);
- settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
-
- drawMenuAndTime();
- }
-
- if(is_upper){
- if(e.y < widget_size){
- return;
- }
-
- Bangle.buzz(40, 0.6);
- settings.menuPosY = settings.menuPosY-1;
- settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
-
- drawMenuAndTime();
- }
-
- if(is_right){
- Bangle.buzz(40, 0.6);
- settings.menuPosX = (settings.menuPosX+1) % menu.length;
- settings.menuPosY = 0;
- drawMenuAndTime();
- }
-
- if(is_left){
- Bangle.buzz(40, 0.6);
- settings.menuPosY = 0;
- settings.menuPosX = settings.menuPosX-1;
- settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
- drawMenuAndTime();
- }
-
- if(is_center){
- if(canRunMenuItem()){
- runMenuItem();
- }
- }
-});
-
-
-E.on("kill", function(){
- try{
- storage.write(SETTINGS_FILE, settings);
- } catch(ex){
- // If this fails, we still kill the app...
- }
-});
+ clockInfoMenu.setItem(0, 2);
+ drawTime();
+}
+Bangle.on('charging', charging);
+let kill = function(){
+ clockInfoMenu.remove();
+ delete clockInfoMenu;
+};
+E.on("kill", kill);
/************************************************
* Startup Clock
@@ -450,17 +345,31 @@ E.on("kill", function(){
// The upper part is inverse i.e. light if dark and dark if light theme
// is enabled. In order to draw the widgets correctly, we invert the
// dark/light theme as well as the colors.
+let themeBackup = g.theme;
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
// Show launcher when middle button pressed
-Bangle.setUI("clock");
+Bangle.setUI({
+ mode : "clock",
+ remove : function() {
+ // Called to unload all of the clock app
+ Bangle.removeListener('lcdPower', lcdListenerBw);
+ Bangle.removeListener('lock', lockListenerBw);
+ Bangle.removeListener('charging', charging);
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ // save settings
+ kill();
+ E.removeListener("kill", kill);
+ g.setTheme(themeBackup);
+ widget_utils.show();
+ }
+});
// Load widgets and draw clock the first time
Bangle.loadWidgets();
-// Cache draw function for dynamic screen to hide / show widgets
-// Bangle.loadWidgets() could also be called later on but its much slower!
-for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
-
// Draw first time
draw();
+
+} // End of app scope
diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json
index fa0f7b01f..39106c827 100644
--- a/apps/bwclk/metadata.json
+++ b/apps/bwclk/metadata.json
@@ -1,11 +1,11 @@
{
"id": "bwclk",
"name": "BW Clock",
- "version": "0.23",
- "description": "A very minimalistic clock to mainly show date and time.",
+ "version": "0.30",
+ "description": "A very minimalistic clock.",
"readme": "README.md",
"icon": "app.png",
- "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
+ "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}],
"type": "clock",
"tags": "clock,clkinfo",
"supports": ["BANGLEJS2"],
diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png
index 3a75f13d1..37acf7cc0 100644
Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ
diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png
index 31bf6373e..8d2f1717f 100644
Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ
diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png
index 8d982cac4..d52057569 100644
Binary files a/apps/bwclk/screenshot_3.png and b/apps/bwclk/screenshot_3.png differ
diff --git a/apps/bwclk/screenshot_4.png b/apps/bwclk/screenshot_4.png
deleted file mode 100644
index 83de5c2ce..000000000
Binary files a/apps/bwclk/screenshot_4.png and /dev/null differ
diff --git a/apps/choozi/ChangeLog b/apps/choozi/ChangeLog
index 03f7ef832..35adc7430 100644
--- a/apps/choozi/ChangeLog
+++ b/apps/choozi/ChangeLog
@@ -1,3 +1,9 @@
0.01: New App!
0.02: Support Bangle.js 2
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
+0.04: Combine code for both apps
+ Better colors for Bangle.js 2
+ Fix selection animation for Bangle.js 2
+ New icon
+ Slightly wider arc segments for better visibility
+ Extract arc drawing code in library
diff --git a/apps/choozi/README.md b/apps/choozi/README.md
index f1e4255bc..ccaa97a27 100644
--- a/apps/choozi/README.md
+++ b/apps/choozi/README.md
@@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
of players, ensure that each person knows which colour represents them, and then
choose a segment. After a short animation, the chosen segment will fill the screen.
-You can use Choozi to randomly select an element from any set with 2 to 13 members,
+You can use Choozi to randomly select an element from any set with 2 to 15 members,
as long as you can define a bijection between members of the set and coloured
segments on the Bangle.js display.
-## Controls
+## Controls Bangle 1
BTN1: increase the number of segments
BTN2: choose a segment at random
BTN3: decrease the number of segments
+## Controls Bangle 2
+
+Swipe up/down: increase/decrease the number of segments
+BTN1 or tap: choose a segment at random
+
## Creator
James Stanley
diff --git a/apps/choozi/app-icon.js b/apps/choozi/app-icon.js
index 51b3bead3..560286098 100644
--- a/apps/choozi/app-icon.js
+++ b/apps/choozi/app-icon.js
@@ -1 +1 @@
-require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
+require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
\ No newline at end of file
diff --git a/apps/choozi/app.js b/apps/choozi/app.js
index 1a5b2f17e..b9f53bc89 100644
--- a/apps/choozi/app.js
+++ b/apps/choozi/app.js
@@ -4,15 +4,16 @@
*
* James Stanley 2021
*/
-
-var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
+const GU = require("graphics_utils");
+var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
+var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
var stepAngle = 0.18; // radians - resolution of polygon
var gapAngle = 0.035; // radians - gap between segments
-var perimMin = 110; // px - min. radius of perimeter
-var perimMax = 120; // px - max. radius of perimeter
+var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
+var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
-var segmentMax = 106; // px - max radius of filled-in segment
+var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
var segmentStep = 5; // px - step size of segment fill animation
var circleStep = 4; // px - step size of circle fill animation
@@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
var animStartSteps = 300; // how many steps before it can start slowing?
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
var ballSize = 3; // px - ball radius
-var ballTrack = 100; // px - radius of ball path
+var ballTrack = perimMin - ballSize*2; // px - radius of ball path
-var centreX = 120; // px - centre of screen
-var centreY = 120; // px - centre of screen
+var centreX = g.getWidth()*0.5; // px - centre of screen
+var centreY = g.getWidth()*0.5; // px - centre of screen
var fontSize = 50; // px
@@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
var defaultN = 3; // default value for N
var minN = 2;
-var maxN = colours.length;
var N;
var arclen;
@@ -51,42 +51,14 @@ function shuffle (array) {
}
}
-// draw an arc between radii minR and maxR, and between
-// angles minAngle and maxAngle
-function arc(minR, maxR, minAngle, maxAngle) {
- var step = stepAngle;
- var angle = minAngle;
- var inside = [];
- var outside = [];
- var c, s;
- while (angle < maxAngle) {
- c = Math.cos(angle);
- s = Math.sin(angle);
- inside.push(centreX+c*minR); // x
- inside.push(centreY+s*minR); // y
- // outside coordinates are built up in reverse order
- outside.unshift(centreY+s*maxR); // y
- outside.unshift(centreX+c*maxR); // x
- angle += step;
- }
- c = Math.cos(maxAngle);
- s = Math.sin(maxAngle);
- inside.push(centreX+c*minR);
- inside.push(centreY+s*minR);
- outside.unshift(centreY+s*maxR);
- outside.unshift(centreX+c*maxR);
-
- var vertices = inside.concat(outside);
- g.fillPoly(vertices, true);
-}
-
// draw the arc segments around the perimeter
function drawPerimeter() {
+ g.setBgColor('#000000');
g.clear();
for (var i = 0; i < N; i++) {
g.setColor(colours[i%colours.length]);
var minAngle = (i/N)*radians;
- arc(perimMin,perimMax,minAngle,minAngle+arclen);
+ GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
}
}
@@ -131,6 +103,7 @@ function animateChoice(target) {
g.fillCircle(x, y, ballSize);
oldx=x;
oldy=y;
+ if (process.env.HWVERSION == 2) g.flip();
}
}
@@ -141,11 +114,15 @@ function choose() {
var maxAngle = minAngle + arclen;
animateChoice((minAngle+maxAngle)/2);
g.setColor(colours[chosen%colours.length]);
- for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
- arc(i, perimMax, minAngle, maxAngle);
- arc(0, perimMax, minAngle, maxAngle);
- for (var r = 1; r < segmentMax; r += circleStep)
+ for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
+ GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
+ if (process.env.HWVERSION == 2) g.flip();
+ }
+ GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
+ for (var r = 1; r < segmentMax; r += circleStep){
g.fillCircle(centreX,centreY,r);
+ if (process.env.HWVERSION == 2) g.flip();
+ }
g.fillCircle(centreX,centreY,segmentMax);
}
@@ -171,38 +148,47 @@ function setN(n) {
drawPerimeter();
}
-// save N to choozi.txt
+// save N to choozi.save
function writeN() {
- var file = require("Storage").open("choozi.txt","w");
- file.write(N);
+ var savedN = read();
+ if (savedN != N) require("Storage").write("choozi.save","" + N);
}
-// load N from choozi.txt
+function read(){
+ var n = require("Storage").read("choozi.save");
+ if (n !== undefined) return parseInt(n);
+ return defaultN;
+}
+
+// load N from choozi.save
function readN() {
- var file = require("Storage").open("choozi.txt","r");
- var n = file.readLine();
- if (n !== undefined) setN(parseInt(n));
- else setN(defaultN);
+ setN(read());
}
-shuffle(colours); // is this really best?
-Bangle.setLCDMode("direct");
-Bangle.setLCDTimeout(0); // keep screen on
+if (process.env.HWVERSION == 1){
+ colours=colours.concat(colours2);
+ shuffle(colours);
+} else {
+ shuffle(colours);
+ shuffle(colours2);
+ colours=colours.concat(colours2);
+}
+
+var maxN = colours.length;
+if (process.env.HWVERSION == 1){
+ Bangle.setLCDMode("direct");
+ Bangle.setLCDTimeout(0); // keep screen on
+}
readN();
drawN();
-setWatch(() => {
- setN(N+1);
- drawN();
-}, BTN1, {repeat:true});
-
-setWatch(() => {
- writeN();
- drawPerimeter();
- choose();
-}, BTN2, {repeat:true});
-
-setWatch(() => {
- setN(N-1);
- drawN();
-}, BTN3, {repeat:true});
+Bangle.setUI("updown", (v)=>{
+ if (!v){
+ writeN();
+ drawPerimeter();
+ choose();
+ } else {
+ setN(N-v);
+ drawN();
+ }
+});
diff --git a/apps/choozi/app.png b/apps/choozi/app.png
index 99c9fa07a..50f09f164 100644
Binary files a/apps/choozi/app.png and b/apps/choozi/app.png differ
diff --git a/apps/choozi/appb2.js b/apps/choozi/appb2.js
deleted file mode 100644
index 5f217f638..000000000
--- a/apps/choozi/appb2.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/* Choozi - Choose people or things at random using Bangle.js.
- * Inspired by the "Chwazi" Android app
- *
- * James Stanley 2021
- */
-
-var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
-
-var stepAngle = 0.18; // radians - resolution of polygon
-var gapAngle = 0.035; // radians - gap between segments
-var perimMin = 80; // px - min. radius of perimeter
-var perimMax = 87; // px - max. radius of perimeter
-
-var segmentMax = 70; // px - max radius of filled-in segment
-var segmentStep = 5; // px - step size of segment fill animation
-var circleStep = 4; // px - step size of circle fill animation
-
-// rolling ball animation:
-var maxSpeed = 0.08; // rad/sec
-var minSpeed = 0.001; // rad/sec
-var animStartSteps = 300; // how many steps before it can start slowing?
-var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
-var ballSize = 3; // px - ball radius
-var ballTrack = 75; // px - radius of ball path
-
-var centreX = 88; // px - centre of screen
-var centreY = 88; // px - centre of screen
-
-var fontSize = 50; // px
-
-var radians = 2*Math.PI; // radians per circle
-
-var defaultN = 3; // default value for N
-var minN = 2;
-var maxN = colours.length;
-var N;
-var arclen;
-
-// https://www.frankmitchell.org/2015/01/fisher-yates/
-function shuffle (array) {
- var i = 0
- , j = 0
- , temp = null;
-
- for (i = array.length - 1; i > 0; i -= 1) {
- j = Math.floor(Math.random() * (i + 1));
- temp = array[i];
- array[i] = array[j];
- array[j] = temp;
- }
-}
-
-// draw an arc between radii minR and maxR, and between
-// angles minAngle and maxAngle
-function arc(minR, maxR, minAngle, maxAngle) {
- var step = stepAngle;
- var angle = minAngle;
- var inside = [];
- var outside = [];
- var c, s;
- while (angle < maxAngle) {
- c = Math.cos(angle);
- s = Math.sin(angle);
- inside.push(centreX+c*minR); // x
- inside.push(centreY+s*minR); // y
- // outside coordinates are built up in reverse order
- outside.unshift(centreY+s*maxR); // y
- outside.unshift(centreX+c*maxR); // x
- angle += step;
- }
- c = Math.cos(maxAngle);
- s = Math.sin(maxAngle);
- inside.push(centreX+c*minR);
- inside.push(centreY+s*minR);
- outside.unshift(centreY+s*maxR);
- outside.unshift(centreX+c*maxR);
-
- var vertices = inside.concat(outside);
- g.fillPoly(vertices, true);
-}
-
-// draw the arc segments around the perimeter
-function drawPerimeter() {
- g.clear();
- for (var i = 0; i < N; i++) {
- g.setColor(colours[i%colours.length]);
- var minAngle = (i/N)*radians;
- arc(perimMin,perimMax,minAngle,minAngle+arclen);
- }
-}
-
-// animate a ball rolling around and settling at "target" radians
-function animateChoice(target) {
- var angle = 0;
- var speed = 0;
- var oldx = -10;
- var oldy = -10;
- var decelFromAngle = -1;
- var allowDecel = false;
- for (var i = 0; true; i++) {
- angle = angle + speed;
- if (angle > radians) angle -= radians;
- if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
- speed = speed + accel;
- if (speed > maxSpeed) {
- speed = maxSpeed;
- /* when we reach max speed, we know how long it takes
- * to accelerate, and therefore how long to decelerate, so
- * we can work out what angle to start decelerating from */
- if (decelFromAngle < 0) {
- decelFromAngle = target-angle;
- while (decelFromAngle < 0) decelFromAngle += radians;
- while (decelFromAngle > radians) decelFromAngle -= radians;
- }
- }
- } else {
- if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
- if (allowDecel) speed = speed - accel;
- if (speed < minSpeed) speed = minSpeed;
- if (speed == minSpeed && angle < target && angle+speed >= target) return;
- }
-
- var r = i/2;
- if (r > ballTrack) r = ballTrack;
- var x = centreX+Math.cos(angle)*r;
- var y = centreY+Math.sin(angle)*r;
- g.setColor('#000000');
- g.fillCircle(oldx,oldy,ballSize+1);
- g.setColor('#ffffff');
- g.fillCircle(x, y, ballSize);
- oldx=x;
- oldy=y;
- g.flip();
- }
-}
-
-// choose a winning segment and animate its selection
-function choose() {
- var chosen = Math.floor(Math.random()*N);
- var minAngle = (chosen/N)*radians;
- var maxAngle = minAngle + arclen;
- animateChoice((minAngle+maxAngle)/2);
- g.setColor(colours[chosen%colours.length]);
- for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
- arc(i, perimMax, minAngle, maxAngle);
- arc(0, perimMax, minAngle, maxAngle);
- for (var r = 1; r < segmentMax; r += circleStep)
- g.fillCircle(centreX,centreY,r);
- g.fillCircle(centreX,centreY,segmentMax);
-}
-
-// draw the current value of N in the middle of the screen, with
-// up/down arrows
-function drawN() {
- g.setColor(g.theme.fg);
- g.setFont("Vector",fontSize);
- g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
- if (N < maxN)
- g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
- if (N > minN)
- g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
-}
-
-// update number of segments, with min/max limit, "arclen" update,
-// and screen reset
-function setN(n) {
- N = n;
- if (N < minN) N = minN;
- if (N > maxN) N = maxN;
- arclen = radians/N - gapAngle;
- drawPerimeter();
-}
-
-// save N to choozi.txt
-function writeN() {
- var file = require("Storage").open("choozi.txt","w");
- file.write(N);
-}
-
-// load N from choozi.txt
-function readN() {
- var file = require("Storage").open("choozi.txt","r");
- var n = file.readLine();
- if (n !== undefined) setN(parseInt(n));
- else setN(defaultN);
-}
-
-shuffle(colours); // is this really best?
-Bangle.setLCDTimeout(0); // keep screen on
-readN();
-drawN();
-
-setWatch(() => {
- writeN();
- drawPerimeter();
- choose();
-}, BTN1, {repeat:true});
-
-Bangle.on('touch', function(zone,e) {
- if(e.x>+88){
- setN(N-1);
- drawN();
- }else{
- setN(N+1);
- drawN();
- }
-});
diff --git a/apps/choozi/bangle1-choozi-screenshot1.png b/apps/choozi/bangle1-choozi-screenshot1.png
index 104024958..ee422ed10 100644
Binary files a/apps/choozi/bangle1-choozi-screenshot1.png and b/apps/choozi/bangle1-choozi-screenshot1.png differ
diff --git a/apps/choozi/bangle1-choozi-screenshot2.png b/apps/choozi/bangle1-choozi-screenshot2.png
index f3b6868bf..20edf4c78 100644
Binary files a/apps/choozi/bangle1-choozi-screenshot2.png and b/apps/choozi/bangle1-choozi-screenshot2.png differ
diff --git a/apps/choozi/metadata.json b/apps/choozi/metadata.json
index 79af76fa2..c42abe079 100644
--- a/apps/choozi/metadata.json
+++ b/apps/choozi/metadata.json
@@ -1,7 +1,7 @@
{
"id": "choozi",
"name": "Choozi",
- "version": "0.03",
+ "version": "0.04",
"description": "Choose people or things at random using Bangle.js.",
"icon": "app.png",
"tags": "tool",
@@ -10,8 +10,10 @@
"allow_emulator": true,
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
"storage": [
- {"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
- {"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
+ {"name":"choozi.app.js","url":"app.js"},
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name":"choozi.save"}
]
}
diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog
index 83abde6df..830bc28e8 100644
--- a/apps/circlesclock/ChangeLog
+++ b/apps/circlesclock/ChangeLog
@@ -39,3 +39,4 @@
0.21: Remade all icons without a palette for dark theme
Now re-adds widgets if they were hidden when fast-loading
0.22: Fixed crash if item has no image and cutting long overflowing text
+0.23: Setting circles colours per clkinfo and not position
diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js
index 30d6a48f4..e1fc8d846 100644
--- a/apps/circlesclock/app.js
+++ b/apps/circlesclock/app.js
@@ -20,24 +20,12 @@ let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
- //TODO deprecate this (and perhaps use in the clkinfo module)
-// Load step goal from health app and pedometer widget as fallback
-if (settings.stepGoal == undefined) {
- let d = storage.readJSON("health.json", true) || {};
- settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
-
- if (settings.stepGoal == undefined) {
- d = storage.readJSON("wpedom.json", true) || {};
- settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
- }
-}
let drawTimeout;
const showWidgets = settings.showWidgets || false;
const circleCount = settings.circleCount || 3;
const showBigWeather = settings.showBigWeather || false;
-let hrtValue; //TODO deprecate this
let now = Math.round(new Date().getTime() / 1000);
// layout values:
@@ -128,8 +116,11 @@ let draw = function() {
queueDraw();
}
-let getCircleColor = function(index) {
- let color = settings["circle" + index + "color"];
+let getCircleColor = function(item, clkmenu) {
+ let colorKey = clkmenu.name;
+ if(!clkmenu.dynamic) colorKey += "/"+item.name;
+ colorKey += "_color";
+ let color = settings[colorKey];
if (color && color != "") return color;
return g.theme.fg;
}
@@ -138,7 +129,7 @@ let getGradientColor = function(color, percent) {
if (isNaN(percent)) percent = 0;
if (percent > 1) percent = 1;
let colorList = [
- '#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
+ '#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'
];
if (color == "fg") {
color = colorFg;
@@ -151,6 +142,17 @@ let getGradientColor = function(color, percent) {
let colorIndex = colorList.length - Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
}
+ colorList = [
+ '#0000ff', '#8800ff', '#ff00ff', '#ff0088', '#ff0000'
+ ];
+ if (color == "blue-red") {
+ let colorIndex = Math.round(colorList.length * percent);
+ return colorList[Math.min(colorIndex, colorList.length) - 1] || "#0000ff";
+ }
+ if (color == "red-blue") {
+ let colorIndex = colorList.length - Math.round(colorList.length * percent);
+ return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
+ }
return color;
}
@@ -172,10 +174,10 @@ let drawEmpty = function(img, w, color) {
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
}
-let drawCircle = function(index, item, data) {
+let drawCircle = function(index, item, data, clkmenu) {
var w = circlePosX[index-1];
drawCircleBackground(w);
- const color = getCircleColor(index);
+ const color = getCircleColor(item, clkmenu);
//drawEmpty(info? info.img : null, w, color);
var img = data.img;
var percent = 1; //fill up if no range
@@ -338,7 +340,8 @@ Bangle.setUI({
let clockInfoDraw = (itm, info, options) => {
//print("Draw",itm.name,options);
- drawCircle(options.circlePosition, itm, info);
+ let clkmenu = clockInfoItems[options.menuA];
+ drawCircle(options.circlePosition, itm, info, clkmenu);
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
};
let clockInfoItems = require("clock_info").load();
diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json
index ad409b992..9d5b3e242 100644
--- a/apps/circlesclock/default.json
+++ b/apps/circlesclock/default.json
@@ -3,23 +3,21 @@
"showWidgets": false,
"weatherCircleData": "humidity",
"circleCount": 3,
- "circle1color": "green-red",
- "circle2color": "#0000ff",
- "circle3color": "red-green",
- "circle4color": "#ffff00",
+ "Bangle/Battery_color":"red-green",
+ "Bangle/Steps_color":"#0000ff",
+ "Bangle/HRM_color":"green-red",
+ "Bangle/Altitude_color":"#00ff00",
+ "Weather/conditionWithTemperature_color":"#ffff00",
+ "Weather/condition_color":"#00ffff",
+ "Weather/humidity_color":"#00ffff",
+ "Weather/wind_color":"fg",
+ "Weather/temperature_color":"blue-red",
+ "Alarms_color":"#00ff00",
+ "Agenda_color":"#ff0000",
"circle1colorizeIcon": true,
"circle2colorizeIcon": true,
"circle3colorizeIcon": true,
"circle4colorizeIcon": false,
"updateInterval": 60,
- "showBigWeather": false,
-
- "minHR": 40,
- "maxHR": 200,
- "confidence": 0,
- "stepGoal": 10000,
- "stepDistanceGoal": 8000,
- "stepLength": 0.8,
- "hrmValidity": 60
-
+ "showBigWeather": false
}
diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json
index 1b94c00b3..45b869521 100644
--- a/apps/circlesclock/metadata.json
+++ b/apps/circlesclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
- "version":"0.22",
+ "version":"0.23",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js
index 5c5ea4f27..63a2b0f93 100644
--- a/apps/circlesclock/settings.js
+++ b/apps/circlesclock/settings.js
@@ -12,12 +12,10 @@
storage.write(SETTINGS_FILE, settings);
}
- const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
- "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
- const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
- "cyan", "white", "black", "green->red", "red->green", "foreground"];
-
- const weatherData = ["empty", "humidity", "wind"];
+ const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
+ "#00ffff", "#fff", "#000", "green-red", "red-green", "blue-red", "red-blue", "fg"];
+ const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
+ "cyan", "white", "black", "green->red", "red->green", "blue->red", "red->blue", "foreground"];
function showMainMenu() {
let menu ={
@@ -30,31 +28,11 @@
step: 1,
onchange: x => save('circleCount', x),
},
- /*LANG*/'circle 1': ()=>showCircleMenu(1),
- /*LANG*/'circle 2': ()=>showCircleMenu(2),
- /*LANG*/'circle 3': ()=>showCircleMenu(3),
- /*LANG*/'circle 4': ()=>showCircleMenu(4),
- /*LANG*/'battery warn': {
- value: settings.batteryWarn,
- min: 10,
- max : 100,
- step: 10,
- format: x => {
- return x + '%';
- },
- onchange: x => save('batteryWarn', x),
- },
/*LANG*/'show widgets': {
value: !!settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x),
},
- /*LANG*/'weather data': {
- value: weatherData.indexOf(settings.weatherCircleData),
- min: 0, max: 2,
- format: v => weatherData[v],
- onchange: x => save('weatherCircleData', weatherData[x]),
- },
/*LANG*/'update interval': {
value: settings.updateInterval,
min: 0,
@@ -65,41 +43,54 @@
},
onchange: x => save('updateInterval', x),
},
- //TODO deprecated local icons, may disappear in future
- /*LANG*/'legacy weather icons': {
- value: !!settings.legacyWeatherIcons,
- format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
- onchange: x => save('legacyWeatherIcons', x),
- },
/*LANG*/'show big weather': {
value: !!settings.showBigWeather,
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
onchange: x => save('showBigWeather', x),
- }
+ },
+ /*LANG*/'colorize icons': ()=>showCircleMenus()
};
+ clock_info.load().forEach(e=>{
+ if(e.dynamic) {
+ const colorKey = e.name + "_color";
+ menu[e.name+/*LANG*/' color'] = {
+ value: valuesColors.indexOf(settings[colorKey]) || 0,
+ min: 0, max: valuesColors.length - 1,
+ format: v => namesColors[v],
+ onchange: x => save(colorKey, valuesColors[x]),
+ };
+ } else {
+ let values = e.items.map(i=>e.name+"/"+i.name);
+ let names = e.name=="Bangle" ? e.items.map(i=>i.name) : values;
+ values.forEach((v,i)=>{
+ const colorKey = v + "_color";
+ menu[names[i]+/*LANG*/' color'] = {
+ value: valuesColors.indexOf(settings[colorKey]) || 0,
+ min: 0, max: valuesColors.length - 1,
+ format: v => namesColors[v],
+ onchange: x => save(colorKey, valuesColors[x]),
+ };
+ });
+ }
+ })
E.showMenu(menu);
}
- function showCircleMenu(circleId) {
- const circleName = "circle" + circleId;
- const colorKey = circleName + "color";
- const colorizeIconKey = circleName + "colorizeIcon";
-
- const menu = {
- '': { 'title': /*LANG*/'Circle ' + circleId },
- /*LANG*/'< Back': ()=>showMainMenu(),
- /*LANG*/'color': {
- value: valuesColors.indexOf(settings[colorKey]) || 0,
- min: 0, max: valuesColors.length - 1,
- format: v => namesColors[v],
- onchange: x => save(colorKey, valuesColors[x]),
- },
- /*LANG*/'colorize icon': {
+ function showCircleMenus() {
+ const menu = {
+ '': { 'title': /*LANG*/'Colorize icons'},
+ /*LANG*/'< Back': ()=>showMainMenu(),
+ };
+ for(var circleId=1; circleId<=4; ++circleId) {
+ const circleName = "circle" + circleId;
+ const colorKey = circleName + "color";
+ const colorizeIconKey = circleName + "colorizeIcon";
+ menu[/*LANG*/'circle ' + circleId] = {
value: settings[colorizeIconKey] || false,
- format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
- onchange: x => save(colorizeIconKey, x),
- },
- };
+ format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
+ onchange: x => save(colorizeIconKey, x),
+ };
+ }
E.showMenu(menu);
}
diff --git a/apps/clkinfofw/ChangeLog b/apps/clkinfofw/ChangeLog
index 7b83706bf..10810802b 100644
--- a/apps/clkinfofw/ChangeLog
+++ b/apps/clkinfofw/ChangeLog
@@ -1 +1,2 @@
0.01: First release
+0.02: Update clock_info to avoid a redraw and image allocation
diff --git a/apps/clkinfofw/clkinfo.js b/apps/clkinfofw/clkinfo.js
index 9815ca87f..2b3cb32ba 100644
--- a/apps/clkinfofw/clkinfo.js
+++ b/apps/clkinfofw/clkinfo.js
@@ -4,26 +4,13 @@
items: [
{ name : "FW",
get : () => {
- let d = new Date();
- let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
- g.drawImage(atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV"),1,0);
return {
text : process.env.VERSION,
- img : g.asImage("string")
+ img : atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV")
};
},
- show : function() {
- this.interval = setTimeout(()=>{
- this.emit("redraw");
- this.interval = setInterval(()=>{
- this.emit("redraw");
- }, 86400000);
- }, 86400000 - (Date.now() % 86400000));
- },
- hide : function() {
- clearInterval(this.interval);
- this.interval = undefined;
- }
+ show : function() {},
+ hide : function() {}
}
]
};
diff --git a/apps/clkinfofw/metadata.json b/apps/clkinfofw/metadata.json
index 924297ca3..720a5baa5 100644
--- a/apps/clkinfofw/metadata.json
+++ b/apps/clkinfofw/metadata.json
@@ -1,6 +1,6 @@
{ "id": "clkinfofw",
"name": "Firmware Clockinfo",
- "version":"0.01",
+ "version":"0.02",
"description": "For clocks that display 'clockinfo', this displays the firmware version string",
"icon": "app.png",
"type": "clkinfo",
diff --git a/apps/cogclock/ChangeLog b/apps/cogclock/ChangeLog
index f4bfe77a5..403cd2258 100644
--- a/apps/cogclock/ChangeLog
+++ b/apps/cogclock/ChangeLog
@@ -1,3 +1,4 @@
0.01: New clock
0.02: Use ClockFace library, add settings
0.03: Use ClockFace_menu.addSettingsFile
+0.04: Hide widgets instead of not loading them at all
diff --git a/apps/cogclock/metadata.json b/apps/cogclock/metadata.json
index 29000b589..d404275ee 100644
--- a/apps/cogclock/metadata.json
+++ b/apps/cogclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "cogclock",
"name": "Cog Clock",
- "version": "0.03",
+ "version": "0.04",
"description": "A cross-shaped clock inside a cog",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
diff --git a/apps/cogclock/settings.js b/apps/cogclock/settings.js
index a91b033d0..fb1dd761c 100644
--- a/apps/cogclock/settings.js
+++ b/apps/cogclock/settings.js
@@ -4,7 +4,7 @@
/*LANG*/"< Back": back,
};
require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [
- "showDate", "loadWidgets"
+ "showDate", "hideWidgets"
]);
E.showMenu(menu);
});
diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog
index 61a09a18d..751164c07 100644
--- a/apps/daisy/ChangeLog
+++ b/apps/daisy/ChangeLog
@@ -7,3 +7,4 @@
0.07: Use default Bangle formatter for booleans
0.08: fix idle timer always getting set to true
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
+0.10: Use widget_utils.
diff --git a/apps/daisy/app.js b/apps/daisy/app.js
index c99b19228..3b3975105 100644
--- a/apps/daisy/app.js
+++ b/apps/daisy/app.js
@@ -1,6 +1,7 @@
var SunCalc = require("suncalc"); // from modules folder
const storage = require('Storage');
const locale = require("locale");
+const widget_utils = require('widget_utils');
const SETTINGS_FILE = "daisy.json";
const LOCATION_FILE = "mylocation.json";
const h = g.getHeight();
@@ -547,8 +548,6 @@ g.clear();
Bangle.loadWidgets();
/*
* we are not drawing the widgets as we are taking over the whole screen
- * so we will blank out the draw() functions of each widget and change the
- * area to the top bar doesn't get cleared.
*/
-for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
+widget_utils.hide();
draw();
diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json
index 0bad50151..471f8e56f 100644
--- a/apps/daisy/metadata.json
+++ b/apps/daisy/metadata.json
@@ -1,6 +1,6 @@
{ "id": "daisy",
"name": "Daisy",
- "version":"0.09",
+ "version":"0.10",
"dependencies": {"mylocation":"app"},
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
"icon": "app.png",
diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog
index 265094e87..faf3d2d33 100644
--- a/apps/dragboard/ChangeLog
+++ b/apps/dragboard/ChangeLog
@@ -4,3 +4,4 @@
0.04: Now displays the opened text string at launch.
0.05: Now scrolls text when string gets longer than screen width.
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
+0.07: Settings for display colors
diff --git a/apps/dragboard/README.md b/apps/dragboard/README.md
index 8960e5749..415be5449 100644
--- a/apps/dragboard/README.md
+++ b/apps/dragboard/README.md
@@ -12,5 +12,8 @@ Known bugs:
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
-To do:
-- Possibly provide a dragboard.settings.js file
+Settings:
+- CAPS LOCK: all characters are displayed and typed in uppercase
+- ABC Color: color of the characters row
+- Num Color: color of the digits and symbols row
+- Highlight Color: color of the currently highlighted character
diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js
index 220f075d7..83aae5f14 100644
--- a/apps/dragboard/lib.js
+++ b/apps/dragboard/lib.js
@@ -2,12 +2,14 @@ exports.input = function(options) {
options = options||{};
var text = options.text;
if ("string"!=typeof text) text="";
+ let settings = require('Storage').readJSON('dragboard.json',1)||{}
var R = Bangle.appRect;
+ const paramToColor = (param) => g.toColor(`#${settings[param].toString(16).padStart(3,0)}`);
var BGCOLOR = g.theme.bg;
- var HLCOLOR = g.theme.fg;
- var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
- var NUMCOLOR = g.toColor(0,1,0);//'#00FF00';
+ var HLCOLOR = settings.Highlight ? paramToColor("Highlight") : g.theme.fg;
+ var ABCCOLOR = settings.ABC ? paramToColor("ABC") : g.toColor(1,0,0);//'#FF0000';
+ var NUMCOLOR = settings.Num ? paramToColor("Num") : g.toColor(0,1,0);//'#00FF00';
var BIGFONT = '6x8:3';
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
var SMALLFONT = '6x8:1';
@@ -102,6 +104,7 @@ exports.input = function(options) {
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
function changeCase(abcHL) {
+ if (settings.uppercase) return;
g.setColor(BGCOLOR);
g.setFontAlign(-1, -1, 0);
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json
index 64b6dbe18..964ace3a7 100644
--- a/apps/dragboard/metadata.json
+++ b/apps/dragboard/metadata.json
@@ -1,6 +1,6 @@
{ "id": "dragboard",
"name": "Dragboard",
- "version":"0.06",
+ "version":"0.07",
"description": "A library for text input via swiping keyboard",
"icon": "app.png",
"type":"textinput",
@@ -9,6 +9,7 @@
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
- {"name":"textinput","url":"lib.js"}
+ {"name":"textinput","url":"lib.js"},
+ {"name":"dragboard.settings.js","url":"settings.js"}
]
}
diff --git a/apps/dragboard/settings.js b/apps/dragboard/settings.js
new file mode 100644
index 000000000..a53914869
--- /dev/null
+++ b/apps/dragboard/settings.js
@@ -0,0 +1,48 @@
+(function(back) {
+ let settings = require('Storage').readJSON('dragboard.json',1)||{};
+ const colors = {
+ 4095: /*LANG*/"White",
+ 4080: /*LANG*/"Yellow",
+ 3840: /*LANG*/"Red",
+ 3855: /*LANG*/"Magenta",
+ 255: /*LANG*/"Cyan",
+ 240: /*LANG*/"Green",
+ 15: /*LANG*/"Blue",
+ 0: /*LANG*/"Black",
+ '-1': /*LANG*/"Default"
+ };
+
+ const save = () => require('Storage').write('dragboard.json', settings);
+ function colorMenu(key) {
+ let menu = {'': {title: key}, '< Back': () => E.showMenu(appMenu)};
+ Object.keys(colors).forEach(color => {
+ var label = colors[color];
+ menu[label] = {
+ value: settings[key] == color,
+ onchange: () => {
+ if (color >= 0) {
+ settings[key] = color;
+ } else {
+ delete settings[key];
+ }
+ save();
+ setTimeout(E.showMenu, 10, appMenu);
+ }
+ };
+ });
+ return menu;
+ }
+
+ const appMenu = {
+ '': {title: 'Dragboard'}, '< Back': back,
+ /*LANG*/'CAPS LOCK': {
+ value: !!settings.uppercase,
+ onchange: v => {settings.uppercase = v; save();}
+ },
+ /*LANG*/'ABC Color': () => E.showMenu(colorMenu("ABC")),
+ /*LANG*/'Num Color': () => E.showMenu(colorMenu("Num")),
+ /*LANG*/'Highlight Color': () => E.showMenu(colorMenu("Highlight"))
+ };
+
+ E.showMenu(appMenu);
+});
\ No newline at end of file
diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog
index 044b8c35f..e0bd76eb0 100644
--- a/apps/dtlaunch/ChangeLog
+++ b/apps/dtlaunch/ChangeLog
@@ -23,4 +23,8 @@ button to exit is no longer an option.
facilitate 'fast switching' of apps where available.
0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since
widgets would still be loaded when they weren't supposed to.
+0.21: Bangle 2: Call Bangle.drawWidgets() early on so that the widget field
+immediately follows the correct theme.
+0.22: Bangle 2: Change to not automatically marking the first app on a page
+when moving pages. Add caching for faster startups.
diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index a7a318c18..2070f1147 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -13,20 +13,25 @@
}, require('Storage').readJSON("dtlaunch.json", true) || {});
let s = require("Storage");
- var apps = s.list(/\.info$/).map(app=>{
- let a=s.readJSON(app,1);
- return a && {
- name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
- };}).filter(
- app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
-
- apps.sort((a,b)=>{
- let n=(0|a.sortorder)-(0|b.sortorder);
- if (n) return n; // do sortorder first
- if (a.nameb.name) return 1;
- return 0;
- });
+ // Borrowed caching from Icon Launcher, code by halemmerich.
+ let launchCache = s.readJSON("launch.cache.json", true)||{};
+ let launchHash = require("Storage").hash(/\.info/);
+ if (launchCache.hash!=launchHash) {
+ launchCache = {
+ hash : launchHash,
+ apps : s.list(/\.info$/)
+ .map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};})
+ .filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type))
+ .sort((a,b)=>{
+ var n=(0|a.sortorder)-(0|b.sortorder);
+ if (n) return n; // do sortorder first
+ if (a.nameb.name) return 1;
+ return 0;
+ }) };
+ s.writeJSON("launch.cache.json", launchCache);
+ }
+ let apps = launchCache.apps;
apps.forEach(app=>{
if (app.icon)
app.icon = s.read(app.icon); // should just be a link to a memory area
@@ -84,12 +89,13 @@
g.flip();
};
+ Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme.
Bangle.loadWidgets();
drawPage(0);
let swipeListenerDt = function(dirLeftRight, dirUpDown){
updateTimeoutToClock();
- selected = 0;
+ selected = -1;
oldselected=-1;
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
if (dirUpDown==-1||dirLeftRight==-1){
@@ -153,3 +159,4 @@
updateTimeoutToClock();
} // end of app scope
+
diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json
index b69a1a5e6..b19d59e49 100644
--- a/apps/dtlaunch/metadata.json
+++ b/apps/dtlaunch/metadata.json
@@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
- "version": "0.20",
+ "version": "0.22",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",
diff --git a/apps/entonclk/ChangeLog b/apps/entonclk/ChangeLog
index 62e2d0c20..e16defa54 100644
--- a/apps/entonclk/ChangeLog
+++ b/apps/entonclk/ChangeLog
@@ -1 +1,2 @@
-0.1: New App!
\ No newline at end of file
+0.1: New App!
+0.2: Now with timer function
diff --git a/apps/entonclk/README.md b/apps/entonclk/README.md
index 8c788c7a5..c67cc19c8 100644
--- a/apps/entonclk/README.md
+++ b/apps/entonclk/README.md
@@ -6,4 +6,16 @@ Things I changed:
- The main font for the time is now Audiowide
- Removed the written out day name and replaced it with steps and bpm
-- Changed the date string to a (for me) more readable string
\ No newline at end of file
+- Changed the date string to a (for me) more readable string
+
+Timer function:
+- Touch the right side, to start the timer
+- Initial timer timeout is 300s/5min
+- Right touch again, add 300s/5min to timeout
+- Left touch, decrease timeout by 60s/1min
+- So it is easy, to add timeouts like 7min/3min or 12min
+- Special thanks to the maintainer of the a_clock_timer app from which I borrowed the code.
+
+Todo:
+- Make displayed information configurable, after https://github.com/espruino/BangleApps/issues/2226
+- Clean up code
diff --git a/apps/entonclk/app.js b/apps/entonclk/app.js
index 69fdea479..292030b86 100644
--- a/apps/entonclk/app.js
+++ b/apps/entonclk/app.js
@@ -1,67 +1,127 @@
+// Fonts
Graphics.prototype.setFontAudiowide = function() {
- // Actual height 33 (36 - 4)
var widths = atob("CiAsESQjJSQkHyQkDA==");
var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA");
var scale = 1; // size multiplier for this font
g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16));
};
+// Globals variables
+var timervalue = 0;
+var istimeron = false;
+var timertick;
+
+// Functions
function getSteps() {
- var steps = 0;
- try{
- if (WIDGETS.wpedom !== undefined) {
- steps = WIDGETS.wpedom.getSteps();
- } else if (WIDGETS.activepedom !== undefined) {
- steps = WIDGETS.activepedom.getSteps();
- } else {
- steps = Bangle.getHealthStatus("day").steps;
- }
+ var steps = 0;
+ try{
+ if (WIDGETS.wpedom !== undefined) {
+ steps = WIDGETS.wpedom.getSteps();
+ } else if (WIDGETS.activepedom !== undefined) {
+ steps = WIDGETS.activepedom.getSteps();
+ } else {
+ steps = Bangle.getHealthStatus("day").steps;
+ }
} catch(ex) {
- // In case we failed, we can only show 0 steps.
- return "?";
+ // In case we failed, we can only show 0 steps.
+ return "?";
}
- return Math.round(steps);
+ return Math.round(steps);
}
+function timeToString(duration) {
+ var hrs = ~~(duration / 3600);
+ var mins = ~~((duration % 3600) / 60);
+ var secs = ~~duration % 60;
+ var ret = "";
+ if (hrs > 0) {
+ ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
+ }
+ ret += "" + mins + ":" + (secs < 10 ? "0" : "");
+ ret += "" + secs;
+ return ret;
+}
+
+function countDown() {
+ timervalue--;
+
+ g.reset().clearRect(0, 40, 44+99, g.getHeight()/2-25);
+
+ g.setFontAlign(0, -1, 0);
+ g.setFont("6x8", 2).drawString(timeToString(timervalue), 95, g.getHeight()/2-50);
+
+ if (timervalue <= 0) {
+ istimeron = false;
+ clearInterval(timertick);
+
+ Bangle.buzz().then(()=>{
+ return new Promise(resolve=>setTimeout(resolve, 500));
+ }).then(()=>{
+ return Bangle.buzz(1000);
+ });
+ }
+ else
+ if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); }
+}
+
+// Touch
+Bangle.on('touch',t => {
+ if (t == 1) {
+ // Touch on the left, reduce timervalue about 60s
+ Bangle.buzz(30);
+ if (timervalue < 60) { timervalue = 1 ; }
+ else { timervalue -= 60; }
+ }
+ // Touch on the right, raise timervaule about 300s
+ else if (t == 2) {
+ Bangle.buzz(30);
+ if (!istimeron) {
+ istimeron = true;
+ timertick = setInterval(countDown, 1000);
+ }
+ timervalue += 60*5;
+ }
+});
+
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
-let drawTimeout;
+ let drawTimeout;
-// Actually draw the watch face
-let draw = function() {
- var x = g.getWidth() / 2;
- var y = g.getHeight() / 2;
- g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
- var date = new Date();
- var timeStr = require("locale").time(date, 1); // Hour and minute
- g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
- var dateStr = require("locale").date(date, 1).toUpperCase();
- g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
- g.setFontAlign(0, 0).setFont("6x8", 2);
- g.drawString(getSteps(), 50, y+70);
- g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
+ // Actually draw the watch face
+ let draw = function() {
+ var x = g.getWidth() / 2;
+ var y = g.getHeight() / 2;
+ g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
+ var date = new Date();
+ var timeStr = require("locale").time(date, 1); // Hour and minute
+ g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
+ var dateStr = require("locale").date(date, 1).toUpperCase();
+ g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
+ g.setFontAlign(0, 0).setFont("6x8", 2);
+ g.drawString(getSteps(), 50, y+70);
+ g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
- // queue next draw
- if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = setTimeout(function() {
- drawTimeout = undefined;
- draw();
- }, 60000 - (Date.now() % 60000));
-};
-
-// Show launcher when middle button pressed
-Bangle.setUI({
- mode : "clock",
- remove : function() {
- // Called to unload all of the clock app
+ // queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
- drawTimeout = undefined;
- delete Graphics.prototype.setFontAnton;
- }});
-// Load widgets
-Bangle.loadWidgets();
-draw();
-setTimeout(Bangle.drawWidgets,0);
-}
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+ };
+
+ // Show launcher when middle button pressed
+ Bangle.setUI({
+ mode : "clock",
+ remove : function() {
+ // Called to unload all of the clock app
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ delete Graphics.prototype.setFontAnton;
+ }});
+ // Load widgets
+ Bangle.loadWidgets();
+ draw();
+ setTimeout(Bangle.drawWidgets,0);
+}
\ No newline at end of file
diff --git a/apps/entonclk/metadata.json b/apps/entonclk/metadata.json
index 7e4947406..4b7174263 100644
--- a/apps/entonclk/metadata.json
+++ b/apps/entonclk/metadata.json
@@ -1,8 +1,8 @@
{
"id": "entonclk",
"name": "Enton Clock",
- "version": "0.1",
- "description": "A simple clock using the Audiowide font. ",
+ "version": "0.2",
+ "description": "A simple clock using the Audiowide font with timer. ",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
diff --git a/apps/geminiclock/ChangeLog b/apps/geminiclock/ChangeLog
new file mode 100644
index 000000000..c408647b6
--- /dev/null
+++ b/apps/geminiclock/ChangeLog
@@ -0,0 +1 @@
+0.01: App created!
diff --git a/apps/geminiclock/README.md b/apps/geminiclock/README.md
new file mode 100644
index 000000000..d05acca04
--- /dev/null
+++ b/apps/geminiclock/README.md
@@ -0,0 +1,20 @@
+# Gemini clock
+
+A simple clock face using the Buro Destruct Geminis font, inspired by their Pebble Watch designs: https://burodestruct.net/work/pebble-watchfaces
+
+
+
+It is designed for maximum legibility and utility whilst still showing widgets.
+
+If editing or remixing this code, please retain leading zeroes on the hours, they are an integral part of the design.
+
+The minutes are not right-aligned deliberately so that the numbers don't jump around too much when they change.
+
+
+## Creator
+Created by Giles Booth:
+- http://www.suppertime.co.uk/blogmywiki/
+- https://mastodon.social/@blogmywiki
+- https://github.com/blogmywiki
+
+
diff --git a/apps/geminiclock/app-icon.js b/apps/geminiclock/app-icon.js
new file mode 100644
index 000000000..e571d5b6f
--- /dev/null
+++ b/apps/geminiclock/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4f/AoP//+iiE00u++/nnMooWSyhT/AA8C9u27dtAQNkgEKAwdQCIUD5MkyVJAQNOwG9DIf+oARBgIPDAQOZoHSA4cj0ARIyeA6AIDpnAI4X2FgXcwgRBp0SDQYRCgErKAVt+ARBi4HC3AREAAnMCIIFCgN4CJOuiYRDgFmCKFXhIR/CMSPFCI0reYazCCJEDuzXGUIvoCIXCfY0Brdt284pOfqjLBBwQCCzNArf///9DoMx3gaBEAYTBnmA5wZCiQDB4+QCIeY+3bFgPQFg3QCIeXKwdMCJfOCIcbI4gRLhIhCvARMR4oRPWYIR/CNNnCI+Z9u20AREtCPHzMbtv8wEXv7GB3ARHAQXJoGuA4VCCIjdCCIWTwHQfY8DnIHDAQNACI+AgNvHwIACI4Nd23btoCC3x3BEQsggEKCAm26iIGAH4ATA=="))
diff --git a/apps/geminiclock/app.png b/apps/geminiclock/app.png
new file mode 100644
index 000000000..2c96e8937
Binary files /dev/null and b/apps/geminiclock/app.png differ
diff --git a/apps/geminiclock/gemini-watch-app.js b/apps/geminiclock/gemini-watch-app.js
new file mode 100644
index 000000000..716bc37b5
--- /dev/null
+++ b/apps/geminiclock/gemini-watch-app.js
@@ -0,0 +1,73 @@
+// Clock by Giles Booth for BangleJS2 using Büro Destruct Console Remix font
+// based on code in https://www.espruino.com/Bangle.js+Clock+Font
+
+Graphics.prototype.setFontBDGemini = function() {
+ // Actual height 79 (84 - 6)
+ var widths = atob("Gio1KS8zNS8vLzkvGg==");
+ var font = atob("AAAAAAAAD///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAH///gAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH+AAAAAAAAAAAAAAf/AAAAAAAAAAAAAD//AAAAAAAAAAAAAP//AAAAAAAAAAAAA///AAAAAAAAAAAAH///AAAAAAAAAAAAf//+AAAAAAAAAAAD///8AAAAAAAAAAAP///wAAAAAAAAAAB///+AAAAAAAAAAAH///4AAAAAAAAAAA////gAAAAAAAAAAD///8AAAAAAAAAAAP///wAAAAAAAAAAB///+AAAAAAAAAAAH///4AAAAAAAAAAA////AAAAAAAAAAAD///8AAAAAAAAAAAf///gAAAAAAAAAAB///+AAAAAAAAAAAP///wAAAAAAAAAAA////AAAAAAAAAAAH///8AAAAAAAAAAAf///gAAAAAAAAAAB///+AAAAAAAAAAAP///wAAAAAAAAAAA////AAAAAAAAAAAH///4AAAAAAAAAAAP///gAAAAAAAAAAAf//8AAAAAAAAAAAAf//wAAAAAAAAAAAAf/+AAAAAAAAAAAAAf/4AAAAAAAAAAAAAf/gAAAAAAAAAAAAAP8AAAAAAAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////4AAAf///////////8AAAf///////////8AAA////////////+AAA////////////+AAA////////////+AAA////////////+AAA////////////+AAA//gAAAAB////+AAA/+AAAAAD////+AAA/8AAAAAP////+AAA/4AAAAA/////+AAA/4AAAAD/////+AAA/4AAAAH/////+AAA/4AAAAf/////+AAA/4AAAB//+f//+AAA/4AAAD//8f//+AAA/4AAAP//wf//+AAA/4AAA///Af//+AAA/4AAB//+Af//+AAA/4AAH//4Af//+AAA/4AAf//gAf//+AAA/4AB///AAf//+AAA/4AD//8AAf//+AAA/4AP//wAAf//+AAA/4A///gAAf//+AAA/4B//+AAAf//+AAA/4H//4AAAf//+AAA/4f//wAAAf//+AAA/4///AAAAf//+AAA/7//8AAAAf//+AAA////wAAAAf//+AAA////gAAAAf//+AAA///+AAAAAf//+AAA///4AAAAA///+AAA///wAAAAA///+AAA///AAAAAB///+AAA//+AAAAAP///+AAA////////////+AAA////////////+AAA////////////+AAA////////////+AAA////////////+AAAf///////////8AAAP///////////8AAAH///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAB//8AAAf4AAAAAAAD//+AAAf4AAAAAAAH//+AAAf4AAAAAAAP///AAAf8AAAAAAAP///AAAf8AAAAAAAP///AAAf8AAAAAAAP///AAAf8AAAAAAAP///AAAf8AAAAAAAP///AAAf8AAAAAAAf///AAAf+AAAAAAAf///AAAf/AAAAAAA////AAAf/4AAAAAP////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAP////////////AAAP////////////AAAD////////////AAAAAAAAAAAH////AAAAAAAAAAAA////AAAAAAAAAAAAf///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAP///AAAAAAAAAAAAH//+AAAAAAAAAAAAD//+AAAAAAAAAAAAB//4AAAAAAAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAD//////4AAA/wAAAH//////8AAA/wAAAH//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP/4AAB/+AAA/4AAAP/gAAAf+AAA/4AAAP/AAAAf+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/8AAAf+AAAAP+AAA/+AAA/+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAH+AAAf/////8AAAAH+AAAP/////4AAAAH+AAAH/////wAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAPwAAAB+AAAf4AAAAP4AAAD/AAAf4AAAAf8AAAD/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAAf8AAAH/AAAf8AAAA/+AAAH/AAAf+AAAB/+AAAP/AAAf/gAAD//gAAf/AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAP////////////AAAH////////////AAAD////////////AAAAAAAAH///////AAAAAAAAD///////AAAAAAAAB///////AAAAAAAAA///////AAAAAAAAA///////AAAAAAAAAf//////AAAAAAAAAf//////AAAAAAAAAf//////AAAAAAAAAP/////+AAAAAAAAAH/////+AAAAAAAAAD/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAAAAAAAf8AAAAAAAAAAAAAB/+AAAAAAAAAAAAAD/+AAAAAAAAAAAAAP//AAAAAAAAAAAAAf//AAAAAAAAAAAAB///AAAAAAAAAAAAD///AAAAAAAAAAAAP///AAAAAAAAAAAAf///AAAAAAAAAAAB////AAAAAAAAAAAD////AAAAAAAAAAAP////AAAAAAAAAAA//+P/AAAAAAAAAAB//4H/AAAAAAAAAAH//wH/AAAAAAAAAAP//AH/AAAAAAAAAA//+AH/AAAAAAAAAB//4AH/AAAAAAAAAH//wAH/AAAAAAAAAP//AAH/AAAAAAAAAP/+AAH/AAA//4AAAf/4AAH/AAB//8AAAf/wAAH/AAD//+AAAf/AAAH/AAH///AAAf+AAAH/AAH///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAP/AAf///AAAf8AAAP/AAf///AAAf+AAAf/gB////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAP////////////AAAH////////////AAAD////////////AAAAAAAAAAAD////AAAAAAAAAAAA////AAAAAAAAAAAAP///AAAAAAAAAAAAP//+AAAAAAAAAAAAD//8AAAAAAAAAAAAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAAAD8AAAf/////4AAAAH+AAAf/////8AAAAH+AAA//////8AAAAH+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//////+AAAAP+AAA//wAH/+AAAAP+AAA/+AAA/+AAAAP+AAA/8AAAf+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP+AAAAP+AAA/4AAAP/AAAAf+AAA/4AAAP/gAAAf+AAA/4AAAP/wAAB/+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/4AAAP//////+AAA/wAAAH//////+AAA/wAAAH//////8AAAfwAAAD//////4AAAPAAAAA//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///////////8AAAP///////////+AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf/AAA//4B////AAAf+AAAP/gAf///AAAf8AAAP/gAf///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/gAf///AAAf8AAAH/gAf///AAAf8AAAH/4B////AAAf8AAAH///////AAAf8AAAH///////AAAf8AAAH///////AAAf8AAAH///////AAAf8AAAH///////AAAf8AAAD///////AAAf4AAAD///////AAAf4AAAB///////AAAPwAAAA//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/+AAAAAAAAAAAAAf//AAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAAAAAAAAAA///gAAAH////4AAA///gAAAP////8AAA///gAAAf////+AAA///gAAA/////+AAA///gAAB/////+AAA///gAAD/////+AAA///gAAH/////+AAA///gAAP/////8AAA///gAAf/////4AAA///gAA//gAAAAAAA///gAB//AAAAAAAA///wAD/+AAAAAAAA///wAH/8AAAAAAAA///wAf/wAAAAAAAA///4A//gAAAAAAAA///8B//AAAAAAAAA//////+AAAAAAAAA//////8AAAAAAAAA//////4AAAAAAAAA//////wAAAAAAAAA//////gAAAAAAAAA//////AAAAAAAAAAf////+AAAAAAAAAAP////8AAAAAAAAAAH////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////8AAAAAAAAAf/////+AAAAAAAAA///////AAAAAAAAB///////AAAAAAAAD///////AAAAAAAAH///////AAAAAAAAP///////AAAAAAAA////////AAAAAAAD//4B////AAAD//////wA////AAAP//////gAf///AAAP//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf/AAAf/AAP///AAAf+AAAP/AAP///AAAf8AAAP/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf+AAAP/AAP///AAAf/AAAf/AAP///AAAf/4AD//AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAP//////AAP///AAAP//////gAf///AAAD//////wA////AAAAf/////4B////AAAAAAAA////////AAAAAAAAP///////AAAAAAAAH///////AAAAAAAAD///////AAAAAAAAB///////AAAAAAAAA///////AAAAAAAAAf/////+AAAAAAAAAP/////8AAAAAAAAAH/////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////8AAH//+AAAf/////+AAP///AAAf/////+AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf//////AAP///AAAf/AAA//AAP///AAAf+AAAP/AAP///AAAf8AAAP/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAH/AAP///AAAf8AAAP/gAf///AAAf+AAAP/gAf///AAAf/AAAf/wA////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAf////////////AAAP///////////+AAAH///////////+AAAD///////////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAD///gAAAAAH///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAP///gAH///gAAAAAH///AAH///gAAAAAH///AAD///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ var scale = 1; // size multiplier for this font
+ g.setFontCustom(font, 46, widths, 96+(scale<<8)+(1<<16));
+};
+
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+}
+
+
+function draw() {
+ g.reset();
+ // work out locale-friendly date/time
+ var date = new Date();
+ var timeStr = require("locale").time(date,1);
+ var hh = timeStr.substr(0,2);
+ // Kludge to add leading zeros to hours - if recoding please implement leading zeros:
+ // Leading zeroes are an integral part of the design.
+ // If the hour is single digit the first character of the string will be
+ // space, in which case a zero is added. As there is no space in the font,
+ // spaces are ignored.
+ if (hh.substr(0,1) == ' ') {
+ hh = '0' + hh;
+ }
+ var mm = timeStr.substr(-3);
+ var dayName = require("locale").dow(new Date(), 1);
+ var longDateStr = date.getDate() + ' ' + require("locale").month(new Date(), 1);
+ // draw time
+ g.setFont("BDGemini");
+ g.clearRect(0,24,175,175); // clear the background
+ g.drawString(hh,0,24);
+ g.drawString(mm,49,98);
+ // draw date
+ g.setFont("6x8",2);
+ g.drawString(dayName,106,35);
+ g.drawString(longDateStr,106,51);
+ g.drawString(date.getFullYear(),106,67);
+ // queue draw in one minte
+ queueDraw();
+}
+
+// Clear the screen once, at startup
+g.clear();
+// draw immediately at first, queue update
+draw();
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+});
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+// Load widgets
+Bangle.loadWidgets();
+Bangle.drawWidgets();
\ No newline at end of file
diff --git a/apps/geminiclock/metadata.json b/apps/geminiclock/metadata.json
new file mode 100644
index 000000000..cf081ed79
--- /dev/null
+++ b/apps/geminiclock/metadata.json
@@ -0,0 +1,17 @@
+{ "id": "geminiclock",
+ "name": "Gemini clock",
+ "shortName":"Gemini Clock",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "Watch face using retro Gemini font",
+ "type": "clock",
+ "tags": "clock",
+ "supports": ["BANGLEJS2"],
+ "screenshots": [{"url":"screenshot.png"}],
+ "readme": "README.md",
+ "allow_emulator": true,
+ "storage": [
+ {"name":"geminiclock.app.js","url":"gemini-watch-app.js"},
+ {"name":"geminiclock.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/geminiclock/screenshot.png b/apps/geminiclock/screenshot.png
new file mode 100644
index 000000000..44e882f7d
Binary files /dev/null and b/apps/geminiclock/screenshot.png differ
diff --git a/apps/geminiclock/watch-in-use.jpg b/apps/geminiclock/watch-in-use.jpg
new file mode 100644
index 000000000..825a2b120
Binary files /dev/null and b/apps/geminiclock/watch-in-use.jpg differ
diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog
index 3b0d62009..f913c9e58 100644
--- a/apps/gipy/ChangeLog
+++ b/apps/gipy/ChangeLog
@@ -63,3 +63,13 @@
* Record traveled distance to get a good average speed.
* Breaks (low speed) will not count in average speed.
* Bugfix in average speed.
+
+0.16:
+ * When lost indicates nearest point on path.
+ * Rescale display if lost and too far.
+ * New setting to hide points and increase display speed.
+ * Speed optimisations.
+ * Estimated time of Arrival/Going back.
+ * Display current and next segment in red so that you know where to go.
+ * Avoid angles flickering at low speed at the cost of less refresh.
+ * Splash screen while waiting for gps signal.
diff --git a/apps/gipy/README.md b/apps/gipy/README.md
index 6c9b87c23..4ca98dea8 100644
--- a/apps/gipy/README.md
+++ b/apps/gipy/README.md
@@ -2,12 +2,10 @@
Gipy allows you to follow gpx traces on your watch.
-
+
-It is for now meant for bicycling and not hiking
-(it uses your movement to figure out your orientation
-and walking is too slow).
+It is mainly meant for bicycling but hiking might be fine.
It is untested on Banglejs1. If you can try it, you would be welcome.
@@ -20,10 +18,10 @@ It provides the following features :
- display the path with current position from gps
- detects and buzzes if you leave the path
- buzzes before sharp turns
-- buzzes before nodes with comments
+- buzzes before waypoints
(for example when you need to turn in https://mapstogpx.com/)
- display instant / average speed
-- display distance to next node
+- display distance to next point
- display additional data from openstreetmap :
- water points
- toilets
@@ -54,32 +52,47 @@ Your path will be displayed in svg.
### Starting Gipy
-Once you start gipy you will have a menu for selecting your trace (if more than one).
-Choose the one you want and here you go :
+At start you will have a menu for selecting your trace (if more than one).
+Choose the one you want and you will reach the splash screen where you'll wait for the gps signal.
+Once you have a signal you will reach the main screen:
-
+
-On your screen you can see :
+On your screen you can see:
- yourself (the big black dot)
- the path (the top of the screen is in front of you)
+- on the path, current and next segments are red and other ones are black
- if needed a projection of yourself on the path (small black dot)
-- extremities of segments as white dots
-- turning points as doubled white dots
-- some text on the left (from top to bottom) :
+- points as white dots
+- waypoints as doubled white dots
+- some text on the left (from top to bottom):
+ * time to reach start point at current average speed
* current time
+ * time to reach end point at current average speed
* left distance till end of current segment
- * distance from start of path / path length
+ * remaining distance / path length
* average speed / instant speed
- interest points from openstreetmap as color dots :
- * red : bakery
- * deep blue : water point
- * cyan : toilets (often doubles as water point)
- * green : artwork
+ * red: bakery
+ * deep blue: water point
+ * cyan: toilets (often doubles as water point)
+ * green: artwork
- a *turn* indicator on the top right when you reach a turning point
- a *gps* indicator (blinking) on the top right if you lose gps signal
- a *lost* indicator on the top right if you stray too far away from path
-- a black segment extending from you when you are lost, indicating the rough direction of where to go
+
+### Lost
+
+If you stray away from path we will rescale the display to continue displaying nearby segments and
+display the direction to follow as a black segment.
+
+Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you
+are. On path it just needed to scan a few points ahead and behind.
+
+
+
+The distance to next point displayed corresponds to the length of the black segment.
### Settings
@@ -87,6 +100,7 @@ Few settings for now (feel free to suggest me more) :
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
- max speed : used to compute how long to turn the gps off
+- display points : display/hide points (not waypoints)
### Caveats
diff --git a/apps/gipy/TODO b/apps/gipy/TODO
index 53c3530e2..266a1c5c9 100644
--- a/apps/gipy/TODO
+++ b/apps/gipy/TODO
@@ -1,10 +1,5 @@
-* bugs
-
-- when exactly on turn, distance to next point is still often 50m
- -----> it does not buzz very often on turns
-
-- when going backwards we have a tendencing to get a wrong current_segment
++ use Bangle.project(latlong)
* additional features
@@ -15,7 +10,6 @@
(and look at more than next point)
- display distance to next water/toilet ?
-- dynamic map rescale
- display scale (100m)
- compress path ?
diff --git a/apps/gipy/app.js b/apps/gipy/app.js
index ae82e5dfb..c9d018cac 100644
--- a/apps/gipy/app.js
+++ b/apps/gipy/app.js
@@ -6,10 +6,29 @@ var settings = Object.assign(
{
keep_gps_alive: true,
max_speed: 35,
+ display_points: true,
},
require("Storage").readJSON("gipy.json", true) || {}
);
+let profile_start_times = [];
+
+let splashscreen = require("heatshrink").decompress(
+ atob(
+ "2Gwgdly1ZATttAQfZARm2AQXbAREsyXJARmyAQXLAViDgARm2AQVbAR0kyVJAQ2yAQVLARZfBAQSD/ARXZAQVtARnbAQe27aAE5ICClgCMLgICCQEQCCkqDnARb+BAQW2AQyDEARdLAQeyAR3LAQSDXL51v+x9bfAICC7ICM23ZPpD4BAQXJn//7IFCAQ2yAQR6YQZOSQZpBBsiDZARm2AQVbAQSDIAQt///btufTAOyBYL+DARJrBAQSDWLJvvQYNlz/7tiAeEYICBtoCHQZ/+7ds//7tu2pMsyXJlmOnAFDyRoBAQSAWAQUlyVZAQxcBAQX//3ZsjIBWYUtBYN8uPHjqMeAQVbQZ/2QYXbQYNbQwRNBnHjyVLkhNBARvLAQSDLIgNJKZf/+1ZsjIBlmzQwXPjlwg8cux9YtoCD7ICCQZ192yDBIINt2f7tuSvED/0AgeOhMsyXJAQeyAQR6MARElyT+BAQ9lIIL+CsqDF21Ajlx4EAuPBQa4CIQZ0EQYNnAQNt2QCByU48f+nEAh05kuyC4L+DARJ3BAQSDJsmWpICEfwJQEkESoNl2wXByaDB2PAQYPHgEB4cgEYKDc7KDOkmAgMkyCABy3bsuegHjx/4QYM4sk27d/+XJlmSAQpcBAQSAKAQQ1BZAVZkoCHBYNIgEApMgEwcHQYUcgPHEYVv+SDaGQSDNAQZDByUbDQM48eOn/ggCDB23bIIICB/1LC4ICB2QCLPoICEfwNJARA1BAQZEDgEJkkyQAKDB/gCBQYUt+ACB/yDsAQVA8ESrKDC//+nIjB7dt/0bQYNJlmS5ICG2QCCcwQCGGQslAQdZAQ4RDQAPJQYUf//DGQKAB31LQYKeCQbmT//8QZlIQAM4QYkZQYe+raDCC4eyAQVLARaDBAoL4CAQNkz///4FCAQxWCp8AQAKDCjlwU4OCQYcv3yDfIAP/+SDM8EOQYOPCgOAhFl2CDB20bQwIUCfwICMLgICC2XLGQsnIISnDKAVZkoCDpKADAQUSoARBhcs2/Dlm2QbEEiFJggvBeAIAC5KDKpKDF8AIBgEAhMkw3LQYgCIfYICC2QCHCgl/IIf5smWpICIniDELgQdBoEAgVJkqDboMkiVBIAYABQZcjxyDB//4Bw2QRAIIEfAICC5ICM2XJkGSUgIXBIIvkEwklAQdZkiDD4IOBrILDC4UAQbYCBo5BF/iDKkiDB//+LgYCY2QCCpYCCkGCpEkwVPIIv/fwMkAQNkAQuRQYNwBAVZAQRoCRgSDcv5BG+RlLvHjQDHJAQUsAQ6DBhACBn5BG/wpOrMlARZuBAQSDRgEQgMAiJAGAAPJgmQpMEfbQCSpaDDx5BJCgVkAQWWARhoBAQR9SQY0AoEEv5BI/MkiVBPs0sAQfJAQUAQYQ5Bj4CB/hHEExz+BAQT+BARVlAQSDPAAKDJ/8EiFBAQeQQ0gCFkECgEj//HQYUcuPHIIXkwQaHfYICCsgCMrICCQByDFHwQAI/iDFiVBkkSQc3JIIfx46ACAQ1yhEgyUJAQImOrICCkoCLPQICCQZCCKAAXBQYYCFyFJgiGiIIX8QBACD4EgwVIkmCDo1kAQWWARh0BAQR9GQY8H8aDM/CDJiVBkkSQccHQBQCDgGChCGBAQOShImLfYICFfwICKsoCCQYcAQRn+n/8iEBgCGIAQWQQbtPQaMcuSDEwVIkmCEw77BAQVkARlZAQSACAQN/IIM/8f+nCCI8f//H/x0AgkAoCDJiVBkkSQbOT/8AgKANAQiDEAQsJkA1PrICCkoCIz5BBhyDBxyDJAAYOB/iZBAAMBgCGIAQdJgiDUFwKDUjkCQZEIkmCpApCsgCFywCLv9lAoNl//HQYk/P5Hjx4GE+CEDgkAoCDKoMkiQCBPpeT//8AoMnQYSARAQVwH4OAQxMgyUJAQQ7IfwICCrMlz48B+VZngsBgeP/CAIAAaDB8YGD/CEDAAMDMQUQgKJJyFJAQRKGEYK8BhIqCQCQCEgECgEggUIEAX8QwkkwVIHAz7BAQVkAQN/+KqCg4pCOIKDN/0/QwQADwCCCBYIRDoEEgCDHAQMkiQCBJQiABnHggE4VoSDXAQPAgEPKoyDCAQkJkCGFAQdPEYcBFIaAMABsDBA/8gEBgEQgKGIAQNJgmSnCDDhwFDQbICBv5MI5CGFkmCpCACsgCCyImJfAYAOCIPjBA4TI8kAoCDKoMnPQJ9CgeAAQKDdAQMfHgXxBYl+QYYCEhMgyUJngRBgAAHf6R6Cx4FCnALDxyGC/BuCAQVAFoUQgKDEoARF8EOgACBiSDdjlwg4LIpMkhSGHo8cQJEkyRuDABxcBQwaDBMoIFCEYMONwY+BnFL12SoEgoEEgCDCCIfjwE4gYCBhMk2SDeuPAIQKGDFIOSIgICCyCDDwPAQY8SCgXjQaL4FAowAB+EAgYIB9cu3Xrlmy5JECGwIOCDQYCC0gOBCgKAbuB9DAQUAgPHQAgCEkUHP4wABTAplDABaSDPogCDEgMOQwX6r/+QYJrB5csySDCpaAIx06pYUEQbUAAQQABBAPSpF145uFAQOXjkB4ACCC4VIgCVGQYf+n7+FAgYLFMonghyrEh0SpeuyVIkmypEgF4MuQBE49IRB9euQYWyQbUcdw0HNYoCCpFwg8AAQYVDSo6DDKAKDLnAFF8EAfYOAgHj1gjBRIPjlxrDGQOQQBACBnVLl269esQbhrBhMh4BoEw8dNwslDQvAjkBAQKAHQYn4QZHjx4EBL4IJCMokA9ck3ED1xoBlmS8LyB5MgRgSAIAQOkPoIaD2VLlmCQbF0L4ZrLrgUBgCYBAQYABTYgCGPQwAELgX//xfBAQRlCxmS9euyTsCdISABAQKPBQBOOnVJCgKDCC4cgQbEAMpQCDkoaHgPAjkEDRj4C8aGCQY4CGwm48EEMoOscwQFBAQNIkApBhyAInCABTwSbB1waCAoMk2SDVuj1BAQJoLrgXFuEHgFwgUJTxpWDfASADn5iFgYCBgEO2XpLgPL0mSMQOSF4UIkmQTxOOiCYCQYIdBAQUuQYILBPprjBAoMAAQUAMplJkojKuAaNQYoCCQY47BnHgeQPggG69aDENwOChEgwUJCIKDKTAKDCAQKDC5Ms3XIkCDFPQYCE4VcIQIABi8cMptIU5UADRqDHgHj/xiG9JBDiXj0hlB1hrB0mCEAKABkmQDQihDAQQyCPQOyTYIdB1iGBBANIAQMcgLaCgBiIKwtdMpmHDpApBQB4CCeoXhh0QQY+Q9ek3Xr1z+BcYLsDQYKABEYIgBDQYgE9eOiQXCAQI4DQwIIBkmyhYLBgBZBjpZBL4clMQhlQpCAIAQMJQacAgiDBl26L4M6fYO4AoJ3BxgCB126pekL4fJkGChEgyT+FAQvpF4PJOgKDBwR6BUgYCCBwOygB6BVQR9BgVckmXjkAMSIUBQZPSQCKDDl04eoKDDoeu3DmBfYRZBSQLpCQYIdBQYJcBPomP/AFDwm4fYXJkmCpACBHAOy5CPCBAMJCIMJkPCI4VcuESeQcBMqCAJAQNwQCQCCheunT4CoeAiXr1m69MAmSDDcAlLL4MIkGSpb+E8f+AoihBVoXLCgL7C9csDodJAoMLQYZ3DrkAKAkgRIYCLQBICCuiDWPQKDCcYL4BBAaJCBAMsLgWShKDCkmQPQgCG8L7B5aDDAoaDBTwKJC1ytDI4tIL4qPEARMlQBVxDRoCKbQXol2y9JxBpaDBKASJB2TmBQAkgwVJhx9Ex/4QYkQDoVLF4IjFQAXIkizCFgSDGASlcQBICBuAmYpcuJQICCcYRZBL4YIB5MgQYKABQYOSfwvj/wFD8MAPoIgEhICB5L4FQYQRBRIKDaw6AJAQMBVTLRCJQSDCAoTpDPoKDCQAOCDQKAEAQ8LlhxCyRxChCnCliPB1wOBEYI7C5ACBQbCAKjdtwCqZQYZTDAoSDBBYtJLgKDBC4J9F//4AoXbtuwpcuOgIdBfYL4DEwOS9aDBFIOC5ckAQMuQbCAIAQPG7VtmiDbkGy5IFB5KGDAQYIChKDCkm4fwv/Aoc27dp01L0gmCwXr1gjDDoIFB1ytBBwIRCBARZVkqAIAQX2YoMwQbbdB5L1BhJZBboR9BAoSABQYNJhyADAQ2P2xBBw9LPoNIC4KDBOIIvB5B6CAoICBEwIFB9aDWriAJAQRBCnCDgbQJQCwUJlzdCBYWQPov//yDFYoXHof8EwRxBFgJ3CEYOC5KwBQYVLl26SoZWSw6AKAQMB/5KCjsEQbICBLgO65JWBhJWBpbUEd4J6Ex0//6JEoel4BCB48IDoPrkiGBAQa2CWASDBBAQvBSoZWRQBYCBpMF/8DI4NAQCyDEwT4BZwJTBBYJQBl2ShIOBhZ6EfwP/RIk68eBQQKDBgKDCeoPIFgYpBBYIFCQYXLQAPr1iDSQBYCB6VIurFB/04pf0QbFJkGChMsQYOucwRTCBwW4PQgCB//4BAkQYoUcv/CpMMEAOu3QgBwVIF4QpCAoPJAoICB2SGCKB8lQBaDDKYOS/+kWwaDZJQLOCcYLRByVLcAUOQAmPQAoCCEAME3UJZANBDQPJlxxD5AvBQZFIQadIQBgCBF4NIkrCBkkSQDCDE5ZKB9YCBRIJcBLIMDPQv/QY+uPQMEiVBgmyhBrCAQIpBU4R0DPQOCBwY7BBwIIBKBqAMkoCBCgeQpApBQb5oBAQSDBhEg3B6F//+QAmEyCDBTYWyfAL+BFIQgBF4SDCQAIFE126QYQUBQZp0CQZd0y4UCpB9aAQihCKYSJCFIOChEuPQmOn//RIiDB3VJlz+CTYRxBJRCDF1g1B1myRIOCTwKDMpCALQYYUEQcACBdISDBwSMBwVDPQuP/6JEQYfrdgIjC5CDD2QFBF4Wy5ICDQYOu2XrQYKPBQYI1BJpaAMAQVwQchWCAoZKBdgO4PQwCJPQMu3RxCPoyqB5YCCFgeyQYKeBBYNIQZ0lQBoCCuiDkLIRlCJQUIhyAOnHpDoRuBfAZoCQAosEpAUBBAKDB1iDBBYNLkiDJpCAOAQMJPr4CFJoLXCyUIMoMDQBoCB3FL1gdBNwPrEYSGCQAQFDBYaDDAoKPCQYcsQZKAOjskw6AjAQREBQYuAPQ3//AIFoeu3VLAQSDCRIQmB9ekFgSDBGQe6PQKABGQIOCAQQ+DJQ2HQZvXQEwCDIgMJkGCQYL+G//+BAs6QAL1C3TvDQYJoCRIOCpYsBhYIBpEuCga2BfwdLBYUsRIRHEkKALAQXCrqDuhaAEAQM//4IGQYW6QYKABQYQFBQYXLSQMLkgmBBAMIO4UgGoICCQYQjBQZFcQBgCDQE4CBhJWCQYJ3EAQOP/4IGAQKbBL4RlBeQQCCQYR6B9esR4fIBANLQAeCDQOShaDJy6AOQY+CMQaDgAQKDB3CDQiXJO4PJEARiBQwQICNYKDDpYOBC4IRDBAIRCQYYaBQYklQB6DFpCDBQAazDATcIEwICBfY3j//4QY86MQSDDfwREDwXLNYPrPoQUBQASPD1wLDQZMhQaEgwCDEMoiDfpBfBhMOQY3//yMHeQIdDdgZuBPQILBwRrCQwQCB3SDCpcuBAJ9BDQKGCAQJEFQBwCBjt0PRkJQbkIQYMDfYwCJ8JcBcAaDBQARrCQYYICQYnrTwPLQYKGBTYYaCCIOCIgSAOQYbdDQdSAO8eunFBPoKDByTmBQYOkRgIFBEwSDC5MgBYR6B1x3BAQQIBQAXIEASDDy6DPkmHpAXDTwZlGQb24QZ+kyFLOgSDD2RiBPoYmCKYL1DBYSACpcufwQCBSQKDD1hoCw6DPkvXLgiDpPQ3//yDIdgJcBfwVL0h3CyRuCFIiDDAQSYCUIJ9BCIMLQYwaBkqANAQV16S2EMQqJDBY6DWlx6Fn//QAoCCwkyQYJ3BlxfB0iACQZCVDfwYFBpJ9CBwMJRIQRC1gdBQBwCCuAvDO4cgQYgFBQbsLO4uP/6AGAQPhhxWBQYe6QAXJEw4LDOIRNBQYXIQYMIQYYIBBYNLFINIQaEJQYIdCHAaDCAQqDcgZ6F/6DJpYyCLgPrkm6EAiMBQY5TGfwSDB5AOEboaDBQByDDkESQYogCEYYCfO4qCB/CDI8ckiVLC4KDBPoQCBMQPr0gLB1jvCFgcIkGCKYOy5YLBQYQUCQa3CQASDIQECDHn///yAHx069ZWBOIXL1zyDBYO65esAoICBhIUBNwKDCQAKDEDQYgDQbB6jQZ6AGQYfBQYZoBl265JuCkm6PQQFBwUIBYPJBAKJC5MgBwKDCRgKDBSoWCCISDQ6VBL5AsBAoVIQceP/6DKiR6CO4QaBQYQjGQYRHBPoILDQYWCRgVIQYNL126RgOyeQOCQZ50EC4OSWwImCQwaDkQQKAHAQOEEaR9BQYTRGKwOCpaDBhCDBR4SDCBwSDPuAmCwSDCAQQ1DQwSDiQQKDKx0SFjSDFBASDCcwQRDBwIA="
+ )
+);
+
+function start_profiling() {
+ profile_start_times.push(getTime());
+}
+
+function end_profiling(label) {
+ let end_time = getTime();
+ let elapsed = end_time - profile_start_times.pop();
+ console.log("profile:", label, "took", elapsed);
+}
+
let interests_colors = [
0xf800, // Bakery, red
0x001f, // DrinkingWater, blue
@@ -29,9 +48,29 @@ function binary_search(array, x) {
return start;
}
+// return a string containing estimated time of arrival.
+// speed is in km/h
+// remaining distance in km
+// hour, minutes is current time
+function compute_eta(hour, minutes, approximate_speed, remaining_distance) {
+ if (isNaN(approximate_speed) || approximate_speed < 0.1) {
+ return "";
+ }
+ let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes
+ let eta_in_minutes = hour * 60 + minutes + time_needed;
+ let eta_minutes = Math.round(eta_in_minutes % 60);
+ let eta_hour = Math.round((eta_in_minutes - eta_minutes) / 60) % 24;
+ if (eta_minutes < 10) {
+ return eta_hour.toString() + ":0" + eta_minutes;
+ } else {
+ return eta_hour.toString() + ":" + eta_minutes;
+ }
+}
+
class Status {
constructor(path) {
this.path = path;
+ this.scale_factor = 40000.0; // multiply geo coordinates by this to get pixels coordinates
this.on_path = false; // are we on the path or lost ?
this.position = null; // where we are
this.adjusted_cos_direction = null; // cos of where we look at
@@ -39,8 +78,7 @@ class Status {
this.current_segment = null; // which segment is closest
this.reaching = null; // which waypoint are we reaching ?
this.distance_to_next_point = null; // how far are we from next point ?
- this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
- this.paused_since = getTime();
+ this.projected_point = null;
let r = [0];
// let's do a reversed prefix computations on all distances:
@@ -54,67 +92,51 @@ class Status {
previous_point = point;
}
this.remaining_distances = r; // how much distance remains at start of each segment
- this.starting_time = this.paused_since; // time we start
+ this.starting_time = null; // time we start
this.advanced_distance = 0.0;
this.gps_coordinates_counter = 0; // how many coordinates did we receive
- this.old_points = [];
- this.old_times = [];
+ this.old_points = []; // record previous points but only when enough distance between them
+ this.old_times = []; // the corresponding times
}
new_position_reached(position) {
// we try to figure out direction by looking at previous points
// instead of the gps course which is not very nice.
- this.gps_coordinates_counter += 1;
+
let now = getTime();
+
+ if (this.old_points.length == 0) {
+ this.gps_coordinates_counter += 1;
+ this.old_points.push(position);
+ this.old_times.push(now);
+ return null;
+ } else {
+ let previous_point = this.old_points[this.old_points.length - 1];
+ let distance_to_previous = previous_point.distance(position);
+ // gps signal is noisy but rarely above 4 meters
+ if (distance_to_previous < 4) {
+ return null;
+ }
+ }
+ this.gps_coordinates_counter += 1;
this.old_points.push(position);
this.old_times.push(now);
- if (this.old_points.length == 1) {
- return null;
- }
-
- let last_point = this.old_points[this.old_points.length - 1];
let oldest_point = this.old_points[0];
+ let distance_to_oldest = oldest_point.distance(position);
- // every 7 points we count the distance
- if (this.gps_coordinates_counter % 7 == 0) {
- let distance = last_point.distance(oldest_point);
- if (distance < 150.0) {
+ // every 3 points we count the distance
+ if (this.gps_coordinates_counter % 3 == 0) {
+ if (distance_to_oldest < 150.0) {
// to avoid gps glitches
- this.advanced_distance += distance;
+ this.advanced_distance += distance_to_oldest;
}
}
- if (this.old_points.length == 8) {
- let p1 = this.old_points[0]
- .plus(this.old_points[1])
- .plus(this.old_points[2])
- .plus(this.old_points[3])
- .times(1 / 4);
- let p2 = this.old_points[4]
- .plus(this.old_points[5])
- .plus(this.old_points[6])
- .plus(this.old_points[7])
- .times(1 / 4);
- let t1 = (this.old_times[1] + this.old_times[2]) / 2;
- let t2 = (this.old_times[5] + this.old_times[6]) / 2;
- this.instant_speed = p1.distance(p2) / (t2 - t1);
+ this.instant_speed = distance_to_oldest / (now - this.old_times[0]);
+
+ if (this.old_points.length == 4) {
this.old_points.shift();
this.old_times.shift();
- } else {
- this.instant_speed =
- oldest_point.distance(last_point) / (now - this.old_times[0]);
-
- // update paused time if we are too slow
- if (this.instant_speed < 2) {
- if (this.paused_since === null) {
- this.paused_since = now;
- }
- } else {
- if (this.paused_since !== null) {
- this.paused_time += now - this.paused_since;
- this.paused_since = null;
- }
- }
}
// let's just take angle of segment between newest point and a point a bit before
let previous_index = this.old_points.length - 3;
@@ -153,6 +175,7 @@ class Status {
let next_segment = res[1];
if (this.is_lost(next_segment)) {
+ // start_profiling();
// it did not work, try anywhere
res = this.path.nearest_segment(
this.position,
@@ -163,6 +186,7 @@ class Status {
);
orientation = res[0];
next_segment = res[1];
+ // end_profiling("repositioning");
}
// now check if we strayed away from path or back to it
let lost = this.is_lost(next_segment);
@@ -223,16 +247,30 @@ class Status {
return this.remaining_distances[0] - remaining_in_correct_orientation;
}
}
+ // check if we are lost (too far from segment we think we are on)
+ // if we are adjust scale so that path will still be displayed.
+ // we do the scale adjustment here to avoid recomputations later on.
is_lost(segment) {
- let distance_to_nearest = this.position.distance_to_segment(
+ let projection = this.position.closest_segment_point(
this.path.point(segment),
this.path.point(segment + 1)
);
- return distance_to_nearest > 50;
+ this.projected_point = projection; // save this info for display
+ let distance_to_projection = this.position.distance(projection);
+ if (distance_to_projection > 50) {
+ this.scale_factor =
+ Math.min(88.0 / distance_to_projection, 1.0) * 40000.0;
+ return true;
+ } else {
+ this.scale_factor = 40000.0;
+ return false;
+ }
}
display(orientation) {
g.clear();
+ // start_profiling();
this.display_map();
+ // end_profiling("display_map");
this.display_interest_points();
this.display_stats(orientation);
@@ -265,14 +303,17 @@ class Status {
let c = interest_point.coordinates(
this.position,
this.adjusted_cos_direction,
- this.adjusted_sin_direction
+ this.adjusted_sin_direction,
+ this.scale_factor
);
g.setColor(color).fillCircle(c[0], c[1], 5);
}
}
display_stats(orientation) {
- let remaining_distance = this.remaining_distance(orientation);
- let rounded_distance = Math.round(remaining_distance / 100) / 10;
+ let remaining_forward_distance = this.remaining_distance(0);
+ let remaining_backward_distance = this.remaining_distance(1);
+ let rounded_forward_distance =
+ Math.round(remaining_forward_distance / 100) / 10;
let total = Math.round(this.remaining_distances[0] / 100) / 10;
let now = new Date();
let minutes = now.getMinutes().toString();
@@ -280,24 +321,53 @@ class Status {
minutes = "0" + minutes;
}
let hours = now.getHours().toString();
+ // now, distance to next point in meters
g.setFont("6x8:2")
- .setFontAlign(-1, -1, 0)
.setColor(g.theme.fg)
- .drawString(hours + ":" + minutes, 0, 30);
-
- g.setFont("6x8:2").drawString(
- "" + this.distance_to_next_point + "m",
- 0,
- g.getHeight() - 49
- );
+ .drawString(
+ "" + this.distance_to_next_point + "m",
+ 0,
+ g.getHeight() - 49
+ );
let point_time = this.old_times[this.old_times.length - 1];
- let done_in = point_time - this.starting_time - this.paused_time;
+ let done_in = point_time - this.starting_time;
let approximate_speed = Math.round(
(this.advanced_distance * 3.6) / done_in
);
- let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
+ let forward_eta = compute_eta(
+ now.getHours(),
+ now.getMinutes(),
+ approximate_speed,
+ remaining_forward_distance / 1000
+ );
+
+ let backward_eta = compute_eta(
+ now.getHours(),
+ now.getMinutes(),
+ approximate_speed,
+ remaining_backward_distance / 1000
+ );
+
+ // display backward ETA
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .setColor(g.theme.fg)
+ .drawString(backward_eta, 0, 30);
+ // display the clock
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .setColor(g.theme.fg)
+ .drawString(hours + ":" + minutes, 0, 48);
+ // now display ETA
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .setColor(g.theme.fg)
+ .drawString(forward_eta, 0, 66);
+
+ // display speed (avg and instant)
+ let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
g.setFont("6x8:2")
.setFontAlign(-1, -1, 0)
.drawString(
@@ -306,12 +376,14 @@ class Status {
g.getHeight() - 15
);
+ // display distance on path
g.setFont("6x8:2").drawString(
- "" + rounded_distance + "/" + total,
+ "" + rounded_forward_distance + "/" + total,
0,
g.getHeight() - 32
);
+ // display various indicators
if (this.distance_to_next_point <= 100) {
if (this.path.is_waypoint(this.reaching)) {
g.setColor(0.0, 1.0, 0.0)
@@ -343,19 +415,44 @@ class Status {
let half_height = g.getHeight() / 2;
let previous_x = null;
let previous_y = null;
+ let scale_factor = this.scale_factor;
+
+ // display direction to next point if lost
+ if (!this.on_path) {
+ let next_point = this.path.point(this.current_segment + 1);
+ let previous_point = this.path.point(this.current_segment);
+ let nearest_point;
+ if (
+ previous_point.fake_distance(this.position) <
+ next_point.fake_distance(this.position)
+ ) {
+ nearest_point = previous_point;
+ } else {
+ nearest_point = next_point;
+ }
+ let tx = (nearest_point.lon - cx) * scale_factor;
+ let ty = (nearest_point.lat - cy) * scale_factor;
+ let rotated_x = tx * cos - ty * sin;
+ let rotated_y = tx * sin + ty * cos;
+ let x = half_width - Math.round(rotated_x); // x is inverted
+ let y = half_height + Math.round(rotated_y);
+ g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
+ }
+
+ // now display path
for (let i = start; i < end; i++) {
- let tx = (points[2 * i] - cx) * 40000.0;
- let ty = (points[2 * i + 1] - cy) * 40000.0;
+ let tx = (points[2 * i] - cx) * scale_factor;
+ let ty = (points[2 * i + 1] - cy) * scale_factor;
let rotated_x = tx * cos - ty * sin;
let rotated_y = tx * sin + ty * cos;
let x = half_width - Math.round(rotated_x); // x is inverted
let y = half_height + Math.round(rotated_y);
if (previous_x !== null) {
- if (i == this.current_segment + 1) {
- g.setColor(0.0, 1.0, 0.0);
- } else {
- g.setColor(1.0, 0.0, 0.0);
+ let segment_color = g.theme.fg;
+ if (i == this.current_segment + 1 || i == this.current_segment + 2) {
+ segment_color = 0xf800;
}
+ g.setColor(segment_color);
g.drawLine(previous_x, previous_y, x, y);
if (this.path.is_waypoint(i - 1)) {
@@ -364,10 +461,12 @@ class Status {
g.setColor(g.theme.bg);
g.fillCircle(previous_x, previous_y, 5);
}
- g.setColor(g.theme.fg);
- g.fillCircle(previous_x, previous_y, 4);
- g.setColor(g.theme.bg);
- g.fillCircle(previous_x, previous_y, 3);
+ if (settings.display_points) {
+ g.setColor(g.theme.fg);
+ g.fillCircle(previous_x, previous_y, 4);
+ g.setColor(g.theme.bg);
+ g.fillCircle(previous_x, previous_y, 3);
+ }
}
previous_x = x;
@@ -389,51 +488,21 @@ class Status {
g.setColor(g.theme.fgH);
g.fillCircle(half_width, half_height, 5);
- // display old points for direction debug
- // for (let i = 0; i < this.old_points.length; i++) {
- // let tx = (this.old_points[i].lon - cx) * 40000.0;
- // let ty = (this.old_points[i].lat - cy) * 40000.0;
- // let rotated_x = tx * cos - ty * sin;
- // let rotated_y = tx * sin + ty * cos;
- // let x = half_width - Math.round(rotated_x); // x is inverted
- // let y = half_height + Math.round(rotated_y);
- // g.setColor((i + 1) / 4.0, 0.0, 0.0);
- // g.fillCircle(x, y, 3);
- // }
-
- // display current-segment's projection for debug
- let projection = pos.closest_segment_point(
- this.path.point(this.current_segment),
- this.path.point(this.current_segment + 1)
- );
-
- let tx = (projection.lon - cx) * 40000.0;
- let ty = (projection.lat - cy) * 40000.0;
+ // display current-segment's projection
+ let tx = (this.projected_point.lon - cx) * scale_factor;
+ let ty = (this.projected_point.lat - cy) * scale_factor;
let rotated_x = tx * cos - ty * sin;
let rotated_y = tx * sin + ty * cos;
let x = half_width - Math.round(rotated_x); // x is inverted
let y = half_height + Math.round(rotated_y);
- g.setColor(g.theme.fg);
+ g.setColor(g.theme.fgH);
g.fillCircle(x, y, 4);
-
- // display direction to next point if lost
- if (!this.on_path) {
- let next_point = this.path.point(this.current_segment + 1);
- let diff = next_point.minus(this.position);
- let angle = Math.atan2(diff.lat, diff.lon);
- let tx = Math.cos(angle) * 50.0;
- let ty = Math.sin(angle) * 50.0;
- let rotated_x = tx * cos - ty * sin;
- let rotated_y = tx * sin + ty * cos;
- let x = half_width - Math.round(rotated_x); // x is inverted
- let y = half_height + Math.round(rotated_y);
- g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
- }
}
}
function load_gpc(filename) {
let buffer = require("Storage").readArrayBuffer(filename);
+ let file_size = buffer.length;
let offset = 0;
// header
@@ -475,7 +544,7 @@ function load_gpc(filename) {
let interests_starts = Uint16Array(buffer, offset, starts_length);
offset += 2 * starts_length;
- return [
+ let path_data = [
points,
waypoints,
interests_coordinates,
@@ -483,6 +552,18 @@ function load_gpc(filename) {
interests_on_path,
interests_starts,
];
+
+ // checksum file size
+ if (offset != file_size) {
+ console.log("invalid file size", file_size, "expected", offset);
+ let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset;
+ E.showAlert(msg).then(function () {
+ E.showAlert();
+ start_gipy(filename, path_data);
+ });
+ } else {
+ start_gipy(filename, path_data);
+ }
}
class Path {
@@ -502,20 +583,6 @@ class Path {
return r != 0;
}
- // execute op on all segments.
- // start is index of first wanted segment
- // end is 1 after index of last wanted segment
- on_segments(op, start, end) {
- let previous_point = null;
- for (let i = start; i < end + 1; i++) {
- let point = new Point(this.points[2 * i], this.points[2 * i + 1]);
- if (previous_point !== null) {
- op(previous_point, point, i);
- }
- previous_point = point;
- }
- }
-
// return point at given index
point(index) {
let lon = this.points[2 * index];
@@ -541,39 +608,28 @@ class Path {
// we are going to compute two min distances, one for each direction.
let indices = [0, 0];
let mins = [Number.MAX_VALUE, Number.MAX_VALUE];
- this.on_segments(
- function (p1, p2, i) {
- // we use the dot product to figure out if oriented correctly
- // let distance = point.fake_distance_to_segment(p1, p2);
- let projection = point.closest_segment_point(p1, p2);
- let distance = point.fake_distance(projection);
+ let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]);
+ for (let i = start + 1; i < end + 1; i++) {
+ let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]);
+
+ let closest_point = point.closest_segment_point(p1, p2);
+ let distance = point.length_squared(closest_point);
+
+ let dot =
+ cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat);
+ let orientation = +(dot < 0); // index 0 is good orientation
+ if (distance <= mins[orientation]) {
+ mins[orientation] = distance;
+ indices[orientation] = i - 1;
+ }
+
+ p1 = p2;
+ }
- // let d = projection.minus(point).times(40000.0);
- // let rotated_x = d.lon * acos - d.lat * asin;
- // let rotated_y = d.lon * asin + d.lat * acos;
- // let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted
- // let y = g.getHeight() / 2 + Math.round(rotated_y);
- //
- let diff = p2.minus(p1);
- let dot = cos_direction * diff.lon + sin_direction * diff.lat;
- let orientation = +(dot < 0); // index 0 is good orientation
- // g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle(
- // x,
- // y,
- // 10
- // );
- if (distance <= mins[orientation]) {
- mins[orientation] = distance;
- indices[orientation] = i - 1;
- }
- },
- start,
- end
- );
// by default correct orientation (0) wins
// but if other one is really closer, return other one
- if (mins[1] < mins[0] / 10.0) {
+ if (mins[1] < mins[0] / 100.0) {
return [1, indices[1]];
} else {
return [0, indices[0]];
@@ -589,8 +645,8 @@ class Point {
this.lon = lon;
this.lat = lat;
}
- coordinates(current_position, cos_direction, sin_direction) {
- let translated = this.minus(current_position).times(40000.0);
+ coordinates(current_position, cos_direction, sin_direction, scale_factor) {
+ let translated = this.minus(current_position).times(scale_factor);
let rotated_x =
translated.lon * cos_direction - translated.lat * sin_direction;
let rotated_y =
@@ -609,8 +665,9 @@ class Point {
return new Point(this.lon + other_point.lon, this.lat + other_point.lat);
}
length_squared(other_point) {
- let d = this.minus(other_point);
- return d.lon * d.lon + d.lat * d.lat;
+ let londiff = this.lon - other_point.lon;
+ let latdiff = this.lat - other_point.lat;
+ return londiff * londiff + latdiff * latdiff;
}
times(scalar) {
return new Point(this.lon * scalar, this.lat * scalar);
@@ -639,10 +696,15 @@ class Point {
fake_distance(other_point) {
return Math.sqrt(this.length_squared(other_point));
}
+ // return closest point from 'this' on [v,w] segment.
+ // since this function is critical we inline all code here.
closest_segment_point(v, w) {
// from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// Return minimum distance between line segment vw and point p
- let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt
+ let segment_londiff = w.lon - v.lon;
+ let segment_latdiff = w.lat - v.lat;
+ let l2 =
+ segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt
if (l2 == 0.0) {
return v; // v == w case
}
@@ -650,41 +712,48 @@ class Point {
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
// We clamp t from [0,1] to handle points outside the segment vw.
- let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2));
- return v.plus(w.minus(v).times(t)); // Projection falls on the segment
- }
- distance_to_segment(v, w) {
- let projection = this.closest_segment_point(v, w);
- return this.distance(projection);
- }
- fake_distance_to_segment(v, w) {
- let projection = this.closest_segment_point(v, w);
- return this.fake_distance(projection);
+
+ // let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below
+ let start_londiff = this.lon - v.lon;
+ let start_latdiff = this.lat - v.lat;
+ let t =
+ (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2;
+ if (t < 0) {
+ t = 0;
+ } else {
+ if (t > 1) {
+ t = 1;
+ }
+ }
+ let lon = v.lon + segment_londiff * t;
+ let lat = v.lat + segment_latdiff * t;
+ return new Point(lon, lat);
}
}
-Bangle.loadWidgets();
-
let fake_gps_point = 0.0;
function simulate_gps(status) {
+ // let's keep the screen on in simulations
+ Bangle.setLCDTimeout(0);
+ Bangle.setLCDPower(1);
if (fake_gps_point > status.path.len - 1) {
return;
}
let point_index = Math.floor(fake_gps_point);
- if (point_index >= status.path.len) {
+ if (point_index >= status.path.len / 2 - 1) {
return;
}
- //let p1 = status.path.point(0);
- //let n = status.path.len;
- //let p2 = status.path.point(n - 1);
- let p1 = status.path.point(point_index);
- let p2 = status.path.point(point_index + 1);
+ let p1 = status.path.point(2 * point_index); // use these to approximately follow path
+ let p2 = status.path.point(2 * (point_index + 1));
+ //let p1 = status.path.point(point_index); // use these to strictly follow path
+ //let p2 = status.path.point(point_index + 1);
let alpha = fake_gps_point - point_index;
let pos = p1.times(1 - alpha).plus(p2.times(alpha));
let old_pos = status.position;
fake_gps_point += 0.05; // advance simulation
+ // status.update_position(new Point(1, 1), null); // uncomment to be always lost
status.update_position(pos, null);
}
@@ -706,21 +775,28 @@ function start(fn) {
E.showMenu();
console.log("loading", fn);
- // let path = new Path(load_gpx("test.gpx"));
- let path = new Path(load_gpc(fn));
+ load_gpc(fn);
+}
+
+function start_gipy(filename, path_data) {
+ console.log("starting");
+ let path = new Path(path_data);
let status = new Status(path);
if (simulated) {
+ status.starting_time = getTime();
status.position = new Point(status.path.point(0));
setInterval(simulate_gps, 500, status);
} else {
- // let's display start while waiting for gps signal
- let p1 = status.path.point(0);
- let p2 = status.path.point(1);
- let diff = p2.minus(p1);
- let direction = Math.atan2(diff.lat, diff.lon);
+ // let's display splash screen while waiting for gps signal
+ g.clear();
+ g.drawImage(splashscreen, 0, 0);
+ g.setFont("6x8:2")
+ .setFontAlign(-1, -1, 0)
+ .setColor(0xf800)
+ .drawString(filename, 0, g.getHeight() - 30);
+
Bangle.setLocked(false);
- status.update_position(p1, direction);
let frame = 0;
let set_coordinates = function (data) {
@@ -731,6 +807,10 @@ function start(fn) {
!isNaN(data.lon) &&
(data.lat != 0.0 || data.lon != 0.0);
if (valid_coordinates) {
+ if (status.starting_time === null) {
+ status.starting_time = getTime();
+ Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen
+ }
status.update_position(new Point(data.lon, data.lat), null);
}
let gps_status_color;
diff --git a/apps/gipy/interface.html b/apps/gipy/interface.html
index a1c405ed7..552e7be17 100644
--- a/apps/gipy/interface.html
+++ b/apps/gipy/interface.html
@@ -182,12 +182,21 @@ document
document
.getElementById("upload")
.addEventListener('click', function() {
+ document.getElementById('upload').disabled = true;
status.innerHTML = "uploading file";
console.log("uploading");
let gpc_string = vec_to_string(gpc_content);
Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => {
- status.innerHTML = `${gpc_filename}.gpc uploaded`;
- console.log("DONE");
+ status.innerHTML = "Checking upload";
+ Util.readStorage(gpc_filename + ".gpc", uploaded_content => {
+ if (uploaded_content == gpc_string) {
+ status.innerHTML = `${gpc_filename}.gpc uploaded`;
+ console.log("DONE");
+ } else {
+ status.innerHTML = "Upload FAILED";
+ document.getElementById('upload').disabled = false;
+ }
+ });
});
});
diff --git a/apps/gipy/legend.png b/apps/gipy/legend.png
new file mode 100644
index 000000000..9040f6df8
Binary files /dev/null and b/apps/gipy/legend.png differ
diff --git a/apps/gipy/lost.png b/apps/gipy/lost.png
new file mode 100644
index 000000000..348eaed8e
Binary files /dev/null and b/apps/gipy/lost.png differ
diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json
index 2d06a7c2d..97d18f5fe 100644
--- a/apps/gipy/metadata.json
+++ b/apps/gipy/metadata.json
@@ -2,13 +2,13 @@
"id": "gipy",
"name": "Gipy",
"shortName": "Gipy",
- "version": "0.15",
- "description": "Follow gpx files",
+ "version": "0.16",
+ "description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.",
"allow_emulator":false,
"icon": "gipy.png",
"type": "app",
"tags": "tool,outdoors,gps",
- "screenshots": [],
+ "screenshots": [{"url":"splash.png"}],
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
diff --git a/apps/gipy/pkg/gpconv.d.ts b/apps/gipy/pkg/gpconv.d.ts
index ecffa7b69..2bb57f651 100644
--- a/apps/gipy/pkg/gpconv.d.ts
+++ b/apps/gipy/pkg/gpconv.d.ts
@@ -46,11 +46,11 @@ export interface InitOutput {
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
- readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void;
+ readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
- readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
+ readonly wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240: (a: number, b: number, c: number, d: number) => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
diff --git a/apps/gipy/pkg/gpconv.js b/apps/gipy/pkg/gpconv.js
index 97b37e340..b9271ad4b 100644
--- a/apps/gipy/pkg/gpconv.js
+++ b/apps/gipy/pkg/gpconv.js
@@ -98,14 +98,6 @@ function getInt32Memory0() {
return cachedInt32Memory0;
}
-const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
-
-cachedTextDecoder.decode();
-
-function getStringFromWasm0(ptr, len) {
- return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
-}
-
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
@@ -115,6 +107,14 @@ function addHeapObject(obj) {
return idx;
}
+const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+function getStringFromWasm0(ptr, len) {
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
function debugString(val) {
// primitive types
const type = typeof val;
@@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real;
}
function __wbg_adapter_24(arg0, arg1, arg2) {
- wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
+ wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(arg0, arg1, addHeapObject(arg2));
}
function _assertClass(instance, klass) {
@@ -310,7 +310,7 @@ function handleError(f, args) {
}
}
function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
- wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
+ wasm.wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
}
/**
@@ -371,13 +371,13 @@ async function load(module, imports) {
function getImports() {
const imports = {};
imports.wbg = {};
- imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
- takeObject(arg0);
- };
imports.wbg.__wbg_gpcsvg_new = function(arg0) {
const ret = GpcSvg.__wrap(arg0);
return addHeapObject(ret);
};
+ imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+ takeObject(arg0);
+ };
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
@@ -386,15 +386,15 @@ function getImports() {
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
- imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
- const ret = getStringFromWasm0(arg0, arg1);
- return addHeapObject(ret);
- };
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
const ret = getObject(arg0);
return addHeapObject(ret);
};
- imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
+ imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+ const ret = getStringFromWasm0(arg0, arg1);
+ return addHeapObject(ret);
+ };
+ imports.wbg.__wbg_fetch_3894579f6e2af3be = function(arg0) {
const ret = fetch(getObject(arg0));
return addHeapObject(ret);
};
@@ -558,10 +558,6 @@ function getImports() {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
- imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
- const ret = JSON.stringify(getObject(arg0));
- return addHeapObject(ret);
- }, arguments) };
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
@@ -574,6 +570,10 @@ function getImports() {
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments) };
+ imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
+ const ret = JSON.stringify(getObject(arg0));
+ return addHeapObject(ret);
+ }, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
const ret = debugString(getObject(arg1));
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -588,8 +588,8 @@ function getImports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
- imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
- const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
+ imports.wbg.__wbindgen_closure_wrapper929 = function(arg0, arg1, arg2) {
+ const ret = makeMutClosure(arg0, arg1, 143, __wbg_adapter_24);
return addHeapObject(ret);
};
diff --git a/apps/gipy/pkg/gpconv_bg.wasm b/apps/gipy/pkg/gpconv_bg.wasm
index edeb4eb59..245afc20c 100644
Binary files a/apps/gipy/pkg/gpconv_bg.wasm and b/apps/gipy/pkg/gpconv_bg.wasm differ
diff --git a/apps/gipy/pkg/gpconv_bg.wasm.d.ts b/apps/gipy/pkg/gpconv_bg.wasm.d.ts
index 6bc5d3719..cb912f3de 100644
--- a/apps/gipy/pkg/gpconv_bg.wasm.d.ts
+++ b/apps/gipy/pkg/gpconv_bg.wasm.d.ts
@@ -9,8 +9,8 @@ export function convert_gpx_strings(a: number, b: number, c: number, d: number,
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
-export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void;
+export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;
-export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void;
+export function wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(a: number, b: number, c: number, d: number): void;
diff --git a/apps/gipy/screenshot1.png b/apps/gipy/screenshot1.png
deleted file mode 100644
index c7c45fa3b..000000000
Binary files a/apps/gipy/screenshot1.png and /dev/null differ
diff --git a/apps/gipy/screenshot2.png b/apps/gipy/screenshot2.png
deleted file mode 100644
index ed61eb795..000000000
Binary files a/apps/gipy/screenshot2.png and /dev/null differ
diff --git a/apps/gipy/splash.png b/apps/gipy/splash.png
new file mode 100644
index 000000000..56d4de06b
Binary files /dev/null and b/apps/gipy/splash.png differ
diff --git a/apps/gpsinfo/ChangeLog b/apps/gpsinfo/ChangeLog
index 5bb531bc7..827c13cdb 100644
--- a/apps/gpsinfo/ChangeLog
+++ b/apps/gpsinfo/ChangeLog
@@ -7,3 +7,4 @@
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
0.09: Fix FIFO_FULL error
0.10: Show satellites "in view" separated by GNS-system
+0.11: Show number of packets received
diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js
index a6e21af0d..28cb60d8d 100644
--- a/apps/gpsinfo/gps-info.js
+++ b/apps/gpsinfo/gps-info.js
@@ -5,7 +5,7 @@ function satelliteImage() {
var Layout = require("Layout");
var layout;
//Bangle.setGPSPower(1, "app");
-E.showMessage(/*LANG*/"Loading..."); // avoid showing rubbish on screen
+E.showMessage(/*LANG*/"Waiting for GNS data..."); // avoid showing rubbish on screen
var lastFix = {
fix: -1,
@@ -19,6 +19,7 @@ var lastFix = {
var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1)
var listenerGPSraw = 0;
+var dataCounter = 0;
function formatTime(now) {
if (now == undefined) {
@@ -80,11 +81,15 @@ function onGPS(fix) {
type:"v", c: [
{type:"txt", font:"6x8:2", label:"GPS Info" },
{type:"img", src:satelliteImage, pad:4 },
- {type:"txt", font:"6x8", label:"Waiting for GPS" },
+ {type:"txt", font:"6x8", label:"Waiting for GPS fix" },
{type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
{type:"txt", font:"6x8", pad:3, label:"Satellites used" }
]},
+ {type:"h", c: [
+ {type:"txt", font:"10%", label:dataCounter, pad:2, id:"dataCounter" },
+ {type:"txt", font:"6x8", pad:3, label:"packets received" }
+ ]},
{type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
]},{lazy:false});
}
@@ -122,6 +127,9 @@ function onGPS(fix) {
layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
// console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
layout.render(layout.progress);
+ layout.clear(layout.dataCounter);
+ layout.dataCounter.label = ++dataCounter;
+ layout.render(layout.dataCounter);
}
}
diff --git a/apps/gpsinfo/metadata.json b/apps/gpsinfo/metadata.json
index 002febd86..e426f5740 100644
--- a/apps/gpsinfo/metadata.json
+++ b/apps/gpsinfo/metadata.json
@@ -1,11 +1,11 @@
{
"id": "gpsinfo",
"name": "GPS Info",
- "version": "0.10",
- "description": "An application that displays information about altitude, lat/lon, satellites and time",
+ "version": "0.11",
+ "description": "An application that displays information about latitude, longitude, altitude, speed, satellites and time",
"icon": "gps-info.png",
"type": "app",
- "tags": "gps,outdoors",
+ "tags": "gps,outdoors,tools",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"gpsinfo.app.js","url":"gps-info.js"},
diff --git a/apps/grocery/ChangeLog b/apps/grocery/ChangeLog
index 906046782..294dab597 100644
--- a/apps/grocery/ChangeLog
+++ b/apps/grocery/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Refactor code to store grocery list in separate file
+0.03: Sort selected items to bottom and enable Widgets
diff --git a/apps/grocery/app.js b/apps/grocery/app.js
index 481efc3d9..a68f53010 100644
--- a/apps/grocery/app.js
+++ b/apps/grocery/app.js
@@ -1,5 +1,6 @@
var filename = 'grocery_list.json';
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
+let menu;
function updateSettings() {
require("Storage").writeJSON(filename, settings);
@@ -11,19 +12,32 @@ function twoChat(n){
return ''+n;
}
-const mainMenu = settings.products.reduce(function(m, p, i){
- const name = twoChat(p.quantity)+' '+p.name;
- m[name] = {
- value: p.ok,
- format: v => v?'[x]':'[ ]',
- onchange: v => {
- settings.products[i].ok = v;
- updateSettings();
- }
- };
- return m;
-}, {
- '': { 'title': 'Grocery list' }
-});
+function sortMenu() {
+ mainMenu.sort((a,b) => {
+ const byValue = a.value-b.value;
+ return byValue !== 0 ? byValue : a.index-b.index;
+ });
+ if (menu) {
+ menu.draw();
+ }
+}
+
+const mainMenu = settings.products.map((p,i) => ({
+ title: twoChat(p.quantity)+' '+p.name,
+ value: p.ok,
+ format: v => v?'[x]':'[ ]',
+ index: i,
+ onchange: v => {
+ settings.products[i].ok = v;
+ updateSettings();
+ sortMenu();
+ }
+}));
+sortMenu();
+
+mainMenu[''] = { 'title': 'Grocery list' };
mainMenu['< Back'] = ()=>{load();};
-E.showMenu(mainMenu);
+
+Bangle.loadWidgets();
+menu = E.showMenu(mainMenu);
+Bangle.drawWidgets();
diff --git a/apps/grocery/interface.html b/apps/grocery/interface.html
new file mode 100644
index 000000000..65528c8e6
--- /dev/null
+++ b/apps/grocery/interface.html
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
List of products
+
+
+
+
name
+
quantity
+
done
+
actions
+
+
+
+
+
+
+
+
Add a new product
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/grocery/metadata.json b/apps/grocery/metadata.json
index 8c0e34dff..ef073a1b2 100644
--- a/apps/grocery/metadata.json
+++ b/apps/grocery/metadata.json
@@ -1,13 +1,14 @@
{
"id": "grocery",
"name": "Grocery",
- "version": "0.02",
+ "version": "0.03",
"description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
"icon": "grocery.png",
"type": "app",
"tags": "tool,outdoors,shopping,list",
"supports": ["BANGLEJS", "BANGLEJS2"],
"custom": "grocery.html",
+ "interface": "interface.html",
"allow_emulator": true,
"storage": [
{"name":"grocery.app.js","url":"app.js"},
diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog
index f9ca3c16d..2a911c207 100644
--- a/apps/ha/ChangeLog
+++ b/apps/ha/ChangeLog
@@ -3,4 +3,7 @@
0.03: Added clkinfo for clocks.
0.04: Feedback if clkinfo run is called.
0.05: Clkinfo improvements.
-0.06: Updated clkinfo icon.
\ No newline at end of file
+0.06: Updated clkinfo icon.
+0.07: Update clock_info to avoid a redraw
+0.08: Allow swiping to switch triggers
+0.09: Improve web interface, arrows in UI
diff --git a/apps/ha/README.md b/apps/ha/README.md
index 654a262c8..b0309b040 100644
--- a/apps/ha/README.md
+++ b/apps/ha/README.md
@@ -1,28 +1,30 @@
# Home Assistant
-This app integrates your BangleJs into the HomeAssistant.
+This app integrates your Bangle.js into the Home Assistant.
# How to use
-Click on the left and right side of the screen to select the triggers that you
-configured. Click in the middle of the screen to send the trigger to HomeAssistant.
+Click on the left or right side of the screen to select the triggers that you configured.
+Swiping left or right works as well.
+
+Click in the middle of the screen to send the trigger to Home Assistant via Gadgetbridge.

# Initial Setup
-1.) First of all, make sure that HomeAssistant and the HomeAssistant Android App works.
+1.) First of all, make sure that Home Assistant and the Home Assistant Android Companion App work.
-2.) Open your BangleJs Gadgetbridge App, click on the Settings icon of your BangleJs and enable "Allow Intent Access"
+2.) Open your Bangle.js Gadgetbridge App, click on the Settings icon of your Bangle.js and enable "Allow Intent Access"
-3.) Enable sensor in HomeAssistant Andoird App/Configuration/Companion App/Manage Sensors/LastUpdate Trigger
+3.) Enable sensor in Home Assistant Android App/Configuration/Companion App/Manage Sensors/LastUpdate Trigger
4.) At the bottom of the same screen click on "Add New Intent" and enter "com.espruino.gadgetbridge.banglejs.HA"
-5.) The HomeAssistant Android app must be restarted in order to listen for those actions
+5.) The Home Assistant Android app must be restarted in order to listen for those actions
-- a "Force Stop" is necessary (through Android App settings) or restart your phone!
This setup must be done only once -- now you are ready to configure your BangleJS to
-control some devices or entities in your HomeAssistant :)
+control some devices or entities in your Home Assistant :)
# Setup Trigger
@@ -35,7 +37,7 @@ The following icons are currently supported:
- fire
-2.) Create an "automation" in the HomeAssistant WebUI for each trigger that you created on your BangleJs in order to tell HomeAssistant what you want to control. A sample configuration is shown in the image below -- I use this trigger to open the door:
+2.) Create an "automation" in the Home Assistant WebUI for each trigger that you created on your Bangle.js in order to tell Home Assistant what you want to control. A sample configuration is shown in the image below -- I use this trigger to open the door:

@@ -48,7 +50,7 @@ This app also implements two default trigger that can always be used:
- TRIGGER -- Will be sent whenever some trigger is executed. So you could generically listen to that.
-# How to use the library (ha.lib.js) in my own app/clk
+# How to use the library (ha.lib.js) in my own app/clock
This app inlcludes a library that can be used by other apps or clocks
to read all configured intents or to send a trigger. Example code:
@@ -74,17 +76,20 @@ ha.sendTrigger("MY_CUSTOM_TRIGGER");
# FAQ
## Sometimes the trigger is not executed
-While playing and testing a bit I found that it is very important that you allow the android HomeAssistant app, as well as BangleJs Gadgetbridge app to (1) run in background and (2), disable energy optimizations for both apps.
-Otherwise, Android could stop one of both apps and the trigger will never be sent to HomeAssistant...
+While playing and testing a bit I found that it is very important that you allow the android Home Assistant app, as well as Bangle.js Gadgetbridge app to (1) run in background and (2), disable energy optimizations for both apps.
+Otherwise, Android could stop one of both apps and the trigger will never be sent to Home Assistant...
If you still have problems, you can try another trick:
-Install "MacroDroid" from the Android AppStore and start the HomeAssistant App
+Install "MacroDroid" from the Android AppStore and start the Home Assistant App
each time the "com.espruino.gadgetbridge.banglejs.HA" intent is send together
-with the extra trigger: APP_STARTED. Then whenever you open the app on your BangleJs
-it is ensured that HomeAssistant is running...
+with the extra trigger: APP_STARTED. Then whenever you open the app on your Bangle.js
+it is ensured that Home Assistant is running...
## Thanks to
Icons created by Flaticon
## Creator
- [David Peer](https://github.com/peerdavid).
+
+## Contributor
+- [myxor](https://github.com/myxor)
diff --git a/apps/ha/custom.html b/apps/ha/custom.html
deleted file mode 100644
index 49f5a2eb8..000000000
--- a/apps/ha/custom.html
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
Upload Tigger
-
-
-
-
-
-
-
-
diff --git a/apps/ha/ha.app.js b/apps/ha/ha.app.js
index d9199fb0e..14c6dc3be 100644
--- a/apps/ha/ha.app.js
+++ b/apps/ha/ha.app.js
@@ -17,39 +17,41 @@ function draw() {
g.setFontAlign(-1,-1);
var icon = trigger.getIcon();
- g.setColor(g.theme.fg).drawImage(icon, 12, H/5-2);
- g.drawString("Home", icon.width + 20, H/5);
- g.drawString("Assistant", icon.width + 18, H/5+24);
+ g.setColor(g.theme.fg).drawImage(icon, 12, H/5-2-5);
+ g.drawString("Home", icon.width + 20, H/5-5);
+ g.drawString("Assistant", icon.width + 18, H/5+24-5);
g.setFontAlign(0,0);
- var ypos = H/5*3+20;
+ var ypos = H/5*3+23;
g.drawRect(W/2-w/2-8, ypos-h/2-8, W/2+w/2+5, ypos+h/2+5);
g.fillRect(W/2-w/2-6, ypos-h/2-6, W/2+w/2+3, ypos+h/2+3);
g.setColor(g.theme.bg).drawString(trigger.display, W/2, ypos);
+
+ // draw arrows
+ g.setColor(g.theme.fg);
+ if (position > 0) {
+ g.drawLine(10, H/2, 20, H/2 - 10);
+ g.drawLine(10, H/2, 20, H/2 + 10);
+ }
+ if (position < triggers.length -1) {
+ g.drawLine(W - 10, H/2, W - 20, H/2 - 10);
+ g.drawLine(W - 10, H/2, W - 20, H/2 + 10);
+ }
}
-
-Bangle.on('touch', function(btn, e){
- var left = parseInt(g.getWidth() * 0.3);
- var right = g.getWidth() - left;
- var isLeft = e.x < left;
- var isRight = e.x > right;
-
- if(isRight){
- Bangle.buzz(40, 0.6);
- position += 1;
- position = position >= triggers.length ? 0 : position;
- draw();
- }
-
- if(isLeft){
+function toLeft() {
Bangle.buzz(40, 0.6);
position -= 1;
position = position < 0 ? triggers.length-1 : position;
draw();
- }
-
- if(!isRight && !isLeft){
+}
+function toRight() {
+ Bangle.buzz(40, 0.6);
+ position += 1;
+ position = position >= triggers.length ? 0 : position;
+ draw();
+}
+function sendTrigger() {
ha.sendTrigger("TRIGGER");
// Now send the selected trigger
@@ -59,9 +61,34 @@ Bangle.on('touch', function(btn, e){
Bangle.buzz(80, 0.6);
}, 250);
});
+}
+
+Bangle.on('touch', function(btn, e){
+ var left = parseInt(g.getWidth() * 0.3);
+ var right = g.getWidth() - left;
+ var isLeft = e.x < left;
+ var isRight = e.x > right;
+
+ if(isLeft){
+ toLeft();
+ }
+ if(isRight){
+ toRight();
+ }
+ if(!isRight && !isLeft){
+ sendTrigger();
}
});
+Bangle.on("swipe", (lr,ud) => {
+ if (lr == -1) {
+ toLeft();
+ }
+ if (lr == 1) {
+ toRight();
+ }
+ });
+
// Send intent that the we started the app.
ha.sendTrigger("APP_STARTED");
diff --git a/apps/ha/ha.clkinfo.js b/apps/ha/ha.clkinfo.js
index d6a0f72a0..09724ba45 100644
--- a/apps/ha/ha.clkinfo.js
+++ b/apps/ha/ha.clkinfo.js
@@ -12,7 +12,7 @@
haItems.items.push({
name: null,
get: () => ({ text: trigger.display, img: trigger.getIcon()}),
- show: function() { haItems.items[i].emit("redraw"); },
+ show: function() {},
hide: function () {},
run: function() {
ha.sendTrigger("TRIGGER_BW");
@@ -23,4 +23,4 @@
});
return haItems;
-})
\ No newline at end of file
+})
diff --git a/apps/ha/interface.html b/apps/ha/interface.html
new file mode 100644
index 000000000..3520b217b
--- /dev/null
+++ b/apps/ha/interface.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+