diff --git a/apps/contacts/ChangeLog b/apps/contacts/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/contacts/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/contacts/README.md b/apps/contacts/README.md
new file mode 100644
index 000000000..1bfc99c8e
--- /dev/null
+++ b/apps/contacts/README.md
@@ -0,0 +1,29 @@
+# Contacts
+
+This app provides a common way to set up the `contacts.json` file.
+
+## Contacts JSON file
+
+When the app is loaded from the app loader, a file named
+`contacts.json` is loaded along with the javascript etc. The file
+has the following contents:
+
+```
+[
+ {
+ "name":"NONE"
+ },
+ {
+ "name":"First Last",
+ "number":"123456789",
+ }
+]
+```
+
+## Contacts Editor
+
+Clicking on the download icon of `Contents` in the app loader invokes
+the contact editor. The editor downloads and displays the current
+`contacts.json` file. Clicking the `Edit` button beside an entry
+causes the entry to be deleted from the list and displayed in the edit
+boxes. It can be restored - by clicking the `Add` button.
\ No newline at end of file
diff --git a/apps/contacts/app-icon.js b/apps/contacts/app-icon.js
new file mode 100644
index 000000000..3012be8d8
--- /dev/null
+++ b/apps/contacts/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcBkmSpIC/AVsJCJ+AQaCZBCOeACKGQLKGQBA0ggARPJ4IRsYo0ggR9IoAIGiRiIpEECJsAiACBBYoRGpEAI4JBFI47CBLIRlDHYJrGYQIRCwQICL4MQOgx9GboUSeQ4RFwAFBiSGHCIo4CiVIWZyPICP4RaRIQROgARHdIwICoIIFkDpGBAKqHgGACI0AyVIggIDoEEMQ1ICINJCIj4CfwIREBwUgQYYOCfYoFDJQKDFCIopEO4RoDKAqJHRhAC/ATA="))
diff --git a/apps/contacts/app.png b/apps/contacts/app.png
new file mode 100644
index 000000000..147dcc61a
Binary files /dev/null and b/apps/contacts/app.png differ
diff --git a/apps/contacts/contacts.app.js b/apps/contacts/contacts.app.js
new file mode 100644
index 000000000..85eef625b
--- /dev/null
+++ b/apps/contacts/contacts.app.js
@@ -0,0 +1,189 @@
+/* contacts.js */
+
+var Layout = require("Layout");
+
+const W = g.getWidth();
+const H = g.getHeight();
+
+var wp = require('Storage').readJSON("contacts.json", true) || [];
+// Use this with corrupted contacts
+//var wp = [];
+
+var key; /* Shared between functions, typically wp name */
+
+function writeContact() {
+ require('Storage').writeJSON("contacts.json", wp);
+}
+
+function mainMenu() {
+ var menu = {
+ "< Back" : Bangle.load
+ };
+ if (Object.keys(wp).length==0) Object.assign(menu, {"NO Contacts":""});
+ else for (let id in wp) {
+ let i = id;
+ menu[wp[id]["name"]]=()=>{ decode(i); };
+ }
+ menu["Add"]=addCard;
+ menu["Remove"]=removeCard;
+ g.clear();
+ E.showMenu(menu);
+}
+
+function decode(pin) {
+ var i = wp[pin];
+ var l = i["name"] + "\n" + i["number"];
+ var la = new Layout ({
+ type:"v", c: [
+ {type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
+ ], lazy:true});
+ g.clear();
+ la.render();
+}
+
+function showNumpad(text, key_, callback) {
+ key = key_;
+ E.showMenu();
+ function addDigit(digit) {
+ key+=digit;
+ if (1) {
+ l = text[key.length];
+ switch (l) {
+ case '.': case ' ': case "'":
+ key+=l;
+ break;
+ case 'd': case 'D': default:
+ break;
+ }
+ }
+ Bangle.buzz(20);
+ update();
+ }
+ function update() {
+ g.reset();
+ g.clearRect(0,0,g.getWidth(),23);
+ s = key + text.substr(key.length, 999);
+ g.setFont("Vector:24").setFontAlign(1,0).drawString(s,g.getWidth(),12);
+ }
+ ds="12%";
+ var numPad = new Layout ({
+ type:"v", c: [{
+ type:"v", c: [
+ {type:"", height:24},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"7", cb:l=>{addDigit("7");}},
+ {type:"btn", font:ds, width:58, label:"8", cb:l=>{addDigit("8");}},
+ {type:"btn", font:ds, width:58, label:"9", cb:l=>{addDigit("9");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"4", cb:l=>{addDigit("4");}},
+ {type:"btn", font:ds, width:58, label:"5", cb:l=>{addDigit("5");}},
+ {type:"btn", font:ds, width:58, label:"6", cb:l=>{addDigit("6");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"1", cb:l=>{addDigit("1");}},
+ {type:"btn", font:ds, width:58, label:"2", cb:l=>{addDigit("2");}},
+ {type:"btn", font:ds, width:58, label:"3", cb:l=>{addDigit("3");}}
+ ]},
+ {type:"h",filly:1, c: [
+ {type:"btn", font:ds, width:58, label:"0", cb:l=>{addDigit("0");}},
+ {type:"btn", font:ds, width:58, label:"C", cb:l=>{key=key.slice(0,-1); update();}},
+ {type:"btn", font:ds, width:58, id:"OK", label:"OK", cb:callback}
+ ]}
+ ]}
+ ], lazy:true});
+ g.clear();
+ numPad.render();
+ update();
+}
+
+function removeCard() {
+ var menu = {
+ "" : {title : "Select Contact"},
+ "< Back" : mainMenu
+ };
+ if (Object.keys(wp).length==0) Object.assign(menu, {"No Contacts":""});
+ else {
+ wp.forEach((val, card) => {
+ const name = wp[card].name;
+ menu[name]=()=>{
+ E.showMenu();
+ var confirmRemove = new Layout (
+ {type:"v", c: [
+ {type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Delete"},
+ {type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:name},
+ {type:"h", c: [
+ {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
+ wp.splice(card, 1);
+ writeContact();
+ mainMenu();
+ }},
+ {type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
+ ]}
+ ], lazy:true});
+ g.clear();
+ confirmRemove.render();
+ };
+ });
+ }
+ E.showMenu(menu);
+}
+
+function askPosition(callback) {
+ let full = "";
+ showNumpad("dddDDDddd", "", function() {
+ callback(key, "");
+ });
+}
+
+function createContact(lat, name) {
+ let n = {};
+ n["name"] = name;
+ n["number"] = lat;
+ wp.push(n);
+ print("add -- contacts", wp);
+ writeContact();
+}
+
+function addCardName2(key) {
+ g.clear();
+ askPosition(function(lat, lon) {
+ print("position -- ", lat, lon);
+ createContact(lat, result);
+ mainMenu();
+ });
+}
+
+function addCardName(key) {
+ result = key;
+ if (wp[result]!=undefined) {
+ E.showMenu();
+ var alreadyExists = new Layout (
+ {type:"v", c: [
+ {type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
+ {type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
+ {type:"h", c: [
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addCardName2(key); }},
+ {type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
+ ]}
+ ], lazy:true});
+ g.clear();
+ alreadyExists.render();
+ return;
+ }
+ addCardName2(key);
+}
+
+function addCard() {
+ require("textinput").input({text:""}).then(result => {
+ if (result != "") {
+ addCardName(result);
+ } else
+ mainMenu();
+ });
+}
+
+g.reset();
+Bangle.setUI();
+mainMenu();
diff --git a/apps/contacts/contacts.json b/apps/contacts/contacts.json
new file mode 100644
index 000000000..40afa27dd
--- /dev/null
+++ b/apps/contacts/contacts.json
@@ -0,0 +1,6 @@
+[
+ {
+ "name":"EU emergency",
+ "number":"112"
+ }
+]
diff --git a/apps/contacts/interface.html b/apps/contacts/interface.html
new file mode 100644
index 000000000..013478960
--- /dev/null
+++ b/apps/contacts/interface.html
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contacts v.2
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Number |
+
+
+
+
+
+
+
Add a new contact
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/contacts/metadata.json b/apps/contacts/metadata.json
new file mode 100644
index 000000000..c466906b5
--- /dev/null
+++ b/apps/contacts/metadata.json
@@ -0,0 +1,19 @@
+{ "id": "contacts",
+ "name": "contacts",
+ "version":"0.01",
+ "description": "Provides means of storing user contacts, viewing/editing them on device and from the App loader",
+ "icon": "app.png",
+ "tags": "tool",
+ "supports" : ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "interface": "interface.html",
+ "dependencies": {"textinput":"type"},
+ "storage": [
+ {"name":"contacts.app.js","url":"contacts.app.js"},
+ {"name":"contacts.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [
+ {"name":"contacts.json","url":"contacts.json"}
+ ]
+}
diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog
index c3ea6041a..665d11afa 100644
--- a/apps/recorder/ChangeLog
+++ b/apps/recorder/ChangeLog
@@ -38,4 +38,5 @@
0.30: Add clock info for showing and toggling recording state
0.31: Ensure that background-drawn tracks can get cancelled, and draw less at a time to make updates smoother
plotTrack now draws the current track even if you're not actively recording
-0.32: Add cadence data to output files
\ No newline at end of file
+0.32: Add cadence data to output files
+0.33: Ensure that a new file is always created if the stuff that's being recorded has changed (fix #3081)
\ No newline at end of file
diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json
index a95ddf470..33034ae34 100644
--- a/apps/recorder/metadata.json
+++ b/apps/recorder/metadata.json
@@ -2,7 +2,7 @@
"id": "recorder",
"name": "Recorder",
"shortName": "Recorder",
- "version": "0.32",
+ "version": "0.33",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png",
"tags": "tool,outdoors,gps,widget,clkinfo",
diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js
index 2525a96e4..585b97d52 100644
--- a/apps/recorder/widget.js
+++ b/apps/recorder/widget.js
@@ -159,6 +159,21 @@
return recorders;
}
+ let getActiveRecorders = function() {
+ let activeRecorders = [];
+ let recorders = getRecorders();
+ settings.record.forEach(r => {
+ var recorder = recorders[r];
+ if (!recorder) {
+ console.log(/*LANG*/"Recorder for "+E.toJS(r)+/*LANG*/"+not found");
+ return;
+ }
+ activeRecorders.push(recorder());
+ });
+ return activeRecorders;
+ };
+ let getCSVHeaders = activeRecorders => ["Time"].concat(activeRecorders.map(r=>r.fields));
+
let writeLog = function() {
entriesWritten++;
WIDGETS["recorder"].draw();
@@ -189,17 +204,9 @@
if (settings.recording) {
// set up recorders
- var recorders = getRecorders(); // TODO: order??
- settings.record.forEach(r => {
- var recorder = recorders[r];
- if (!recorder) {
- console.log(/*LANG*/"Recorder for "+E.toJS(r)+/*LANG*/"+not found");
- return;
- }
- var activeRecorder = recorder();
+ activeRecorders = getActiveRecorders();
+ activeRecorders.forEach(activeRecorder => {
activeRecorder.start();
- activeRecorders.push(activeRecorder);
- // TODO: write field names?
});
WIDGETS["recorder"].width = 15 + ((activeRecorders.length+1)>>1)*12; // 12px per recorder
// open/create file
@@ -209,9 +216,7 @@
} else {
storageFile = require("Storage").open(settings.file,"w");
// New file - write headers
- var fields = ["Time"];
- activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.fields));
- storageFile.write(fields.join(",")+"\n");
+ storageFile.write(getCSVHeaders(activeRecorders).join(",")+"\n");
}
// start recording...
WIDGETS["recorder"].draw();
@@ -246,7 +251,8 @@
// if no filename set or date different, set up a new filename
settings.file = getTrackFilename();
}
- if (require("Storage").list(settings.file).length){ // if file exists
+ var headers = require("Storage").open(settings.file,"r").readLine();
+ if (headers && headers.trim()==getCSVHeaders(getActiveRecorders()).join(",")){ // if file exists AND the headers match (#3081)
if (!options.force) { // if not forced, ask the question
g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset
return E.showPrompt(
diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog
index 1fdeada0d..50ffea933 100644
--- a/apps/setting/ChangeLog
+++ b/apps/setting/ChangeLog
@@ -73,4 +73,6 @@ of 'Select Clock'
Remove 'beta' label from passkey - it's been around for a while and works ok
0.64: Default to wakeOnTwist being off
0.65: Prepend 'LCD->Calibration' touch listener and stop event propagation.
+0.66: Fix LCD calibration bug where it would come on again after the
+ calibration was done.
diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json
index 4b5a02135..412bad6f6 100644
--- a/apps/setting/metadata.json
+++ b/apps/setting/metadata.json
@@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
- "version": "0.65",
+ "version": "0.66",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",
diff --git a/apps/setting/settings.js b/apps/setting/settings.js
index 2a928a7a0..6ecd8c808 100644
--- a/apps/setting/settings.js
+++ b/apps/setting/settings.js
@@ -922,7 +922,7 @@ function showTouchscreenCalibration() {
}
showTapSpot();
}
- Bangle.prependListener&&Bangle.prependListener('touch',touchHandler)||Bangle.on('touch',touchHandler);
+ Bangle.prependListener?Bangle.prependListener('touch',touchHandler):Bangle.on('touch',touchHandler);
showTapSpot();
}
diff --git a/apps/twentyeightbysix/app.js b/apps/twentyeightbysix/app.js
index 5ad755800..fb9067c73 100644
--- a/apps/twentyeightbysix/app.js
+++ b/apps/twentyeightbysix/app.js
@@ -13,27 +13,27 @@ const weirdAwakeHours = 19;
const weirdSleepHours = 9;
const normalDayWidth = HARDWARE_VERSION == 1 ? 24: 28;
-const normalWeekDayHeight = HARDWARE_VERSION == 1 ? 10: 11;
-const normalDayBoxHeight = HARDWARE_VERSION == 1 ? 9: 5;
-const normalSleepDayHeight = 28;
+const normalWeekDayHeight = HARDWARE_VERSION == 1 ? 10: 9;
+const normalDayBoxHeight = HARDWARE_VERSION == 1 ? 9: 4;
+const normalSleepDayHeight = HARDWARE_VERSION == 1 ? 28: 27;
const normalAwakeHours = 15;
const normalSleepHours = 9;
const timeSetHeight = HARDWARE_VERSION == 1 ? 30: 34;
const timeSetDistance = HARDWARE_VERSION == 1 ? 50: 29;
-const backgroundColor = "#2c2e3a";
+const backgroundColor = HARDWARE_VERSION == 1 ? "#2c2e3a": "#000000";
const mainTextColor = "#FFFFFF";
-const watchColor = "#aaaaaa";
+const watchColor = HARDWARE_VERSION == 1 ? "#aaaaaa": "#FFFFFF";
-const sleepTextColor = "#000000";
-const sleepBlockColor = "#D8D8D8";
+const sleepTextColor = "#FFFFFF";
+const sleepBlockColor = HARDWARE_VERSION == 1 ? "#D8D8D8": "#000000";
const awakeTextColor = "#000000";
const awakeBlockColor = "#FFFFFF";
const dayTextColor = "#FFFFFF";
-const dayBlockColor = "#2c2e3a";
+const dayBlockColor = HARDWARE_VERSION == 1 ? "#2c2e3a": "#000000";
const quotes = [
["", "Drop the", "ancient", "way of", "sleeping", ""],
@@ -403,24 +403,26 @@ function drawClockPointer() {
middle, circleBottom + 15
]);
- g.fillPoly([
- middle, circleTop,
- middle - 25, circleTop + 5,
- middle - 40, circleTop + 16,
- middle - 10, circleTop - 5,
- middle - 3, circleTop - 10,
- middle, circleTop - 15
- ]);
-
- var circleTopRightY = normalSleepDayHeight + 29;
- g.fillPoly([
- middle, circleTop,
- middle + 25, circleTop + 5,
- middle + 40, circleTop + 16,
- middle + 10, circleTop - 5,
- middle + 3, circleTop - 10,
- middle, circleTop - 15
- ]);
+ if (HARDWARE_VERSION == 1) {
+ g.fillPoly([
+ middle, circleTop,
+ middle - 25, circleTop + 5,
+ middle - 40, circleTop + 16,
+ middle - 10, circleTop - 5,
+ middle - 3, circleTop - 10,
+ middle, circleTop - 15
+ ]);
+
+ g.fillPoly([
+ middle, circleTop,
+ middle + 25, circleTop + 5,
+ middle + 40, circleTop + 16,
+ middle + 10, circleTop - 5,
+ middle + 3, circleTop - 10,
+ middle, circleTop - 15
+ ]);
+
+ }
}
@@ -633,12 +635,11 @@ function printBackground() {
g.setColor(watchColor);
if (HARDWARE_VERSION == 1) {
-
g.drawCircle(screenWidth / 2, screenHeight / 2, 55);
g.drawCircle(screenWidth / 2, screenHeight / 2, 54);
g.drawCircle(screenWidth / 2, screenHeight / 2, 53);
- drawClockPointer();
}
+ drawClockPointer();
}
diff --git a/apps/twentyeightbysix/screenshot3.png b/apps/twentyeightbysix/screenshot3.png
index 3771001e6..d1ae7f15a 100644
Binary files a/apps/twentyeightbysix/screenshot3.png and b/apps/twentyeightbysix/screenshot3.png differ