Merge branch 'espruino:master' into quicklaunch
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Load AGPS data on app start and automatically in background
|
0.02: Load AGPS data on app start and automatically in background
|
||||||
0.03: Do not load AGPS data on boot
|
0.03: Do not load AGPS data on boot
|
||||||
Increase minimum interval to 6 hours
|
Increase minimum interval to 6 hours
|
||||||
|
0.04: Write AGPS data chunks with delay to improve reliability
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ function updateAgps() {
|
||||||
g.clear();
|
g.clear();
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
waiting = true;
|
waiting = true;
|
||||||
display("Updating A-GPS...");
|
display("Updating A-GPS...", "takes ~ 10 seconds");
|
||||||
require("agpsdata").pull(function() {
|
require("agpsdata").pull(function() {
|
||||||
waiting = false;
|
waiting = false;
|
||||||
display("A-GPS updated.", "touch to close");
|
display("A-GPS updated.", "touch to close");
|
||||||
|
|
|
||||||
|
|
@ -8,41 +8,52 @@ var FILE = "agpsdata.settings.json";
|
||||||
var settings;
|
var settings;
|
||||||
readSettings();
|
readSettings();
|
||||||
|
|
||||||
function setAGPS(data) {
|
function setAGPS(b64) {
|
||||||
var js = jsFromBase64(data);
|
return new Promise(function(resolve, reject) {
|
||||||
try {
|
var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||||
eval(js);
|
const gnsstype = settings.gnsstype || 1; // default GPS
|
||||||
return true;
|
initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode
|
||||||
}
|
// What about:
|
||||||
catch(e) {
|
// NAV-TIMEUTC (0x01 0x10)
|
||||||
console.log("error:", e);
|
// NAV-PV (0x01 0x03)
|
||||||
}
|
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||||
return false;
|
|
||||||
|
eval(initCommands);
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeChunks(atob(b64), resolve);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error:", e);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsFromBase64(b64) {
|
var chunkI = 0;
|
||||||
var bin = atob(b64);
|
function writeChunks(bin, resolve) {
|
||||||
var chunkSize = 128;
|
return new Promise(function(resolve2) {
|
||||||
var js = "Bangle.setGPSPower(1);\n"; // turn GPS on
|
const chunkSize = 128;
|
||||||
var gnsstype = settings.gnsstype || 1; // default GPS
|
setTimeout(function() {
|
||||||
js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode
|
if (chunkI < bin.length) {
|
||||||
// What about:
|
var chunk = bin.substr(chunkI, chunkSize);
|
||||||
// NAV-TIMEUTC (0x01 0x10)
|
js = `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
||||||
// NAV-PV (0x01 0x03)
|
eval(js);
|
||||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
|
||||||
|
|
||||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
chunkI += chunkSize;
|
||||||
var chunk = bin.substr(i,chunkSize);
|
writeChunks(bin, resolve);
|
||||||
js += `Serial1.write(atob("${btoa(chunk)}"))\n`;
|
} else {
|
||||||
}
|
if (resolve)
|
||||||
return js;
|
resolve(); // call outer resolve
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function CASIC_CHECKSUM(cmd) {
|
function CASIC_CHECKSUM(cmd) {
|
||||||
var cs = 0;
|
var cs = 0;
|
||||||
for (var i=1;i<cmd.length;i++)
|
for (var i = 1; i < cmd.length; i++)
|
||||||
cs = cs ^ cmd.charCodeAt(i);
|
cs = cs ^ cmd.charCodeAt(i);
|
||||||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
return cmd + "*" + cs.toString(16).toUpperCase().padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLastUpdate() {
|
function updateLastUpdate() {
|
||||||
|
|
@ -53,23 +64,30 @@ function updateLastUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.pull = function(successCallback, failureCallback) {
|
exports.pull = function(successCallback, failureCallback) {
|
||||||
let uri = "https://www.espruino.com/agps/casic.base64";
|
const uri = "https://www.espruino.com/agps/casic.base64";
|
||||||
if (Bangle.http){
|
if (Bangle.http) {
|
||||||
Bangle.http(uri, {timeout:10000}).then(event => {
|
Bangle.http(uri, {timeout : 10000})
|
||||||
let result = setAGPS(event.resp);
|
.then(event => {
|
||||||
if (result) {
|
setAGPS(event.resp)
|
||||||
updateLastUpdate();
|
.then(r => {
|
||||||
if (successCallback) successCallback();
|
updateLastUpdate();
|
||||||
} else {
|
if (successCallback)
|
||||||
console.log("error applying AGPS data");
|
successCallback();
|
||||||
if (failureCallback) failureCallback("Error applying AGPS data");
|
})
|
||||||
}
|
.catch((e) => {
|
||||||
}).catch((e)=>{
|
console.log("error", e);
|
||||||
console.log("error", e);
|
if (failureCallback)
|
||||||
if (failureCallback) failureCallback(e);
|
failureCallback(e);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("error", e);
|
||||||
|
if (failureCallback)
|
||||||
|
failureCallback(e);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("error: No http method found");
|
console.log("error: No http method found");
|
||||||
if (failureCallback) failureCallback(/*LANG*/"No http method");
|
if (failureCallback)
|
||||||
|
failureCallback(/*LANG*/ "No http method");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "A-GPS Data Downloader App",
|
"name": "A-GPS Data Downloader App",
|
||||||
"shortName":"A-GPS Data",
|
"shortName":"A-GPS Data",
|
||||||
"icon": "agpsdata.png",
|
"icon": "agpsdata.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"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.",
|
"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",
|
"tags": "boot,tool,assisted,gps,agps,http",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First version
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# BarWatch - an experimental watch
|
||||||
|
|
||||||
|
For too long the watches have shown the time with digits or hands. No more!
|
||||||
|
With this stylish watch the time is represented by bars. Up to 24 as the day goes by.
|
||||||
|
Practical? Not really, but a different look!
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY="))
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
if(g.theme.dark){
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
}else{
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out how to display the current time
|
||||||
|
var d = new Date();
|
||||||
|
var h = d.getHours(), m = d.getMinutes();
|
||||||
|
|
||||||
|
// hour bars
|
||||||
|
var bx_offset = 10, by_offset = 35;
|
||||||
|
var b_width = 8, b_height = 60;
|
||||||
|
var b_space = 5;
|
||||||
|
|
||||||
|
for(var i=0; i<h; i++){
|
||||||
|
if(i > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var iter = i % 12;
|
||||||
|
//console.log(iter);
|
||||||
|
g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter),
|
||||||
|
by_offset,
|
||||||
|
bx_offset+(b_width*iter)+(b_space*iter),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// minute bar
|
||||||
|
if(h > 11){
|
||||||
|
by_offset = 105;
|
||||||
|
}
|
||||||
|
var m_bar = h % 12;
|
||||||
|
if(m != 0){
|
||||||
|
g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar),
|
||||||
|
by_offset+b_height-m,
|
||||||
|
bx_offset+(b_width*m_bar)+(b_space*m_bar),
|
||||||
|
by_offset+b_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue draw in one minute
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
After Width: | Height: | Size: 973 B |
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"id": "barwatch",
|
||||||
|
"name": "BarWatch",
|
||||||
|
"shortName":"BarWatch",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A watch that displays the time using bars. One bar for each hour.",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "screenshot.png",
|
||||||
|
"tags": "clock",
|
||||||
|
"type": "clock",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"screenshots" : [ { "url": "screenshot.png" } ],
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"barwatch.app.js","url":"app.js"},
|
||||||
|
{"name":"barwatch.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -30,3 +30,7 @@
|
||||||
Allow recording unmodified internal HR
|
Allow recording unmodified internal HR
|
||||||
Better connection retry handling
|
Better connection retry handling
|
||||||
0.13: Less time used during boot if disabled
|
0.13: Less time used during boot if disabled
|
||||||
|
0.14: Allow bonding (Debug menu)
|
||||||
|
Prevent mixing of BT and internal HRM events if both are enabled
|
||||||
|
Always use a grace period (default 0 ms) to decouple some connection steps
|
||||||
|
Device not found errors now utilize increasing timeouts
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,6 @@
|
||||||
"gracePeriodNotification": 0,
|
"gracePeriodNotification": 0,
|
||||||
"gracePeriodConnect": 0,
|
"gracePeriodConnect": 0,
|
||||||
"gracePeriodService": 0,
|
"gracePeriodService": 0,
|
||||||
"gracePeriodRequest": 0
|
"gracePeriodRequest": 0,
|
||||||
|
"bonding": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ exports.enable = () => {
|
||||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
||||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||||
bpmTimeout = setTimeout(()=>{
|
bpmTimeout = setTimeout(()=>{
|
||||||
|
bpmTimeout = undefined;
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
startFallback();
|
startFallback();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
@ -154,8 +155,8 @@ exports.enable = () => {
|
||||||
src: "bthrm"
|
src: "bthrm"
|
||||||
};
|
};
|
||||||
|
|
||||||
log("Emitting HRM", repEvent);
|
log("Emitting aggregated HRM", repEvent);
|
||||||
Bangle.emit("HRM_int", repEvent);
|
Bangle.emit("HRM_R", repEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newEvent = {
|
var newEvent = {
|
||||||
|
|
@ -280,7 +281,11 @@ exports.enable = () => {
|
||||||
log("Disconnect: " + reason);
|
log("Disconnect: " + reason);
|
||||||
log("GATT", gatt);
|
log("GATT", gatt);
|
||||||
log("Characteristics", characteristics);
|
log("Characteristics", characteristics);
|
||||||
clearRetryTimeout(reason != "Connection Timeout");
|
|
||||||
|
var retryTimeResetNeeded = true;
|
||||||
|
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||||
|
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||||
|
clearRetryTimeout(retryTimeResetNeeded);
|
||||||
supportedCharacteristics["0x2a37"].active = false;
|
supportedCharacteristics["0x2a37"].active = false;
|
||||||
startFallback();
|
startFallback();
|
||||||
blockInit = false;
|
blockInit = false;
|
||||||
|
|
@ -312,13 +317,13 @@ exports.enable = () => {
|
||||||
result = result.then(()=>{
|
result = result.then(()=>{
|
||||||
log("Starting notifications", newCharacteristic);
|
log("Starting notifications", newCharacteristic);
|
||||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||||
if (settings.gracePeriodNotification > 0){
|
|
||||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||||
startPromise = startPromise.then(()=>{
|
startPromise = startPromise.then(()=>{
|
||||||
log("Wait after connect");
|
log("Wait after connect");
|
||||||
return waitingPromise(settings.gracePeriodNotification);
|
return waitingPromise(settings.gracePeriodNotification);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return startPromise;
|
return startPromise;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -429,30 +434,30 @@ exports.enable = () => {
|
||||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
var connectPromise = gatt.connect(connectSettings).then(function() {
|
||||||
log("Connected.");
|
log("Connected.");
|
||||||
});
|
});
|
||||||
if (settings.gracePeriodConnect > 0){
|
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
connectPromise = connectPromise.then(()=>{
|
||||||
connectPromise = connectPromise.then(()=>{
|
log("Wait after connect");
|
||||||
log("Wait after connect");
|
return waitingPromise(settings.gracePeriodConnect);
|
||||||
return waitingPromise(settings.gracePeriodConnect);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
return connectPromise;
|
return connectPromise;
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* promise = promise.then(() => {
|
if (settings.bonding){
|
||||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
promise = promise.then(() => {
|
||||||
if (gatt.getSecurityStatus()['bonded']) {
|
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||||
log("Already bonded");
|
if (gatt.getSecurityStatus()['bonded']) {
|
||||||
return Promise.resolve();
|
log("Already bonded");
|
||||||
} else {
|
return Promise.resolve();
|
||||||
log("Start bonding");
|
} else {
|
||||||
return gatt.startBonding()
|
log("Start bonding");
|
||||||
.then(() => console.log(gatt.getSecurityStatus()));
|
return gatt.startBonding()
|
||||||
}
|
.then(() => console.log(gatt.getSecurityStatus()));
|
||||||
});*/
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
promise = promise.then(()=>{
|
promise = promise.then(()=>{
|
||||||
if (!characteristics || characteristics.length === 0){
|
if (!characteristics || characteristics.length === 0){
|
||||||
|
|
@ -476,13 +481,11 @@ exports.enable = () => {
|
||||||
log("Supporting service", service.uuid);
|
log("Supporting service", service.uuid);
|
||||||
result = attachServicePromise(result, service);
|
result = attachServicePromise(result, service);
|
||||||
}
|
}
|
||||||
if (settings.gracePeriodService > 0) {
|
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
result = result.then(()=>{
|
||||||
result = result.then(()=>{
|
log("Wait after services");
|
||||||
log("Wait after services");
|
return waitingPromise(settings.gracePeriodService);
|
||||||
return waitingPromise(settings.gracePeriodService);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -538,35 +541,33 @@ exports.enable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (settings.replace){
|
if (settings.replace){
|
||||||
|
// register a listener for original HRM events and emit as HRM_int
|
||||||
Bangle.on("HRM", (e) => {
|
Bangle.on("HRM", (e) => {
|
||||||
e.modified = true;
|
e.modified = true;
|
||||||
Bangle.emit("HRM_int", e);
|
Bangle.emit("HRM_int", e);
|
||||||
|
if (fallbackActive){
|
||||||
|
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||||
|
Bangle.emit("HRM_R", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.origOn = Bangle.on;
|
// force all apps wanting to listen to HRM to actually get events for HRM_R
|
||||||
Bangle.on = function(name, callback) {
|
Bangle.on = ( o => (name, cb) => {
|
||||||
if (name == "HRM") {
|
o = o.bind(Bangle);
|
||||||
Bangle.origOn("HRM_int", callback);
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
} else {
|
else o(name, cb);
|
||||||
Bangle.origOn(name, callback);
|
})(Bangle.on);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Bangle.origRemoveListener = Bangle.removeListener;
|
|
||||||
Bangle.removeListener = function(name, callback) {
|
|
||||||
if (name == "HRM") {
|
|
||||||
Bangle.origRemoveListener("HRM_int", callback);
|
|
||||||
} else {
|
|
||||||
Bangle.origRemoveListener(name, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
Bangle.removeListener = ( o => (name, cb) => {
|
||||||
|
o = o.bind(Bangle);
|
||||||
|
if (name == "HRM") o("HRM_R", cb);
|
||||||
|
else o(name, cb);
|
||||||
|
})(Bangle.removeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||||
|
|
||||||
if (settings.startWithHrm){
|
if (settings.startWithHrm){
|
||||||
|
|
||||||
Bangle.setHRMPower = function(isOn, app) {
|
Bangle.setHRMPower = function(isOn, app) {
|
||||||
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
||||||
if (settings.enabled){
|
if (settings.enabled){
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bthrm",
|
"id": "bthrm",
|
||||||
"name": "Bluetooth Heart Rate Monitor",
|
"name": "Bluetooth Heart Rate Monitor",
|
||||||
"shortName": "BT HRM",
|
"shortName": "BT HRM",
|
||||||
"version": "0.13",
|
"version": "0.14",
|
||||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,12 @@
|
||||||
writeSettings("debuglog",v);
|
writeSettings("debuglog",v);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'Use bonding': {
|
||||||
|
value: !!settings.bonding,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("bonding",v);
|
||||||
|
}
|
||||||
|
},
|
||||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@
|
||||||
0.03: Support for different screen sizes and touchscreen
|
0.03: Support for different screen sizes and touchscreen
|
||||||
0.04: Display current operation on LHS
|
0.04: Display current operation on LHS
|
||||||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||||
|
0.06: Bangle.js 2: Exit with a short press of the physical button
|
||||||
|
0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
Bangle.js 1
|
||||||
- UP: BTN1
|
- UP: BTN1
|
||||||
- DOWN: BTN3
|
- DOWN: BTN3
|
||||||
- LEFT: BTN4
|
- LEFT: BTN4
|
||||||
- RIGHT: BTN5
|
- RIGHT: BTN5
|
||||||
- SELECT: BTN2
|
- SELECT: BTN2
|
||||||
|
|
||||||
|
Bangle.js 2
|
||||||
|
- Swipes to change visible buttons
|
||||||
|
- Click physical button to exit
|
||||||
|
- Press upper left corner of screen to exit (where the red back button would be)
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
<https://twitter.com/fredericrous>
|
<https://twitter.com/fredericrous>
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
[thyttan](https://github.com/thyttan)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
||||||
* Created: April 2020
|
* Created: April 2020
|
||||||
|
*
|
||||||
|
* Contributors: thyttan https://github.com/thyttan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
@ -402,43 +404,42 @@ if (process.env.HWVERSION==1) {
|
||||||
swipeEnabled = false;
|
swipeEnabled = false;
|
||||||
drawGlobal();
|
drawGlobal();
|
||||||
} else { // touchscreen?
|
} else { // touchscreen?
|
||||||
selected = "NONE";
|
selected = "NONE";
|
||||||
swipeEnabled = true;
|
swipeEnabled = true;
|
||||||
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
prepareScreen(numbers, numbersGrid, COLORS.DEFAULT);
|
||||||
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
prepareScreen(operators, operatorsGrid, COLORS.OPERATOR);
|
||||||
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
prepareScreen(specials, specialsGrid, COLORS.SPECIAL);
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
Bangle.on('touch',(n,e)=>{
|
|
||||||
for (var key in screen) {
|
Bangle.setUI({
|
||||||
if (typeof screen[key] == "undefined") break;
|
mode : 'custom',
|
||||||
var r = screen[key].xy;
|
back : load, // Clicking physical button or pressing upper left corner turns off (where red back button would be)
|
||||||
if (e.x>=r[0] && e.y>=r[1] &&
|
touch : (n,e)=>{
|
||||||
e.x<r[2] && e.y<r[3]) {
|
for (var key in screen) {
|
||||||
//print("Press "+key);
|
if (typeof screen[key] == "undefined") break;
|
||||||
buttonPress(""+key);
|
var r = screen[key].xy;
|
||||||
|
if (e.x>=r[0] && e.y>=r[1] && e.x<r[2] && e.y<r[3]) {
|
||||||
|
//print("Press "+key);
|
||||||
|
buttonPress(""+key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
swipe : (LR, UD) => {
|
||||||
var lastX = 0, lastY = 0;
|
if (LR == 1) { // right
|
||||||
Bangle.on('drag', (e) => {
|
|
||||||
if (!e.b) {
|
|
||||||
if (lastX > 50) { // right
|
|
||||||
drawSpecials();
|
drawSpecials();
|
||||||
} else if (lastX < -50) { // left
|
}
|
||||||
|
if (LR == -1) { // left
|
||||||
drawOperators();
|
drawOperators();
|
||||||
} else if (lastY > 50) { // down
|
}
|
||||||
drawNumbers();
|
if (UD == 1) { // down
|
||||||
} else if (lastY < -50) { // up
|
drawNumbers();
|
||||||
|
}
|
||||||
|
if (UD == -1) { // up
|
||||||
drawNumbers();
|
drawNumbers();
|
||||||
}
|
}
|
||||||
lastX = 0;
|
|
||||||
lastY = 0;
|
|
||||||
} else {
|
|
||||||
lastX = lastX + e.dx;
|
|
||||||
lastY = lastY + e.dy;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
displayOutput(0);
|
displayOutput(0);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "calculator",
|
"id": "calculator",
|
||||||
"name": "Calculator",
|
"name": "Calculator",
|
||||||
"shortName": "Calculator",
|
"shortName": "Calculator",
|
||||||
"version": "0.05",
|
"version": "0.07",
|
||||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||||
"icon": "calculator.png",
|
"icon": "calculator.png",
|
||||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
||||||
0.04: Now displays the opened text string at launch.
|
0.04: Now displays the opened text string at launch.
|
||||||
0.05: Now scrolls text when string gets longer than screen width.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes
|
|
||||||
//Bangle.setLCDTimeout(30);
|
|
||||||
//Bangle.setLCDPower(1);
|
|
||||||
|
|
||||||
exports.input = function(options) {
|
exports.input = function(options) {
|
||||||
options = options||{};
|
options = options||{};
|
||||||
var text = options.text;
|
var text = options.text;
|
||||||
if ("string"!=typeof text) text="";
|
if ("string"!=typeof text) text="";
|
||||||
|
|
||||||
|
var R = Bangle.appRect;
|
||||||
var BGCOLOR = g.theme.bg;
|
var BGCOLOR = g.theme.bg;
|
||||||
var HLCOLOR = g.theme.fg;
|
var HLCOLOR = g.theme.fg;
|
||||||
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
||||||
|
|
@ -17,35 +14,38 @@ exports.input = function(options) {
|
||||||
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
||||||
|
|
||||||
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
||||||
var ABCPADDING = (g.getWidth()-6*ABC.length)/2;
|
var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2;
|
||||||
|
|
||||||
var NUM = ' 1234567890!?,.- ';
|
var NUM = ' 1234567890!?,.- ';
|
||||||
var NUMHIDDEN = ' 1234567890!?,.- ';
|
var NUMHIDDEN = ' 1234567890!?,.- ';
|
||||||
var NUMPADDING = (g.getWidth()-6*NUM.length)/2;
|
var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2;
|
||||||
|
|
||||||
var rectHeight = 40;
|
var rectHeight = 40;
|
||||||
|
|
||||||
|
|
||||||
var delSpaceLast;
|
var delSpaceLast;
|
||||||
|
|
||||||
function drawAbcRow() {
|
function drawAbcRow() {
|
||||||
g.clear();
|
g.clear();
|
||||||
|
try { // Draw widgets if they are present in the current app.
|
||||||
|
if (WIDGETS) Bangle.drawWidgets();
|
||||||
|
} catch (_) {}
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.setColor(ABCCOLOR);
|
g.setColor(ABCCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.setFontAlign(-1, -1, 0);
|
||||||
g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight());
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
|
g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h));
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawNumRow() {
|
function drawNumRow() {
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.setColor(NUMCOLOR);
|
g.setColor(NUMCOLOR);
|
||||||
g.drawString(NUM, NUMPADDING, g.getHeight()/4);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(NUM, NUMPADDING, (R.y+R.h)/4);
|
||||||
|
|
||||||
g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3);
|
g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTopString() {
|
function updateTopString() {
|
||||||
"ram"
|
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.fillRect(0,4+20,176,13+20);
|
g.fillRect(0,4+20,176,13+20);
|
||||||
g.setColor(0.2,0,0);
|
g.setColor(0.2,0,0);
|
||||||
|
|
@ -54,13 +54,10 @@ exports.input = function(options) {
|
||||||
g.setColor(0.7,0,0);
|
g.setColor(0.7,0,0);
|
||||||
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
||||||
g.setColor(1,1,1);
|
g.setColor(1,1,1);
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawAbcRow();
|
|
||||||
drawNumRow();
|
|
||||||
updateTopString();
|
|
||||||
|
|
||||||
var abcHL;
|
var abcHL;
|
||||||
var abcHLPrev = -10;
|
var abcHLPrev = -10;
|
||||||
var numHL;
|
var numHL;
|
||||||
|
|
@ -70,192 +67,180 @@ exports.input = function(options) {
|
||||||
var largeCharOffset = 6;
|
var largeCharOffset = 6;
|
||||||
|
|
||||||
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
||||||
"ram"
|
"ram";
|
||||||
// Small character in list
|
// Small character in list
|
||||||
g.setColor(rowColor);
|
g.setColor(rowColor);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor);
|
||||||
// Large character
|
// Large character
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24);
|
g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24);
|
||||||
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
||||||
// mark in the list
|
// mark in the list
|
||||||
}
|
}
|
||||||
function showChars(char, HL, typePadding, heightDivisor) {
|
function showChars(char, HL, typePadding, heightDivisor) {
|
||||||
"ram"
|
"ram";
|
||||||
// mark in the list
|
// mark in the list
|
||||||
g.setColor(HLCOLOR);
|
g.setColor(HLCOLOR);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor);
|
||||||
// show new large character
|
// show new large character
|
||||||
g.setFont(BIGFONT);
|
g.setFont(BIGFONT);
|
||||||
g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3);
|
g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3);
|
||||||
g.setFont(SMALLFONT);
|
g.setFont(SMALLFONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initDraw() {
|
||||||
|
//var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed.
|
||||||
|
drawAbcRow();
|
||||||
|
drawNumRow();
|
||||||
|
updateTopString();
|
||||||
|
}
|
||||||
|
initDraw();
|
||||||
|
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
|
||||||
|
|
||||||
function changeCase(abcHL) {
|
function changeCase(abcHL) {
|
||||||
g.setColor(BGCOLOR);
|
g.setColor(BGCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
||||||
else ABC = ABC.toUpperCase();
|
else ABC = ABC.toUpperCase();
|
||||||
g.setColor(ABCCOLOR);
|
g.setColor(ABCCOLOR);
|
||||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||||
}
|
}
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
// Interpret touch input
|
// Interpret touch input
|
||||||
Bangle.setUI({
|
Bangle.setUI({
|
||||||
mode: 'custom',
|
mode: 'custom',
|
||||||
back: ()=>{
|
back: ()=>{
|
||||||
Bangle.setUI();
|
Bangle.setUI();
|
||||||
g.clearRect(Bangle.appRect);
|
g.clearRect(Bangle.appRect);
|
||||||
resolve(text);
|
resolve(text);
|
||||||
},
|
},
|
||||||
drag: function(event) {
|
drag: function(event) {
|
||||||
|
"ram";
|
||||||
|
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
// Choose character by draging along red rectangle at bottom of screen
|
||||||
|
if (event.y >= ( (R.y+R.h) - 12 )) {
|
||||||
|
// Translate x-position to character
|
||||||
|
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||||
|
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||||
|
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
||||||
|
|
||||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
// Datastream for development purposes
|
||||||
// Choose character by draging along red rectangle at bottom of screen
|
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
||||||
if (event.y >= ( g.getHeight() - 12 )) {
|
|
||||||
// Translate x-position to character
|
|
||||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
|
||||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
|
||||||
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
|
||||||
|
|
||||||
// Datastream for development purposes
|
// Unmark previous character and mark the current one...
|
||||||
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
// Handling switching between letters and numbers/punctuation
|
||||||
|
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
// Unmark previous character and mark the current one...
|
if (abcHL != abcHLPrev) {
|
||||||
// Handling switching between letters and numbers/punctuation
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
||||||
|
|
||||||
if (abcHL != abcHLPrev) {
|
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
|
||||||
}
|
}
|
||||||
// Print string at top of screen
|
// Print string at top of screen
|
||||||
if (event.b == 0) {
|
if (event.b == 0) {
|
||||||
text = text + ABC.charAt(abcHL);
|
text = text + ABC.charAt(abcHL);
|
||||||
updateTopString();
|
|
||||||
|
|
||||||
// Autoswitching letter case
|
|
||||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
|
||||||
}
|
|
||||||
// Update previous character to current one
|
|
||||||
abcHLPrev = abcHL;
|
|
||||||
typePrev = 'abc';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 12345678901234567890
|
|
||||||
// Choose number or puctuation by draging on green rectangle
|
|
||||||
else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) {
|
|
||||||
// Translate x-position to character
|
|
||||||
if (event.x < NUMPADDING) { numHL = 0; }
|
|
||||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
|
||||||
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
|
||||||
|
|
||||||
// Datastream for development purposes
|
|
||||||
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
|
||||||
|
|
||||||
// Unmark previous character and mark the current one...
|
|
||||||
// Handling switching between letters and numbers/punctuation
|
|
||||||
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
|
|
||||||
if (numHL != numHLPrev) {
|
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
|
||||||
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
|
||||||
}
|
|
||||||
// Print string at top of screen
|
|
||||||
if (event.b == 0) {
|
|
||||||
g.setColor(HLCOLOR);
|
|
||||||
// Backspace if releasing before list of numbers/punctuation
|
|
||||||
if (event.x < NUMPADDING) {
|
|
||||||
// show delete sign
|
|
||||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
text = text.slice(0, -1);
|
|
||||||
updateTopString();
|
|
||||||
//print(text);
|
|
||||||
}
|
|
||||||
// Append space if releasing after list of numbers/punctuation
|
|
||||||
else if (event.x > g.getWidth()-NUMPADDING) {
|
|
||||||
//show space sign
|
|
||||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
text = text + ' ';
|
|
||||||
updateTopString();
|
|
||||||
//print(text);
|
|
||||||
}
|
|
||||||
// Append selected number/punctuation
|
|
||||||
else {
|
|
||||||
text = text + NUMHIDDEN.charAt(numHL);
|
|
||||||
updateTopString();
|
updateTopString();
|
||||||
|
|
||||||
// Autoswitching letter case
|
// Autoswitching letter case
|
||||||
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
||||||
}
|
}
|
||||||
|
// Update previous character to current one
|
||||||
|
abcHLPrev = abcHL;
|
||||||
|
typePrev = 'abc';
|
||||||
}
|
}
|
||||||
// Update previous character to current one
|
|
||||||
numHLPrev = numHL;
|
|
||||||
typePrev = 'num';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 12345678901234567890
|
||||||
|
// Choose number or puctuation by draging on green rectangle
|
||||||
|
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
|
||||||
|
// Translate x-position to character
|
||||||
|
if (event.x < NUMPADDING) { numHL = 0; }
|
||||||
|
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||||
|
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
||||||
|
|
||||||
|
// Datastream for development purposes
|
||||||
|
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
||||||
|
|
||||||
|
// Unmark previous character and mark the current one...
|
||||||
|
// Handling switching between letters and numbers/punctuation
|
||||||
|
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
|
||||||
|
if (numHL != numHLPrev) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Make a space or backspace by swiping right or left on screen above green rectangle
|
|
||||||
else if (event.y > 20+4) {
|
|
||||||
if (event.b == 0) {
|
|
||||||
g.setColor(HLCOLOR);
|
|
||||||
if (event.x < g.getWidth()/2) {
|
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
||||||
// show delete sign
|
|
||||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
|
||||||
delSpaceLast = 1;
|
|
||||||
|
|
||||||
// Backspace and draw string upper right corner
|
|
||||||
text = text.slice(0, -1);
|
|
||||||
updateTopString();
|
|
||||||
if (text.length==0) changeCase(abcHL);
|
|
||||||
//print(text, 'undid');
|
|
||||||
}
|
}
|
||||||
else {
|
// Print string at top of screen
|
||||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
if (event.b == 0) {
|
||||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
g.setColor(HLCOLOR);
|
||||||
|
// Backspace if releasing before list of numbers/punctuation
|
||||||
|
if (event.x < NUMPADDING) {
|
||||||
|
// show delete sign
|
||||||
|
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
text = text.slice(0, -1);
|
||||||
|
updateTopString();
|
||||||
|
//print(text);
|
||||||
|
}
|
||||||
|
// Append space if releasing after list of numbers/punctuation
|
||||||
|
else if (event.x > (R.x+R.w)-NUMPADDING) {
|
||||||
|
//show space sign
|
||||||
|
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
text = text + ' ';
|
||||||
|
updateTopString();
|
||||||
|
//print(text);
|
||||||
|
}
|
||||||
|
// Append selected number/punctuation
|
||||||
|
else {
|
||||||
|
text = text + NUMHIDDEN.charAt(numHL);
|
||||||
|
updateTopString();
|
||||||
|
|
||||||
//show space sign
|
// Autoswitching letter case
|
||||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
||||||
delSpaceLast = 1;
|
}
|
||||||
|
}
|
||||||
|
// Update previous character to current one
|
||||||
|
numHLPrev = numHL;
|
||||||
|
typePrev = 'num';
|
||||||
|
}
|
||||||
|
|
||||||
// Append space and draw string upper right corner
|
// Make a space or backspace by swiping right or left on screen above green rectangle
|
||||||
text = text + NUMHIDDEN.charAt(0);
|
else if (event.y > 20+4) {
|
||||||
updateTopString();
|
if (event.b == 0) {
|
||||||
//print(text, 'made space');
|
g.setColor(HLCOLOR);
|
||||||
|
if (event.x < (R.x+R.w)/2) {
|
||||||
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
|
// show delete sign
|
||||||
|
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
|
||||||
|
// Backspace and draw string upper right corner
|
||||||
|
text = text.slice(0, -1);
|
||||||
|
updateTopString();
|
||||||
|
if (text.length==0) changeCase(abcHL);
|
||||||
|
//print(text, 'undid');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||||
|
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||||
|
|
||||||
|
//show space sign
|
||||||
|
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||||
|
delSpaceLast = 1;
|
||||||
|
|
||||||
|
// Append space and draw string upper right corner
|
||||||
|
text = text + NUMHIDDEN.charAt(0);
|
||||||
|
updateTopString();
|
||||||
|
//print(text, 'made space');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
/* return new Promise((resolve,reject) => {
|
|
||||||
Bangle.setUI({mode:"custom", back:()=>{
|
|
||||||
Bangle.setUI();
|
|
||||||
g.clearRect(Bangle.appRect);
|
|
||||||
Bangle.setUI();
|
|
||||||
resolve(text);
|
|
||||||
}});
|
|
||||||
}); */
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "dragboard",
|
{ "id": "dragboard",
|
||||||
"name": "Dragboard",
|
"name": "Dragboard",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "A library for text input via swiping keyboard",
|
"description": "A library for text input via swiping keyboard",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type":"textinput",
|
"type":"textinput",
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,8 @@
|
||||||
0.14: Don't move pages when doing exit swipe - Bangle 2.
|
0.14: Don't move pages when doing exit swipe - Bangle 2.
|
||||||
0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
|
0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2.
|
||||||
0.16: Use default Bangle formatter for booleans
|
0.16: Use default Bangle formatter for booleans
|
||||||
|
0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
|
||||||
|
clock face by timeout.
|
||||||
|
0.18: Move interactions inside setUI. Replace "one click exit" with
|
||||||
|
back-functionality through setUI, adding the red back button as well. Hardware
|
||||||
|
button to exit is no longer an option.
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,27 @@
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
|
||||||
/* Desktop launcher
|
/* Desktop launcher
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var settings = Object.assign({
|
let settings = Object.assign({
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
showLaunchers: true,
|
showLaunchers: true,
|
||||||
direct: false,
|
direct: false,
|
||||||
oneClickExit:false,
|
swipeExit: false,
|
||||||
swipeExit: false
|
timeOut: "Off"
|
||||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||||
|
|
||||||
if( settings.oneClickExit)
|
let s = require("Storage");
|
||||||
setWatch(_=> load(), BTN1);
|
var apps = s.list(/\.info$/).map(app=>{
|
||||||
|
let a=s.readJSON(app,1);
|
||||||
var s = require("Storage");
|
|
||||||
var apps = s.list(/\.info$/).map(app=>{
|
|
||||||
var a=s.readJSON(app,1);
|
|
||||||
return a && {
|
return a && {
|
||||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||||
};}).filter(
|
};}).filter(
|
||||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||||
|
|
||||||
apps.sort((a,b)=>{
|
apps.sort((a,b)=>{
|
||||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||||
if (n) return n; // do sortorder first
|
if (n) return n; // do sortorder first
|
||||||
if (a.name<b.name) return -1;
|
if (a.name<b.name) return -1;
|
||||||
if (a.name>b.name) return 1;
|
if (a.name>b.name) return 1;
|
||||||
|
|
@ -33,29 +32,28 @@ apps.forEach(app=>{
|
||||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||||
});
|
});
|
||||||
|
|
||||||
var Napps = apps.length;
|
let Napps = apps.length;
|
||||||
var Npages = Math.ceil(Napps/4);
|
let Npages = Math.ceil(Napps/4);
|
||||||
var maxPage = Npages-1;
|
let maxPage = Npages-1;
|
||||||
var selected = -1;
|
let selected = -1;
|
||||||
var oldselected = -1;
|
let oldselected = -1;
|
||||||
var page = 0;
|
let page = 0;
|
||||||
const XOFF = 24;
|
const XOFF = 24;
|
||||||
const YOFF = 30;
|
const YOFF = 30;
|
||||||
|
|
||||||
function draw_icon(p,n,selected) {
|
let drawIcon= function(p,n,selected) {
|
||||||
var x = (n%2)*72+XOFF;
|
let x = (n%2)*72+XOFF;
|
||||||
var y = n>1?72+YOFF:YOFF;
|
let y = n>1?72+YOFF:YOFF;
|
||||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
||||||
g.clearRect(x+12,y+4,x+59,y+51);
|
g.clearRect(x+12,y+4,x+59,y+51);
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||||
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
g.setFontAlign(0,-1,0).setFont("6x8",1);
|
||||||
var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
|
let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ");
|
||||||
var lineY = 0;
|
let lineY = 0;
|
||||||
var line = "";
|
let line = "";
|
||||||
while (txt.length > 0){
|
while (txt.length > 0){
|
||||||
var c = txt.shift();
|
let c = txt.shift();
|
||||||
|
|
||||||
if (c.length + 1 + line.length > 13){
|
if (c.length + 1 + line.length > 13){
|
||||||
if (line.length > 0){
|
if (line.length > 0){
|
||||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||||
|
|
@ -67,29 +65,34 @@ function draw_icon(p,n,selected) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||||
}
|
};
|
||||||
|
|
||||||
function drawPage(p){
|
let drawPage = function(p){
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(0,24,175,175);
|
g.clearRect(0,24,175,175);
|
||||||
var O = 88+YOFF/2-12*(Npages/2);
|
let O = 88+YOFF/2-12*(Npages/2);
|
||||||
for (var j=0;j<Npages;j++){
|
for (let j=0;j<Npages;j++){
|
||||||
var y = O+j*12;
|
let y = O+j*12;
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||||
else g.drawCircle(XOFF/2,y,4);
|
else g.drawCircle(XOFF/2,y,4);
|
||||||
}
|
}
|
||||||
for (var i=0;i<4;i++) {
|
for (let i=0;i<4;i++) {
|
||||||
if (!apps[p*4+i]) return i;
|
if (!apps[p*4+i]) return i;
|
||||||
draw_icon(p,i,selected==i && !settings.direct);
|
drawIcon(p,i,selected==i && !settings.direct);
|
||||||
}
|
}
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
};
|
||||||
|
|
||||||
Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
Bangle.loadWidgets();
|
||||||
|
//g.clear();
|
||||||
|
//Bangle.drawWidgets();
|
||||||
|
drawPage(0);
|
||||||
|
|
||||||
|
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||||
selected = 0;
|
selected = 0;
|
||||||
oldselected=-1;
|
oldselected=-1;
|
||||||
if(settings.swipeExit && dirLeftRight==1) load();
|
if(settings.swipeExit && dirLeftRight==1) returnToClock();
|
||||||
if (dirUpDown==-1||dirLeftRight==-1){
|
if (dirUpDown==-1||dirLeftRight==-1){
|
||||||
++page; if (page>maxPage) page=0;
|
++page; if (page>maxPage) page=0;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
|
|
@ -97,24 +100,24 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
|
||||||
--page; if (page<0) page=maxPage;
|
--page; if (page<0) page=maxPage;
|
||||||
drawPage(page);
|
drawPage(page);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
function isTouched(p,n){
|
let isTouched = function(p,n){
|
||||||
if (n<0 || n>3) return false;
|
if (n<0 || n>3) return false;
|
||||||
var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF;
|
let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF;
|
||||||
var x2 = x1+71; var y2 = y1+81;
|
let x2 = x1+71; let y2 = y1+81;
|
||||||
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||||
}
|
};
|
||||||
|
|
||||||
Bangle.on("touch",(_,p)=>{
|
let touchListenerDt = function(_,p){
|
||||||
var i;
|
let i;
|
||||||
for (i=0;i<4;i++){
|
for (i=0;i<4;i++){
|
||||||
if((page*4+i)<Napps){
|
if((page*4+i)<Napps){
|
||||||
if (isTouched(p,i)) {
|
if (isTouched(p,i)) {
|
||||||
draw_icon(page,i,true && !settings.direct);
|
drawIcon(page,i,true && !settings.direct);
|
||||||
if (selected>=0 || settings.direct) {
|
if (selected>=0 || settings.direct) {
|
||||||
if (selected!=i && !settings.direct){
|
if (selected!=i && !settings.direct){
|
||||||
draw_icon(page,selected,false);
|
drawIcon(page,selected,false);
|
||||||
} else {
|
} else {
|
||||||
load(apps[page*4+i].src);
|
load(apps[page*4+i].src);
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +128,32 @@ Bangle.on("touch",(_,p)=>{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||||
draw_icon(page,selected,false);
|
drawIcon(page,selected,false);
|
||||||
selected=-1;
|
selected=-1;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const returnToClock = function() {
|
||||||
|
Bangle.setUI();
|
||||||
|
setTimeout(eval, 0, s.read(".bootcde"));
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : 'custom',
|
||||||
|
back : returnToClock,
|
||||||
|
swipe : swipeListenerDt,
|
||||||
|
touch : touchListenerDt
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
// taken from Icon Launcher with minor alterations
|
||||||
g.clear();
|
var timeoutToClock;
|
||||||
Bangle.drawWidgets();
|
const updateTimeoutToClock = function(){
|
||||||
drawPage(0);
|
if (settings.timeOut!="Off"){
|
||||||
|
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||||
|
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||||
|
timeoutToClock = setTimeout(returnToClock,time*1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateTimeoutToClock();
|
||||||
|
|
||||||
|
} // end of app scope
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dtlaunch",
|
"id": "dtlaunch",
|
||||||
"name": "Desktop Launcher",
|
"name": "Desktop Launcher",
|
||||||
"version": "0.16",
|
"version": "0.18",
|
||||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
"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"}],
|
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
|
|
|
||||||
|
|
@ -5,51 +5,56 @@
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
showLaunchers: true,
|
showLaunchers: true,
|
||||||
direct: false,
|
direct: false,
|
||||||
oneClickExit:false,
|
swipeExit: false,
|
||||||
swipeExit: false
|
timeOut: "Off"
|
||||||
}, require('Storage').readJSON(FILE, true) || {});
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
function writeSettings() {
|
function writeSettings() {
|
||||||
require('Storage').writeJSON(FILE, settings);
|
require('Storage').writeJSON(FILE, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
|
||||||
|
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"" : { "title" : "Desktop launcher" },
|
"" : { "title" : "Desktop launcher" },
|
||||||
"< Back" : () => back(),
|
/*LANG*/"< Back" : () => back(),
|
||||||
'Show clocks': {
|
/*LANG*/'Show clocks': {
|
||||||
value: settings.showClocks,
|
value: settings.showClocks,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.showClocks = v;
|
settings.showClocks = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Show launchers': {
|
/*LANG*/'Show launchers': {
|
||||||
value: settings.showLaunchers,
|
value: settings.showLaunchers,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.showLaunchers = v;
|
settings.showLaunchers = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Direct launch': {
|
/*LANG*/'Direct launch': {
|
||||||
value: settings.direct,
|
value: settings.direct,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.direct = v;
|
settings.direct = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Swipe Exit': {
|
/*LANG*/'Swipe Exit': {
|
||||||
value: settings.swipeExit,
|
value: settings.swipeExit,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.swipeExit = v;
|
settings.swipeExit = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'One click exit': {
|
/*LANG*/'Time Out': { // Adapted from Icon Launcher
|
||||||
value: settings.oneClickExit,
|
value: timeOutChoices.indexOf(settings.timeOut),
|
||||||
|
min: 0,
|
||||||
|
max: timeOutChoices.length-1,
|
||||||
|
format: v => timeOutChoices[v],
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.oneClickExit = v;
|
settings.timeOut = timeOutChoices[v];
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.1: New App!
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
Enton - Enhanced Anton Clock
|
||||||
|
|
||||||
|
This clock face is based on the 'Anton Clock'.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkE/4A/AH4A/AH4A/AH4Aw+cikf/mQDCAAIFBAwQDBBYgXCgEDAQIABn4JBkAFBgIKDgQwFmMD+UCmcgl/zEIMzmcQmYKBmYiCAAfxC4QrBl8wBwcgkYsGC4sAiMAF4UxiIGBn8QAgMSC48wgMRiEDBAISCiYcFC48v//yC4PzgJAGiAXIiczPgPzC4JyBmf/AYQXI+KcCj8wmYFCgEjAYQ3G+cjbQIABJIMzAoUin7XIADpSEK4rWGI4MhmRJBn8j+U/d4MimUTkUzIw5dBl4UBMgIXBAgMyLYKOBmQXHiSbCDgMyl8z+UjmJ1BHgJbHCgM/IYQABAgQJBYYYA/AH4AtaQU/mTvBBozWBd44KBkUSkLnBEo8jkcvBI0/CgMiDAIXHHYIXImUzJQJHH+Y+Bn6Z/ABQA=="))
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
// In case we failed, we can only show 0 steps.
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // 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;
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFontAnton;
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
draw();
|
||||||
|
setTimeout(Bangle.drawWidgets,0);
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 905 B |
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "entonclk",
|
||||||
|
"name": "Enton Clock",
|
||||||
|
"version": "0.1",
|
||||||
|
"description": "A simple clock using the Audiowide font. ",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"readme":"README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"entonclk.app.js","url":"app.js"},
|
||||||
|
{"name":"entonclk.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New app!
|
||||||
|
0.02: Submitted to app loader
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Gallery
|
||||||
|
|
||||||
|
A simple gallery app
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Upon opening the gallery app, you will be presented with a list of images that you can display. Tap the image to show it. Brightness will be set to full, and the screen timeout will be disabled. When you are done viewing the image, you can tap the screen to go back to the list of images. Press BTN1 to flip the image upside down.
|
||||||
|
|
||||||
|
## Adding images
|
||||||
|
|
||||||
|
1. The gallery app does not perform any scaling, and does not support panning. Therefore, you should use your favorite image editor to produce an image of the appropriate size for your watch. (240x240 for Bangle 1 or 176x176 for Bangle 2.) How you achieve this is up to you. If on a Bangle 2, I recommend adjusting the colors here to comply with the color restrictions.
|
||||||
|
|
||||||
|
2. Upload your image to the [Espruino image converter](https://www.espruino.com/Image+Converter). I recommend enabling compression and choosing one of the following color settings:
|
||||||
|
* 16 bit RGB565 for Bangle 1
|
||||||
|
* 3 bit RGB for Bangle 2
|
||||||
|
* 1 bit black/white for monochrome images that you want to respond to your system theme. (White will be rendered as your foreground color and black will be rendered as your background color.)
|
||||||
|
|
||||||
|
3. Set the output format to an image string, copy it into the [IDE](https://www.espruino.com/ide/), and set the destination to a file in storage. The file name should begin with "gal-" (without the quotes) and end with ".img" (without the quotes) to appear in the gallery. Note that the gal- prefix and .img extension will be removed in the UI. Upload the file.
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
const storage = require('Storage');
|
||||||
|
|
||||||
|
let imageFiles = storage.list(/^gal-.*\.img/).sort();
|
||||||
|
|
||||||
|
let imageMenu = { '': { 'title': 'Gallery' } };
|
||||||
|
|
||||||
|
for (let fileName of imageFiles) {
|
||||||
|
let displayName = fileName.substr(4, fileName.length - 8); // Trim off the 'gal-' and '.img' for a friendly display name
|
||||||
|
imageMenu[displayName] = eval(`() => { drawImage("${fileName}"); }`); // Unfortunately, eval is the only reasonable way to do this
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedOptions = Bangle.getOptions(); // We will change the backlight and timeouts later, and need to restore them when displaying the menu
|
||||||
|
let backlightSetting = storage.readJSON('setting.json').brightness; // LCD brightness is not included in there for some reason
|
||||||
|
|
||||||
|
let angle = 0; // Store the angle of rotation
|
||||||
|
let image; // Cache the image here because we access it in multiple places
|
||||||
|
|
||||||
|
function drawMenu() {
|
||||||
|
Bangle.removeListener('touch', drawMenu); // We no longer want touching to reload the menu
|
||||||
|
Bangle.setOptions(cachedOptions); // The drawImage function set no timeout, undo that
|
||||||
|
Bangle.setLCDBrightness(backlightSetting); // Restore backlight
|
||||||
|
image = undefined; // Delete the image from memory
|
||||||
|
|
||||||
|
E.showMenu(imageMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawImage(fileName) {
|
||||||
|
E.showMenu(); // Remove the menu to prevent it from breaking things
|
||||||
|
setTimeout(() => { Bangle.on('touch', drawMenu); }, 300); // Touch the screen to go back to the image menu (300ms timeout to allow user to lift finger)
|
||||||
|
Bangle.setOptions({ // Disable display power saving while showing the image
|
||||||
|
lockTimeout: 0,
|
||||||
|
lcdPowerTimeout: 0,
|
||||||
|
backlightTimeout: 0
|
||||||
|
});
|
||||||
|
Bangle.setLCDBrightness(1); // Full brightness
|
||||||
|
|
||||||
|
image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this
|
||||||
|
g.clear().reset().drawImage(image, 88, 88, { rotate: angle });
|
||||||
|
}
|
||||||
|
|
||||||
|
setWatch(info => {
|
||||||
|
if (image) {
|
||||||
|
if (angle == 0) angle = Math.PI;
|
||||||
|
else angle = 0;
|
||||||
|
Bangle.buzz();
|
||||||
|
|
||||||
|
g.clear().reset().drawImage(image, 88, 88, { rotate: angle })
|
||||||
|
}
|
||||||
|
}, BTN1, { repeat: true });
|
||||||
|
|
||||||
|
// We don't load the widgets because there is no reasonable way to unload them
|
||||||
|
drawMenu();
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgIOLgf/AAX8Av4FBJgkMAos/CIfMAv4Fe4AF/Apq5EAAw"))
|
||||||
|
After Width: | Height: | Size: 249 B |
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"id": "gallery",
|
||||||
|
"name": "Gallery",
|
||||||
|
"version": "0.02",
|
||||||
|
"description": "A gallery that lets you view images uploaded with the IDE (see README)",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tools",
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS2",
|
||||||
|
"BANGLEJS"
|
||||||
|
],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"name": "gallery.app.js",
|
||||||
|
"url": "app.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gallery.img",
|
||||||
|
"url": "icon.js",
|
||||||
|
"evaluate": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -6,3 +6,5 @@
|
||||||
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||||
0.06: Fix waypoint menu always selecting last waypoint
|
0.06: Fix waypoint menu always selecting last waypoint
|
||||||
Fix widget adding listeners more than once
|
Fix widget adding listeners more than once
|
||||||
|
0.07: Show checkered flag for target markers
|
||||||
|
Single waypoints are now shown in the compass view
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Tapping or button to switch to the next information display, swipe right for the
|
||||||
|
|
||||||
Choose either a route or a waypoint as basis for the display.
|
Choose either a route or a waypoint as basis for the display.
|
||||||
|
|
||||||
After this selection and availability of a GPS fix the compass will show a blue dot for your destination and a green one for possibly available waypoints on the way.
|
After this selection and availability of a GPS fix the compass will show a checkered flag for your destination and a green dot for possibly available waypoints on the way.
|
||||||
Waypoints are shown with name if available and distance to waypoint.
|
Waypoints are shown with name if available and distance to waypoint.
|
||||||
|
|
||||||
As long as no GPS signal is available the compass shows the heading from the build in magnetometer. When a GPS fix becomes available, the compass display shows the GPS course. This can be differentiated by the display of bubble levels on top and sides of the compass.
|
As long as no GPS signal is available the compass shows the heading from the build in magnetometer. When a GPS fix becomes available, the compass display shows the GPS course. This can be differentiated by the display of bubble levels on top and sides of the compass.
|
||||||
|
|
|
||||||
|
|
@ -239,8 +239,14 @@ function getCompassSlice(compassDataSource){
|
||||||
} else {
|
} else {
|
||||||
bpos=Math.round(bpos*increment);
|
bpos=Math.round(bpos*increment);
|
||||||
}
|
}
|
||||||
graphics.setColor(p.color);
|
if (p.color){
|
||||||
graphics.fillCircle(bpos,y+height-12,Math.floor(width*0.03));
|
graphics.setColor(p.color);
|
||||||
|
}
|
||||||
|
if (p.icon){
|
||||||
|
graphics.drawImage(p.icon, bpos,y+height-12, {rotate:0,scale:2});
|
||||||
|
} else {
|
||||||
|
graphics.fillCircle(bpos,y+height-12,Math.floor(width*0.03));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (compassDataSource.getMarkers){
|
if (compassDataSource.getMarkers){
|
||||||
|
|
@ -595,8 +601,8 @@ function showBackgroundMenu(){
|
||||||
"title" : "Background",
|
"title" : "Background",
|
||||||
back : showMenu,
|
back : showMenu,
|
||||||
},
|
},
|
||||||
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {E.showMenu(mainmenu);}});},
|
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||||
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {E.showMenu(mainmenu);}});},
|
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||||
};
|
};
|
||||||
E.showMenu(menu);
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
@ -677,13 +683,15 @@ function setClosestWaypoint(route, startindex, progress){
|
||||||
|
|
||||||
let screen = 1;
|
let screen = 1;
|
||||||
|
|
||||||
|
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
|
||||||
|
|
||||||
const compassSliceData = {
|
const compassSliceData = {
|
||||||
getCourseType: function(){
|
getCourseType: function(){
|
||||||
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
||||||
},
|
},
|
||||||
getCourse: function (){
|
getCourse: function (){
|
||||||
if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course;
|
if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course;
|
||||||
return state.compassHeading?360-state.compassHeading:undefined;
|
return state.compassHeading?state.compassHeading:undefined;
|
||||||
},
|
},
|
||||||
getPoints: function (){
|
getPoints: function (){
|
||||||
let points = [];
|
let points = [];
|
||||||
|
|
@ -691,7 +699,10 @@ const compassSliceData = {
|
||||||
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
||||||
}
|
}
|
||||||
if (state.currentPos && state.currentPos.lon && state.route){
|
if (state.currentPos && state.currentPos.lon && state.route){
|
||||||
points.push({bearing:bearing(state.currentPos, getLast(state.route)), color:"#00f"});
|
points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon});
|
||||||
|
}
|
||||||
|
if (state.currentPos && state.currentPos.lon && state.waypoint){
|
||||||
|
points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon});
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "gpstrek",
|
"id": "gpstrek",
|
||||||
"name": "GPS Trekking",
|
"name": "GPS Trekking",
|
||||||
"version": "0.06",
|
"version": "0.07",
|
||||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -24,7 +24,7 @@ function onGPS(fix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMag(e) {
|
function onMag(e) {
|
||||||
if (!state.compassHeading) state.compassHeading = 360-e.heading;
|
if (!state.compassHeading) state.compassHeading = e.heading;
|
||||||
|
|
||||||
//if (a+180)mod 360 == b then
|
//if (a+180)mod 360 == b then
|
||||||
//return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction)
|
//return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Henkinen
|
||||||
|
|
||||||
|
By Jukio Kallio
|
||||||
|
|
||||||
|
A tiny app helping you to breath and relax.
|
||||||
|
|
||||||
|

|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkEogA0/4AKCpNPCxYAB+gtTGJQuOGBAWPGAwuQGAwXH+cykc/C6UhgMSkMQiQXKBQsgiYFDmMCMBIIEmAWEDAUDC5nzBwogDMYgXHBoohJC4wuJEQwXG+ALDmUQgMjEYcPC5MhAYXxgAACj4ICVYYXGIwXzCwYABHAUwC5HyEwXwC4pEC+MvC4/xEoUQC4sBHIQlCC4vwIxBIEGYQXFmJKCC45ECfQQXIRoiRGC5EiOxB4EBwQXdI653XU67XX+QJCPAwrC+JKCC4v/gZIIHIUwCAQXGkIDCSIg4C/8SC5PwEwX/mUQgMjAwXzJQQXH+ZICAA8wEYYXGBgoAEEQoXHGBIhFC44OBcgQADmIgFC5H/kAYEmMCBooXDp4KFkMBiUhiCjDAAX0C5RjBmUjPo4XMABQXEMAwALCwgwRFwowRCwwwPFw4xOCpIArA"))
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
// Henkinen
|
||||||
|
//
|
||||||
|
// Bangle.js 2 breathing helper
|
||||||
|
// by Jukio Kallio
|
||||||
|
// www.jukiokallio.com
|
||||||
|
|
||||||
|
require("FontHaxorNarrow7x17").add(Graphics);
|
||||||
|
|
||||||
|
// settings
|
||||||
|
const breath = {
|
||||||
|
theme: "default",
|
||||||
|
x:0, y:0, w:0, h:0,
|
||||||
|
size: 60,
|
||||||
|
|
||||||
|
bgcolor: g.theme.bg,
|
||||||
|
incolor: g.theme.fg,
|
||||||
|
keepcolor: g.theme.fg,
|
||||||
|
outcolor: g.theme.fg,
|
||||||
|
|
||||||
|
font: "HaxorNarrow7x17", fontsize: 1,
|
||||||
|
textcolor: g.theme.fg,
|
||||||
|
texty: 18,
|
||||||
|
|
||||||
|
in: 4000,
|
||||||
|
keep: 7000,
|
||||||
|
out: 8000
|
||||||
|
};
|
||||||
|
|
||||||
|
// set some additional settings
|
||||||
|
breath.w = g.getWidth(); // size of the background
|
||||||
|
breath.h = g.getHeight();
|
||||||
|
breath.x = breath.w * 0.5; // position of the circles
|
||||||
|
breath.y = breath.h * 0.45;
|
||||||
|
breath.texty = breath.y + breath.size + breath.texty; // text position
|
||||||
|
|
||||||
|
var wait = 100; // wait time, normally a minute
|
||||||
|
var time = 0; // for time keeping
|
||||||
|
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}, wait - (Date.now() % wait));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main function
|
||||||
|
function draw() {
|
||||||
|
// make date object
|
||||||
|
var date = new Date();
|
||||||
|
|
||||||
|
// update current time
|
||||||
|
time += wait - (Date.now() % wait);
|
||||||
|
if (time > breath.in + breath.keep + breath.out) time = 0; // reset time
|
||||||
|
|
||||||
|
// Reset the state of the graphics library
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
// Clear the area where we want to draw the time
|
||||||
|
g.setColor(breath.bgcolor);
|
||||||
|
g.fillRect(0, 0, breath.w, breath.h);
|
||||||
|
|
||||||
|
// calculate circle size
|
||||||
|
var circle = 0;
|
||||||
|
if (time < breath.in) {
|
||||||
|
// breath in
|
||||||
|
circle = time / breath.in;
|
||||||
|
g.setColor(breath.incolor);
|
||||||
|
|
||||||
|
} else if (time < breath.in + breath.keep) {
|
||||||
|
// keep breath
|
||||||
|
circle = 1;
|
||||||
|
g.setColor(breath.keepcolor);
|
||||||
|
|
||||||
|
} else if (time < breath.in + breath.keep + breath.out) {
|
||||||
|
// breath out
|
||||||
|
circle = ((breath.in + breath.keep + breath.out) - time) / breath.out;
|
||||||
|
g.setColor(breath.outcolor);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw breath circle
|
||||||
|
g.fillCircle(breath.x, breath.y, breath.size * circle);
|
||||||
|
|
||||||
|
// breath area
|
||||||
|
g.setColor(breath.textcolor);
|
||||||
|
g.drawCircle(breath.x, breath.y, breath.size);
|
||||||
|
|
||||||
|
// draw text
|
||||||
|
g.setFontAlign(0,0).setFont(breath.font, breath.fontsize).setColor(breath.textcolor);
|
||||||
|
|
||||||
|
if (time < breath.in) {
|
||||||
|
// breath in
|
||||||
|
g.drawString("Breath in", breath.x, breath.texty);
|
||||||
|
|
||||||
|
} else if (time < breath.in + breath.keep) {
|
||||||
|
// keep breath
|
||||||
|
g.drawString("Keep it in", breath.x, breath.texty);
|
||||||
|
|
||||||
|
} else if (time < breath.in + breath.keep + breath.out) {
|
||||||
|
// breath out
|
||||||
|
g.drawString("Breath out", breath.x, breath.texty);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue draw
|
||||||
|
queueDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first
|
||||||
|
draw();
|
||||||
|
|
||||||
|
|
||||||
|
// keep LCD on
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ "id": "henkinen",
|
||||||
|
"name": "Henkinen - Tiny Breathing Helper",
|
||||||
|
"shortName":"Henkinen",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A tiny app helping you to breath and relax.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot1.png"}],
|
||||||
|
"tags": "outdoors",
|
||||||
|
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"henkinen.app.js","url":"app.js"},
|
||||||
|
{"name":"henkinen.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -8,3 +8,9 @@
|
||||||
Add swipe-to-exit
|
Add swipe-to-exit
|
||||||
0.08: Only use fast loading for switching to clock to prevent problems in full screen apps
|
0.08: Only use fast loading for switching to clock to prevent problems in full screen apps
|
||||||
0.09: Remove fast load option since clocks containing Bangle.loadWidgets are now always normally loaded
|
0.09: Remove fast load option since clocks containing Bangle.loadWidgets are now always normally loaded
|
||||||
|
0.10: changed the launch.json file name in iconlaunch.json ( launch.cache.json -> iconlaunch.cache.json)
|
||||||
|
used Object.assing for the settings
|
||||||
|
fix cache not deleted when "showClocks" options is changed
|
||||||
|
added timeOut to return to the clock
|
||||||
|
0.11: Cleanup timeout when changing to clock
|
||||||
|
Reset timeout on swipe and drag
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,20 @@
|
||||||
{
|
{
|
||||||
const s = require("Storage");
|
const s = require("Storage");
|
||||||
const settings = s.readJSON("launch.json", true) || { showClocks: true, fullscreen: false,direct:false,swipeExit:false,oneClickExit:false};
|
const settings = Object.assign({
|
||||||
|
showClocks: true,
|
||||||
|
fullscreen: false,
|
||||||
|
direct: false,
|
||||||
|
oneClickExit: false,
|
||||||
|
swipeExit: false,
|
||||||
|
timeOut:"Off"
|
||||||
|
}, s.readJSON("iconlaunch.json", true) || {});
|
||||||
|
|
||||||
if (!settings.fullscreen) {
|
if (!settings.fullscreen) {
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
}
|
}
|
||||||
let launchCache = s.readJSON("launch.cache.json", true)||{};
|
let launchCache = s.readJSON("iconlaunch.cache.json", true)||{};
|
||||||
let launchHash = require("Storage").hash(/\.info/);
|
let launchHash = s.hash(/\.info/);
|
||||||
if (launchCache.hash!=launchHash) {
|
if (launchCache.hash!=launchHash) {
|
||||||
launchCache = {
|
launchCache = {
|
||||||
hash : launchHash,
|
hash : launchHash,
|
||||||
|
|
@ -20,7 +28,7 @@
|
||||||
if (a.name>b.name) return 1;
|
if (a.name>b.name) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}) };
|
}) };
|
||||||
s.writeJSON("launch.cache.json", launchCache);
|
s.writeJSON("iconlaunch.cache.json", launchCache);
|
||||||
}
|
}
|
||||||
let scroll = 0;
|
let scroll = 0;
|
||||||
let selectedItem = -1;
|
let selectedItem = -1;
|
||||||
|
|
@ -124,6 +132,7 @@
|
||||||
g.flip();
|
g.flip();
|
||||||
const itemsN = Math.ceil(launchCache.apps.length / appsN);
|
const itemsN = Math.ceil(launchCache.apps.length / appsN);
|
||||||
let onDrag = function(e) {
|
let onDrag = function(e) {
|
||||||
|
updateTimeout();
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
g.setBgColor(g.theme.bg);
|
g.setBgColor(g.theme.bg);
|
||||||
let dy = e.dy;
|
let dy = e.dy;
|
||||||
|
|
@ -173,6 +182,7 @@
|
||||||
drag: onDrag,
|
drag: onDrag,
|
||||||
touch: (_, e) => {
|
touch: (_, e) => {
|
||||||
if (e.y < R.y - 4) return;
|
if (e.y < R.y - 4) return;
|
||||||
|
updateTimeout();
|
||||||
let i = YtoIdx(e.y);
|
let i = YtoIdx(e.y);
|
||||||
selectItem(i, e);
|
selectItem(i, e);
|
||||||
},
|
},
|
||||||
|
|
@ -193,11 +203,23 @@
|
||||||
delete idxToY;
|
delete idxToY;
|
||||||
delete YtoIdx;
|
delete YtoIdx;
|
||||||
delete settings;
|
delete settings;
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
setTimeout(eval, 0, s.read(".bootcde"));
|
setTimeout(eval, 0, s.read(".bootcde"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (settings.oneClickExit) mode.btn = returnToClock;
|
if (settings.oneClickExit) mode.btn = returnToClock;
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
const updateTimeout = function(){
|
||||||
|
if (settings.timeOut!="Off"){
|
||||||
|
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(returnToClock,time*1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeout();
|
||||||
|
|
||||||
Bangle.setUI(mode);
|
Bangle.setUI(mode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "iconlaunch",
|
"id": "iconlaunch",
|
||||||
"name": "Icon Launcher",
|
"name": "Icon Launcher",
|
||||||
"shortName" : "Icon launcher",
|
"shortName" : "Icon launcher",
|
||||||
"version": "0.09",
|
"version": "0.11",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||||
"tags": "tool,system,launcher",
|
"tags": "tool,system,launcher",
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
{ "name": "iconlaunch.app.js", "url": "app.js" },
|
{ "name": "iconlaunch.app.js", "url": "app.js" },
|
||||||
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
|
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
|
||||||
],
|
],
|
||||||
|
"data": [{"name":"iconlaunch.json"},{"name":"iconlaunch.cache.json"}],
|
||||||
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],
|
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],
|
||||||
"readme": "README.md"
|
"readme": "README.md"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,29 @@
|
||||||
// make sure to enclose the function in parentheses
|
// make sure to enclose the function in parentheses
|
||||||
(function(back) {
|
(function(back) {
|
||||||
|
const s = require("Storage");
|
||||||
let settings = Object.assign({
|
let settings = Object.assign({
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
direct: false,
|
direct: false,
|
||||||
oneClickExit: false,
|
oneClickExit: false,
|
||||||
swipeExit: false
|
swipeExit: false,
|
||||||
}, require("Storage").readJSON("launch.json", true) || {});
|
timeOut:"Off"
|
||||||
|
}, s.readJSON("iconlaunch.json", true) || {});
|
||||||
|
|
||||||
let fonts = g.getFonts();
|
|
||||||
function save(key, value) {
|
function save(key, value) {
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
require("Storage").write("launch.json",settings);
|
s.write("iconlaunch.json",settings);
|
||||||
}
|
}
|
||||||
|
const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
|
||||||
const appMenu = {
|
const appMenu = {
|
||||||
"": { "title": /*LANG*/"Launcher" },
|
"": { "title": /*LANG*/"Launcher" },
|
||||||
/*LANG*/"< Back": back,
|
/*LANG*/"< Back": back,
|
||||||
/*LANG*/"Show Clocks": {
|
/*LANG*/"Show Clocks": {
|
||||||
value: settings.showClocks == true,
|
value: settings.showClocks == true,
|
||||||
onchange: (m) => { save("showClocks", m) }
|
onchange: (m) => {
|
||||||
|
save("showClocks", m);
|
||||||
|
s.erase("iconlaunch.cache.json"); //delete the cache app list
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/*LANG*/"Fullscreen": {
|
/*LANG*/"Fullscreen": {
|
||||||
value: settings.fullscreen == true,
|
value: settings.fullscreen == true,
|
||||||
|
|
@ -35,7 +40,15 @@
|
||||||
/*LANG*/"Swipe exit": {
|
/*LANG*/"Swipe exit": {
|
||||||
value: settings.swipeExit == true,
|
value: settings.swipeExit == true,
|
||||||
onchange: m => { save("swipeExit", m) }
|
onchange: m => { save("swipeExit", m) }
|
||||||
}
|
},
|
||||||
|
/*LANG*/'Time Out': {
|
||||||
|
value: timeOutChoices.indexOf(settings.timeOut),
|
||||||
|
min: 0, max: timeOutChoices.length-1,
|
||||||
|
format: v => timeOutChoices[v],
|
||||||
|
onchange: m => {
|
||||||
|
save("timeOut", timeOutChoices[m]);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
E.showMenu(appMenu);
|
E.showMenu(appMenu);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,7 @@
|
||||||
0.10: Fix clock not correctly refreshing when drawing in timeouts option is not on
|
0.10: Fix clock not correctly refreshing when drawing in timeouts option is not on
|
||||||
0.11: Additional option in customizer to force drawing directly
|
0.11: Additional option in customizer to force drawing directly
|
||||||
Fix some problems in handling timeouts
|
Fix some problems in handling timeouts
|
||||||
|
0.12: Use widget_utils module
|
||||||
|
Fix colorsetting in promises in generated code
|
||||||
|
Some performance improvements by caching lookups
|
||||||
|
Activate UI after first draw is complete to prevent drawing over launcher
|
||||||
|
|
|
||||||
|
|
@ -202,27 +202,39 @@ let firstDraw = true;
|
||||||
let firstDigitY = element.Y;
|
let firstDigitY = element.Y;
|
||||||
let imageIndex = element.ImageIndex ? element.ImageIndex : 0;
|
let imageIndex = element.ImageIndex ? element.ImageIndex : 0;
|
||||||
|
|
||||||
let firstImage;
|
let firstImage = element.cachedFirstImage;
|
||||||
if (imageIndex){
|
if (!firstImage && !element.cachedFirstImageMissing){
|
||||||
firstImage = getByPath(resources, [], "" + (0 + imageIndex));
|
if (imageIndex){
|
||||||
} else {
|
firstImage = getByPath(resources, [], "" + (0 + imageIndex));
|
||||||
firstImage = getByPath(resources, element.ImagePath, 0);
|
} else {
|
||||||
|
firstImage = getByPath(resources, element.ImagePath, 0);
|
||||||
|
}
|
||||||
|
element.cachedFirstImage = firstImage;
|
||||||
|
if (!firstImage) element.cachedFirstImageMissing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let minusImage;
|
let minusImage = element.cachedMinusImage;
|
||||||
if (imageIndexMinus){
|
if (!minusImage && !element.cachedMinusImageMissing){
|
||||||
minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus));
|
if (imageIndexMinus){
|
||||||
} else {
|
minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus));
|
||||||
minusImage = getByPath(resources, element.ImagePath, "minus");
|
} else {
|
||||||
|
minusImage = getByPath(resources, element.ImagePath, "minus");
|
||||||
|
}
|
||||||
|
element.cachedMinusImage = minusImage;
|
||||||
|
if (!minusImage) element.cachedMinusImageMissing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let unitImage;
|
let unitImage = element.cachedUnitImage;
|
||||||
//print("Get image for unit", imageIndexUnit);
|
//print("Get image for unit", imageIndexUnit);
|
||||||
if (imageIndexUnit !== undefined){
|
if (!unitImage && !element.cachedUnitImageMissing){
|
||||||
unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit));
|
if (imageIndexUnit !== undefined){
|
||||||
//print("Unit image is", unitImage);
|
unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit));
|
||||||
} else if (element.Unit){
|
//print("Unit image is", unitImage);
|
||||||
unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown"));
|
} else if (element.Unit){
|
||||||
|
unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown"));
|
||||||
|
}
|
||||||
|
unitImage = element.cachedUnitImage;
|
||||||
|
if (!unitImage) element.cachedUnitImageMissing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing);
|
let numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing);
|
||||||
|
|
@ -292,14 +304,7 @@ let firstDraw = true;
|
||||||
if (resource){
|
if (resource){
|
||||||
prepareImg(resource);
|
prepareImg(resource);
|
||||||
//print("lastElem", typeof resource)
|
//print("lastElem", typeof resource)
|
||||||
if (resource) {
|
element.cachedImage[cacheKey] = resource;
|
||||||
element.cachedImage[cacheKey] = resource;
|
|
||||||
//print("cache res ",typeof element.cachedImage[cacheKey]);
|
|
||||||
} else {
|
|
||||||
element.cachedImage[cacheKey] = null;
|
|
||||||
//print("cache null",typeof element.cachedImage[cacheKey]);
|
|
||||||
//print("Could not create image from", resource);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//print("Could not get resource from", element, lastElem);
|
//print("Could not get resource from", element, lastElem);
|
||||||
}
|
}
|
||||||
|
|
@ -604,18 +609,15 @@ let firstDraw = true;
|
||||||
|
|
||||||
promise.then(()=>{
|
promise.then(()=>{
|
||||||
let currentDrawingTime = Date.now();
|
let currentDrawingTime = Date.now();
|
||||||
if (showWidgets && global.WIDGETS){
|
if (showWidgets){
|
||||||
//print("Draw widgets");
|
|
||||||
restoreWidgetDraw();
|
restoreWidgetDraw();
|
||||||
Bangle.drawWidgets();
|
|
||||||
g.setColor(g.theme.fg);
|
|
||||||
g.drawLine(0,24,g.getWidth(),24);
|
|
||||||
}
|
}
|
||||||
lastDrawTime = Date.now() - start;
|
lastDrawTime = Date.now() - start;
|
||||||
isDrawing=false;
|
isDrawing=false;
|
||||||
firstDraw=false;
|
firstDraw=false;
|
||||||
requestRefresh = false;
|
requestRefresh = false;
|
||||||
endPerfLog("initialDraw");
|
endPerfLog("initialDraw");
|
||||||
|
if (!Bangle.uiRemove) setUi();
|
||||||
}).catch((e)=>{
|
}).catch((e)=>{
|
||||||
print("Error during drawing", e);
|
print("Error during drawing", e);
|
||||||
});
|
});
|
||||||
|
|
@ -751,30 +753,19 @@ let firstDraw = true;
|
||||||
|
|
||||||
|
|
||||||
let showWidgetsChanged = false;
|
let showWidgetsChanged = false;
|
||||||
let currentDragDistance = 0;
|
|
||||||
|
|
||||||
let restoreWidgetDraw = function(){
|
let restoreWidgetDraw = function(){
|
||||||
if (global.WIDGETS) {
|
require("widget_utils").show();
|
||||||
for (let w in global.WIDGETS) {
|
Bangle.drawWidgets();
|
||||||
let wd = global.WIDGETS[w];
|
|
||||||
wd.draw = originalWidgetDraw[w];
|
|
||||||
wd.area = originalWidgetArea[w];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let handleDrag = function(e){
|
let handleSwipe = function(lr, ud){
|
||||||
//print("handleDrag");
|
if (!showWidgets && ud == 1){
|
||||||
currentDragDistance += e.dy;
|
|
||||||
if (Math.abs(currentDragDistance) < 10) return;
|
|
||||||
dragDown = currentDragDistance > 0;
|
|
||||||
currentDragDistance = 0;
|
|
||||||
if (!showWidgets && dragDown){
|
|
||||||
//print("Enable widgets");
|
//print("Enable widgets");
|
||||||
restoreWidgetDraw();
|
restoreWidgetDraw();
|
||||||
showWidgetsChanged = true;
|
showWidgetsChanged = true;
|
||||||
}
|
}
|
||||||
if (showWidgets && !dragDown){
|
if (showWidgets && ud == -1){
|
||||||
//print("Disable widgets");
|
//print("Disable widgets");
|
||||||
clearWidgetsDraw();
|
clearWidgetsDraw();
|
||||||
firstDraw = true;
|
firstDraw = true;
|
||||||
|
|
@ -783,12 +774,12 @@ let firstDraw = true;
|
||||||
if (showWidgetsChanged){
|
if (showWidgetsChanged){
|
||||||
showWidgetsChanged = false;
|
showWidgetsChanged = false;
|
||||||
//print("Draw after widget change");
|
//print("Draw after widget change");
|
||||||
showWidgets = dragDown;
|
showWidgets = ud == 1;
|
||||||
initialDraw();
|
initialDraw();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bangle.on('drag', handleDrag);
|
Bangle.on('swipe', handleSwipe);
|
||||||
|
|
||||||
if (!events || events.includes("pressure")){
|
if (!events || events.includes("pressure")){
|
||||||
Bangle.on('pressure', handlePressure);
|
Bangle.on('pressure', handlePressure);
|
||||||
|
|
@ -814,62 +805,54 @@ let firstDraw = true;
|
||||||
|
|
||||||
let clearWidgetsDraw = function(){
|
let clearWidgetsDraw = function(){
|
||||||
//print("Clear widget draw calls");
|
//print("Clear widget draw calls");
|
||||||
if (global.WIDGETS) {
|
require("widget_utils").hide();
|
||||||
originalWidgetDraw = {};
|
|
||||||
originalWidgetArea = {};
|
|
||||||
for (let w in global.WIDGETS) {
|
|
||||||
let wd = global.WIDGETS[w];
|
|
||||||
originalWidgetDraw[w] = wd.draw;
|
|
||||||
originalWidgetArea[w] = wd.area;
|
|
||||||
wd.draw = () => {};
|
|
||||||
wd.area = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLock(Bangle.isLocked(), true);
|
handleLock(Bangle.isLocked(), true);
|
||||||
|
|
||||||
Bangle.setUI({
|
let setUi = function(){
|
||||||
mode : "clock",
|
Bangle.setUI({
|
||||||
remove : function() {
|
mode : "clock",
|
||||||
//print("remove calls");
|
remove : function() {
|
||||||
// Called to unload all of the clock app
|
//print("remove calls");
|
||||||
Bangle.setHRMPower(0, "imageclock");
|
// Called to unload all of the clock app
|
||||||
Bangle.setBarometerPower(0, 'imageclock');
|
Bangle.setHRMPower(0, "imageclock");
|
||||||
|
Bangle.setBarometerPower(0, 'imageclock');
|
||||||
|
|
||||||
Bangle.removeListener('drag', handleDrag);
|
Bangle.removeListener('swipe', handleSwipe);
|
||||||
Bangle.removeListener('lock', handleLock);
|
Bangle.removeListener('lock', handleLock);
|
||||||
Bangle.removeListener('charging', handleCharging);
|
Bangle.removeListener('charging', handleCharging);
|
||||||
Bangle.removeListener('HRM', handleHrm);
|
Bangle.removeListener('HRM', handleHrm);
|
||||||
Bangle.removeListener('pressure', handlePressure);
|
Bangle.removeListener('pressure', handlePressure);
|
||||||
|
|
||||||
if (deferredTimout) clearTimeout(deferredTimout);
|
if (deferredTimout) clearTimeout(deferredTimout);
|
||||||
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
|
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
|
||||||
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
|
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
|
||||||
|
|
||||||
for (let i of unlockedDrawInterval){
|
for (let i of global.unlockedDrawInterval){
|
||||||
//print("Clearing unlocked", i);
|
//print("Clearing unlocked", i);
|
||||||
clearInterval(i);
|
clearInterval(i);
|
||||||
|
}
|
||||||
|
delete global.unlockedDrawInterval;
|
||||||
|
for (let i of global.lockedDrawInterval){
|
||||||
|
//print("Clearing locked", i);
|
||||||
|
clearInterval(i);
|
||||||
|
}
|
||||||
|
delete global.lockedDrawInterval;
|
||||||
|
delete global.showWidgets;
|
||||||
|
delete global.firstDraw;
|
||||||
|
|
||||||
|
delete Bangle.printPerfLog;
|
||||||
|
if (settings.perflog){
|
||||||
|
delete Bangle.resetPerfLog;
|
||||||
|
delete performanceLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupDelays();
|
||||||
|
restoreWidgetDraw();
|
||||||
}
|
}
|
||||||
delete unlockedDrawInterval;
|
});
|
||||||
for (let i of lockedDrawInterval){
|
}
|
||||||
//print("Clearing locked", i);
|
|
||||||
clearInterval(i);
|
|
||||||
}
|
|
||||||
delete lockedDrawInterval;
|
|
||||||
delete showWidgets;
|
|
||||||
delete firstDraw;
|
|
||||||
|
|
||||||
delete Bangle.printPerfLog;
|
|
||||||
if (settings.perflog){
|
|
||||||
delete Bangle.resetPerfLog;
|
|
||||||
delete performanceLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupDelays();
|
|
||||||
restoreWidgetDraw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
clearWidgetsDraw();
|
clearWidgetsDraw();
|
||||||
|
|
|
||||||
|
|
@ -714,13 +714,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n";
|
if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n";
|
||||||
code += "" + colorsetting;
|
|
||||||
code += (condition.length > 0 ? "if (" + condition + "){\n" : "");
|
code += (condition.length > 0 ? "if (" + condition + "){\n" : "");
|
||||||
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
|
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
|
||||||
code += "p = p.then(()=>delay(0)).then(()=>{\n";
|
code += "p = p.then(()=>delay(0)).then(()=>{\n";
|
||||||
} else {
|
} else {
|
||||||
code += "p = p.then(()=>{\n";
|
code += "p = p.then(()=>{\n";
|
||||||
}
|
}
|
||||||
|
code += "" + colorsetting;
|
||||||
if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n";
|
if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n";
|
||||||
code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n";
|
code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "imageclock",
|
"id": "imageclock",
|
||||||
"name": "Imageclock",
|
"name": "Imageclock",
|
||||||
"shortName": "Imageclock",
|
"shortName": "Imageclock",
|
||||||
"version": "0.11",
|
"version": "0.12",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
|
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: New app!
|
||||||
|
0.02-0.07: Bug fixes
|
||||||
|
0.08: Submitted to the app loader
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Informational clock
|
||||||
|
|
||||||
|
A configurable clock with extra info and shortcuts when unlocked, but large time when locked
|
||||||
|
|
||||||
|
## Information
|
||||||
|
|
||||||
|
The clock has two different screen arrangements, depending on whether the watch is locked or unlocked. The most commonly viewed piece of information is the time, so when the watch is locked it optimizes for the time being visible at a glance without the backlight. The hours and minutes take up nearly the entire top half of the display, with the date and seconds taking up nearly the entire bottom half. The day progress bar is between them if enabled, unless configured to be on the bottom row. The bottom row can be configured to display a weather summary, step count, step count and heart rate, the daily progress bar, or nothing.
|
||||||
|
|
||||||
|
When the watch is unlocked, it can be assumed that the backlight is on and the user is actively looking at the watch, so instead we can optimize for information density. The bottom half of the display becomes shortcuts, and the top half of the display becomes 4 rows of information (date and time, step count and heart rate, 2 line weather summary) + an optional daily progress bar. (The daily progress bar can be independently enabled when locked and unlocked.)
|
||||||
|
|
||||||
|
Most things are self-explanatory, but the day progress bar might not be. The day progress bar is intended to show approximately how far through the day you are, in the form of a progress bar. You might want to configure it to show how far you are through your waking hours, or you might want to use it to show how far you are through your work or school day.
|
||||||
|
|
||||||
|
## Shortcuts
|
||||||
|
|
||||||
|
There are generally a few apps that the user uses far more frequently than the others. For example, they might use a timer, alarm clock, and calculator every day, while everything else (such as the settings app) gets used only occasionally. This clock has space for 8 apps in the bottom half of the screen only one tap away, avoiding the need to wait for the launcher to open and then scroll through it. Tapping the top of the watch opens the launcher, eliminating the need for the button (which still opens the launcher due to bangle.js conventions). There is also handling for left, right, and vertical swipes. A vertical swipe by default opens the messages app, mimicking mobile operating systems which use a swipe down to view the notification shade.
|
||||||
|
|
||||||
|
## Configurability
|
||||||
|
|
||||||
|
Displaying the seconds allows for more precise timing, but waking up the CPU to refresh the display more often consumes battery. The user can enable or disable them completely, but can also configure them to be enabled or disabled automatically based on some hueristics:
|
||||||
|
|
||||||
|
* They can be hidden while the display is locked, if the user expects to unlock their watch when they need the seconds.
|
||||||
|
* They can be hidden when the battery is too low, to make the last portion of the battery last a little bit longer.
|
||||||
|
* They can be hidden during a period of time such as when the user is asleep and therefore unlikely to need very much precision.
|
||||||
|
|
||||||
|
The date format can be changed.
|
||||||
|
|
||||||
|
As described earlier, the contents of the bottom row when locked can be changed.
|
||||||
|
|
||||||
|
The 8 tap-based shortcuts on the bottom and the 3 swipe-based shortcuts can be changed to nothing, the launcher, or any app on the watch.
|
||||||
|
|
||||||
|
The start and end time of the day progress bar can be changed. It can be enabled or disabled separately when the watch is locked and unlocked. The color can be changed. The time when it resets from full to empty can be changed.
|
||||||
|
|
||||||
|
When the battery is below a defined point, the watch's color can change to another chosen color to help the user notice that the battery is low.
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
const SETTINGS_FILE = "infoclk.json";
|
||||||
|
const FONT = require('infoclk-font.js');
|
||||||
|
|
||||||
|
const storage = require("Storage");
|
||||||
|
const locale = require("locale");
|
||||||
|
const weather = require('weather');
|
||||||
|
|
||||||
|
let config = Object.assign({
|
||||||
|
seconds: {
|
||||||
|
// Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display.
|
||||||
|
// The seconds will be shown unless one of these conditions is enabled here, and currently true.
|
||||||
|
hideLocked: false, // Hide the seconds when the display is locked.
|
||||||
|
hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage.
|
||||||
|
hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds
|
||||||
|
hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes
|
||||||
|
hideEnd: 700, // The time when the seconds are shown again
|
||||||
|
hideAlways: false, // Always hide (never show) the seconds
|
||||||
|
},
|
||||||
|
|
||||||
|
date: {
|
||||||
|
// Settings related to the display of the date
|
||||||
|
mmdd: true, // If true, display the month first. If false, display the date first.
|
||||||
|
separator: '-', // The character that goes between the month and date
|
||||||
|
monthName: false, // If false, display the month as a number. If true, display the name.
|
||||||
|
monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name.
|
||||||
|
dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name.
|
||||||
|
},
|
||||||
|
|
||||||
|
bottomLocked: {
|
||||||
|
display: 'weather' // What to display in the bottom row when locked:
|
||||||
|
// 'weather': The current temperature and weather description
|
||||||
|
// 'steps': Step count
|
||||||
|
// 'health': Step count and bpm
|
||||||
|
// 'progress': Day progress bar
|
||||||
|
// false: Nothing
|
||||||
|
},
|
||||||
|
|
||||||
|
shortcuts: [
|
||||||
|
//8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked
|
||||||
|
// false = no shortcut
|
||||||
|
// '#LAUNCHER' = open the launcher
|
||||||
|
// any other string = name of app to open
|
||||||
|
'stlap', 'keytimer', 'pomoplus', 'alarm',
|
||||||
|
'rpnsci', 'calendar', 'torch', 'weather'
|
||||||
|
],
|
||||||
|
|
||||||
|
swipe: {
|
||||||
|
// 3 shortcuts to launch upon swiping:
|
||||||
|
// false = no shortcut
|
||||||
|
// '#LAUNCHER' = open the launcher
|
||||||
|
// any other string = name of app to open
|
||||||
|
up: 'messages', // Swipe up or swipe down, due to limitation of event handler
|
||||||
|
left: '#LAUNCHER',
|
||||||
|
right: '#LAUNCHER',
|
||||||
|
},
|
||||||
|
|
||||||
|
dayProgress: {
|
||||||
|
// A progress bar representing how far through the day you are
|
||||||
|
enabledLocked: true, // Whether this bar is enabled when the watch is locked
|
||||||
|
enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked
|
||||||
|
color: [0, 0, 1], // The color of the bar
|
||||||
|
start: 700, // The time of day that the bar starts filling
|
||||||
|
end: 2200, // The time of day that the bar becomes full
|
||||||
|
reset: 300 // The time of day when the progress bar resets from full to empty
|
||||||
|
},
|
||||||
|
|
||||||
|
lowBattColor: {
|
||||||
|
// The text can change color to indicate that the battery is low
|
||||||
|
level: 20, // The percentage where this happens
|
||||||
|
color: [1, 0, 0] // The color that the text changes to
|
||||||
|
}
|
||||||
|
}, storage.readJSON(SETTINGS_FILE));
|
||||||
|
|
||||||
|
// Return whether the given time (as a date object) is between start and end (as a number where the first 2 digits are hours on a 24 hour clock and the last 2 are minutes), with end time wrapping to next day if necessary
|
||||||
|
function timeInRange(start, time, end) {
|
||||||
|
|
||||||
|
// Convert the given date object to a time number
|
||||||
|
let timeNumber = time.getHours() * 100 + time.getMinutes();
|
||||||
|
|
||||||
|
// Normalize to prevent the numbers from wrapping around at midnight
|
||||||
|
if (end <= start) {
|
||||||
|
end += 2400;
|
||||||
|
if (timeNumber < start) timeNumber += 2400;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start <= timeNumber && timeNumber <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return whether settings should be displayed based on the user's configuration
|
||||||
|
function shouldDisplaySeconds(now) {
|
||||||
|
return !(
|
||||||
|
(config.seconds.hideAlways) ||
|
||||||
|
(config.seconds.hideLocked && Bangle.isLocked()) ||
|
||||||
|
(E.getBattery() <= config.seconds.hideBattery) ||
|
||||||
|
(config.seconds.hideTime && timeInRange(config.seconds.hideStart, now, config.seconds.hideEnd))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the font size needed to fit a string of the given length widthin maxWidth number of pixels, clamped between minSize and maxSize
|
||||||
|
function getFontSize(length, maxWidth, minSize, maxSize) {
|
||||||
|
let size = Math.floor(maxWidth / length); //Number of pixels of width available to character
|
||||||
|
size *= (20 / 12); //Convert to height, assuming 20 pixels of height for every 12 of width
|
||||||
|
|
||||||
|
// Clamp to within range
|
||||||
|
if (size < minSize) return minSize;
|
||||||
|
else if (size > maxSize) return maxSize;
|
||||||
|
else return Math.floor(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current day of the week according to user settings
|
||||||
|
function getDayString(now) {
|
||||||
|
if (config.date.dayFullName) return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][now.getDay()];
|
||||||
|
else return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][now.getDay()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad a number with zeros to be the given number of digits
|
||||||
|
function pad(number, digits) {
|
||||||
|
let result = '' + number;
|
||||||
|
while (result.length < digits) result = '0' + result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current date formatted according to the user settings
|
||||||
|
function getDateString(now) {
|
||||||
|
let month;
|
||||||
|
if (!config.date.monthName) month = pad(now.getMonth() + 1, 2);
|
||||||
|
else if (config.date.monthFullName) month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][now.getMonth()];
|
||||||
|
else month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.getMonth()];
|
||||||
|
|
||||||
|
if (config.date.mmdd) return `${month}${config.date.separator}${pad(now.getDate(), 2)}`;
|
||||||
|
else return `${pad(now.getDate(), 2)}${config.date.separator}${month}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a floating point number from 0 to 1 representing how far between the user-defined start and end points we are
|
||||||
|
function getDayProgress(now) {
|
||||||
|
let start = config.dayProgress.start;
|
||||||
|
let current = now.getHours() * 100 + now.getMinutes();
|
||||||
|
let end = config.dayProgress.end;
|
||||||
|
let reset = config.dayProgress.reset;
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
if (end <= start) end += 2400;
|
||||||
|
if (current < start) current += 2400;
|
||||||
|
if (reset < start) reset += 2400;
|
||||||
|
|
||||||
|
// Convert an hhmm number into a floating-point hours
|
||||||
|
function toDecimalHours(time) {
|
||||||
|
let hours = Math.floor(time / 100);
|
||||||
|
let minutes = time % 100;
|
||||||
|
|
||||||
|
return hours + (minutes / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = toDecimalHours(start);
|
||||||
|
current = toDecimalHours(current);
|
||||||
|
end = toDecimalHours(end);
|
||||||
|
reset = toDecimalHours(reset);
|
||||||
|
|
||||||
|
let progress = (current - start) / (end - start);
|
||||||
|
|
||||||
|
if (progress < 0 || progress > 1) {
|
||||||
|
if (current < reset) return 1;
|
||||||
|
else return 0;
|
||||||
|
} else {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a Gadgetbridge weather string
|
||||||
|
function getWeatherString() {
|
||||||
|
let current = weather.get();
|
||||||
|
if (current) return locale.temp(current.temp - 273.15) + ', ' + current.txt;
|
||||||
|
else return 'Weather unknown!';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a second weather row showing humidity, wind speed, and wind direction
|
||||||
|
function getWeatherRow2() {
|
||||||
|
let current = weather.get();
|
||||||
|
if (current) return `${current.hum}%, ${locale.speed(current.wind)} ${current.wrose}`;
|
||||||
|
else return 'Check Gadgetbridge';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a step string
|
||||||
|
function getStepsString() {
|
||||||
|
return '' + Bangle.getHealthStatus('day').steps + ' steps';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a health string including daily steps and recent bpm
|
||||||
|
function getHealthString() {
|
||||||
|
return `${Bangle.getHealthStatus('day').steps} steps ${Bangle.getHealthStatus('last').bpm} bpm`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the next timeout to draw the screen
|
||||||
|
let drawTimeout;
|
||||||
|
function setNextDrawTimeout() {
|
||||||
|
if (drawTimeout) {
|
||||||
|
clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let time;
|
||||||
|
let now = new Date();
|
||||||
|
if (shouldDisplaySeconds(now)) time = 1000 - (now.getTime() % 1000);
|
||||||
|
else time = 60000 - (now.getTime() % 60000);
|
||||||
|
|
||||||
|
drawTimeout = setTimeout(draw, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DIGIT_WIDTH = 40; // How much width is allocated for each digit, 37 pixels + 3 pixels of space (which will go off of the screen on the right edge)
|
||||||
|
const COLON_WIDTH = 19; // How much width is allocated for the colon, 16 pixels + 3 pixels of space
|
||||||
|
const HHMM_TOP = 27; // 24 pixels for widgets + 3 pixels of space
|
||||||
|
const DIGIT_HEIGHT = 64; // How tall the digits are
|
||||||
|
|
||||||
|
const SECONDS_TOP = HHMM_TOP + DIGIT_HEIGHT + 3; // The top edge of the seconds, top of hours and minutes + digit height + space
|
||||||
|
const SECONDS_LEFT = 2 * DIGIT_WIDTH + COLON_WIDTH; // The left edge of the seconds: displayed after 2 digits and the colon
|
||||||
|
const DATE_LETTER_HEIGHT = DIGIT_HEIGHT / 2; // Each letter of the day of week and date will be half the height of the time digits
|
||||||
|
|
||||||
|
const DATE_CENTER_X = SECONDS_LEFT / 2; // Day of week and date will be centered between left edge of screen and where seconds start
|
||||||
|
const DOW_CENTER_Y = SECONDS_TOP + (DATE_LETTER_HEIGHT / 2); // Day of week will be the top row
|
||||||
|
const DATE_CENTER_Y = DOW_CENTER_Y + DATE_LETTER_HEIGHT; // Date will be the bottom row
|
||||||
|
const DOW_DATE_CENTER_Y = SECONDS_TOP + (DIGIT_HEIGHT / 2); // When displaying both on one row, center it
|
||||||
|
const BOTTOM_CENTER_Y = ((SECONDS_TOP + DIGIT_HEIGHT + 3) + g.getHeight()) / 2;
|
||||||
|
|
||||||
|
// Draw the clock
|
||||||
|
function draw() {
|
||||||
|
//Prepare to draw
|
||||||
|
g.reset()
|
||||||
|
.setFontAlign(0, 0);
|
||||||
|
|
||||||
|
if (E.getBattery() <= config.lowBattColor.level) {
|
||||||
|
let color = config.lowBattColor.color;
|
||||||
|
g.setColor(color[0], color[1], color[2]);
|
||||||
|
}
|
||||||
|
now = new Date();
|
||||||
|
|
||||||
|
if (Bangle.isLocked()) { //When the watch is locked
|
||||||
|
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||||
|
|
||||||
|
//Draw the hours and minutes
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
for (let digit of locale.time(now, 1)) { //apparently this is how you get an hh:mm time string adjusting for the user's 12/24 hour preference
|
||||||
|
if (digit != ' ') g.drawImage(FONT[digit], x, HHMM_TOP);
|
||||||
|
if (digit == ':') x += COLON_WIDTH;
|
||||||
|
else x += DIGIT_WIDTH;
|
||||||
|
}
|
||||||
|
if (storage.readJSON('setting.json')['12hour']) g.drawImage(FONT[(now.getHours() < 12) ? 'am' : 'pm'], 0, HHMM_TOP);
|
||||||
|
|
||||||
|
//Draw the seconds if necessary
|
||||||
|
if (shouldDisplaySeconds(now)) {
|
||||||
|
let tens = Math.floor(now.getSeconds() / 10);
|
||||||
|
let ones = now.getSeconds() % 10;
|
||||||
|
g.drawImage(FONT[tens], SECONDS_LEFT, SECONDS_TOP)
|
||||||
|
.drawImage(FONT[ones], SECONDS_LEFT + DIGIT_WIDTH, SECONDS_TOP);
|
||||||
|
|
||||||
|
// Draw the day of week and date assuming the seconds are displayed
|
||||||
|
|
||||||
|
g.setFont('Vector', getFontSize(getDayString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||||
|
.drawString(getDayString(now), DATE_CENTER_X, DOW_CENTER_Y)
|
||||||
|
.setFont('Vector', getFontSize(getDateString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||||
|
.drawString(getDateString(now), DATE_CENTER_X, DATE_CENTER_Y);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Draw the day of week and date without the seconds
|
||||||
|
|
||||||
|
let string = getDayString(now) + ' ' + getDateString(now);
|
||||||
|
g.setFont('Vector', getFontSize(string.length, g.getWidth(), 6, DATE_LETTER_HEIGHT))
|
||||||
|
.drawString(string, g.getWidth() / 2, DOW_DATE_CENTER_Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the bottom area
|
||||||
|
if (config.bottomLocked.display == 'progress') {
|
||||||
|
let color = config.dayProgress.color;
|
||||||
|
g.setColor(color[0], color[1], color[2])
|
||||||
|
.fillRect(0, SECONDS_TOP + DIGIT_HEIGHT + 3, g.getWidth() * getDayProgress(now), g.getHeight());
|
||||||
|
} else {
|
||||||
|
let bottomString;
|
||||||
|
|
||||||
|
if (config.bottomLocked.display == 'weather') bottomString = getWeatherString();
|
||||||
|
else if (config.bottomLocked.display == 'steps') bottomString = getStepsString();
|
||||||
|
else if (config.bottomLocked.display == 'health') bottomString = getHealthString();
|
||||||
|
else bottomString = ' ';
|
||||||
|
|
||||||
|
g.setFont('Vector', getFontSize(bottomString.length, 176, 6, g.getHeight() - (SECONDS_TOP + DIGIT_HEIGHT + 3)))
|
||||||
|
.drawString(bottomString, g.getWidth() / 2, BOTTOM_CENTER_Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the day progress bar between the rows if necessary
|
||||||
|
if (config.dayProgress.enabledLocked && config.bottomLocked.display != 'progress') {
|
||||||
|
let color = config.dayProgress.color;
|
||||||
|
g.setColor(color[0], color[1], color[2])
|
||||||
|
.fillRect(0, HHMM_TOP + DIGIT_HEIGHT, g.getWidth() * getDayProgress(now), SECONDS_TOP);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//If the watch is unlocked
|
||||||
|
g.clearRect(0, 24, g.getWidth(), g.getHeight() / 2);
|
||||||
|
rows = [
|
||||||
|
`${getDayString(now)} ${getDateString(now)} ${locale.time(now, 1)}`,
|
||||||
|
getHealthString(),
|
||||||
|
getWeatherString(),
|
||||||
|
getWeatherRow2()
|
||||||
|
];
|
||||||
|
if (shouldDisplaySeconds(now)) rows[0] += ':' + pad(now.getSeconds(), 2);
|
||||||
|
if (storage.readJSON('setting.json')['12hour']) rows[0] += ((now.getHours() < 12) ? ' AM' : ' PM');
|
||||||
|
|
||||||
|
let maxHeight = ((g.getHeight() / 2) - HHMM_TOP) / (config.dayProgress.enabledUnlocked ? (rows.length + 1) : rows.length);
|
||||||
|
|
||||||
|
let y = HHMM_TOP + maxHeight / 2;
|
||||||
|
for (let row of rows) {
|
||||||
|
let size = getFontSize(row.length, g.getWidth(), 6, maxHeight);
|
||||||
|
g.setFont('Vector', size)
|
||||||
|
.drawString(row, g.getWidth() / 2, y);
|
||||||
|
y += maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.dayProgress.enabledUnlocked) {
|
||||||
|
let color = config.dayProgress.color;
|
||||||
|
g.setColor(color[0], color[1], color[2])
|
||||||
|
.fillRect(0, y - maxHeight / 2, 176 * getDayProgress(now), y + maxHeight / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNextDrawTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the icons. This is done separately from the main draw routine to avoid having to scale and draw a bunch of images repeatedly.
|
||||||
|
function drawIcons() {
|
||||||
|
g.reset().clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
let x = [0, 44, 88, 132, 0, 44, 88, 132][i];
|
||||||
|
let y = [88, 88, 88, 88, 132, 132, 132, 132][i];
|
||||||
|
let appId = config.shortcuts[i];
|
||||||
|
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||||
|
if (!appInfo) continue;
|
||||||
|
icon = storage.read(appInfo.icon);
|
||||||
|
g.drawImage(icon, x, y, {
|
||||||
|
scale: 0.916666666667
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weather.on("update", draw);
|
||||||
|
Bangle.on("step", draw);
|
||||||
|
Bangle.on('lock', locked => {
|
||||||
|
//If the watch is unlocked, draw the icons
|
||||||
|
if (!locked) drawIcons();
|
||||||
|
draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// Launch an app given the current ID. Handles special cases:
|
||||||
|
// false: Do nothing
|
||||||
|
// '#LAUNCHER': Open the launcher
|
||||||
|
// nonexistent app: Do nothing
|
||||||
|
function launch(appId) {
|
||||||
|
if (appId == false) return;
|
||||||
|
else if (appId == '#LAUNCHER') {
|
||||||
|
Bangle.buzz();
|
||||||
|
Bangle.showLauncher();
|
||||||
|
} else {
|
||||||
|
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||||
|
if (appInfo) {
|
||||||
|
Bangle.buzz();
|
||||||
|
load(appInfo.src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set up touch to launch the selected app
|
||||||
|
Bangle.on('touch', function (button, xy) {
|
||||||
|
let x = Math.floor(xy.x / 44);
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
else if (x > 3) x = 3;
|
||||||
|
|
||||||
|
let y = Math.floor(xy.y / 44);
|
||||||
|
if (y < 0) y = -1;
|
||||||
|
else if (y > 3) y = 1;
|
||||||
|
else y -= 2;
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
Bangle.buzz();
|
||||||
|
Bangle.showLauncher();
|
||||||
|
} else {
|
||||||
|
let i = 4 * y + x;
|
||||||
|
launch(config.shortcuts[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set up swipe handler
|
||||||
|
Bangle.on('swipe', function (direction) {
|
||||||
|
if (direction == -1) launch(config.swipe.left);
|
||||||
|
else if (direction == 0) launch(config.swipe.up);
|
||||||
|
else launch(config.swipe.right);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Bangle.isLocked()) drawIcons();
|
||||||
|
|
||||||
|
draw();
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
const heatshrink = require("heatshrink")
|
||||||
|
|
||||||
|
function decompress(string) {
|
||||||
|
return heatshrink.decompress(atob(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
exports = {
|
||||||
|
'0': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYk/En4kmAA4qBAAP7BAePAYX4BofBAYX8F4Q+BEwRHBIQI5BA"),
|
||||||
|
'1': decompress("ktAwIGDj/4AgX/4ADBg/+BAU/+ADBgP/wAEBh/8BoV/8ADBgf/En4k/En4k/EgQ="),
|
||||||
|
'2': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECElQdBPA2HAYX8OYfHBAYRD8Z3Dj6TG/kPPYZm4EiwAHO4f7BAfPfI/xBoaTEPAfgQwY"),
|
||||||
|
'3': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECElJWIAEpu/EhpgS34DC/IID54DC/l/AgXDAYX4j57DA"),
|
||||||
|
'4': decompress("ktAwMA//AgEf//+BYP///wgEHAgOAgE///8gEBBAPggEPAgIWBv///EAgYIBEn4kXABf9AgfnAgY4BAAP4BAfDAYX+EwfwIQRRCJIJRBJIRRBJIQICj5RBJIRRBJIJRCNwJRBNwQk/Ei4A=="),
|
||||||
|
'5': decompress("ktAwIEB/4AB/EfAgXDAYX+n4EC+YDC/+fAYX9BAfvAgYAJ+AwBgP/wAEBh/8H4V/8ADBgf/BAUf/AEC//AAYMH/wICn4kpPYUPAgXgv4EC4JfDg4DC/iFD8ANDwaTDCQfwEoZ2/EhrXNAAm/AYX5BAfPQoaTD4ahDj57DA=="),
|
||||||
|
'6': decompress("ktAwIEB/4AB/EfAgXDAYX+n4EC+YDC/+fAYX9BAfvAgYAJ+AwBgP/wAEBh/8H4V/8ADBgf/BAUf/AEC//AAYMH/wICn4kpPYUPAgXgv6AG/6JD/gID84ED358NJIIsCKIQ0BKIRJCFgJJCSYcHAgJuBXYJuBKIQkpAA58D/YIDx6PDBofBQoYvCHwImCI4KUCwA="),
|
||||||
|
'7': decompress("ktAwMA/4AB/EHAgXwn4EC8IDC/+PAYX+v4EC+YND74NDBAYAE4A0Bg/+HIU/+ADBgP/wAEBh/8BoV/8ADBgf/BAUf/AECEn4k/En4kVA"),
|
||||||
|
'8': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYkpAFpu/EhwAHFQIAB/YIDx4DC/AND4IDC/ieD4AmCI4JCBHIIA=="),
|
||||||
|
'9': decompress("ktAwIEB////EAj4EB+EDAYP/8E/AgWDAYX+CIX/+IDC//PBoYIDAAvwgEHAgOAgAnB/kAgIvCgEPAgJCBv5CCHwXAI4X+KAYkpABf9AgfnAgaFD/AID4Z8DEwfwIQRRCJIJRBJIRRBJIQICj5RBJIRRBJIJRCNwJRBNwQkoPhoAE34DC/L0H/iwBQAv4WAJ7CA=="),
|
||||||
|
|
||||||
|
':': decompress("iFAwITQg/gj/4n/8v/+AIP/ABQPDCoIZBDoJTfH94A=="),
|
||||||
|
|
||||||
|
'am': decompress("jFAwIEBngCEvwCH/4CFwEBAQkD//AgfnAQcH4fgAQsPwPwAQf/+Ef//4AQn8n0AvgCCHQN+vkAnwCC/EAj4CF+EAh4CCNIoLFC4v8gE/AQv+gF/AQpwB/4CDwICG+/D94CD8/v+fn54CC+P/x4CF+H/IgICFvwCEngCD"),
|
||||||
|
'pm': decompress("jFAwMAn///l///+/4AE+EAh4CaEYoABFgX8BwMAAUwAFIIv4gEfAQX8OYICF/0Av4CF/8AKQICCwICG+/D94CD8/v+fn54CC+P/x4CF+H/IgICFvwCEngCDA")
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 360 B |
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 290 B |
|
After Width: | Height: | Size: 183 B |
|
After Width: | Height: | Size: 305 B |
|
After Width: | Height: | Size: 270 B |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 302 B |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 227 B |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 319 B |
|
After Width: | Height: | Size: 327 B |
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgOAA4YFS/4AKEf5BlABcAjAgBjAfBAuhH/Apo"))
|
||||||
|
After Width: | Height: | Size: 249 B |
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"id": "infoclk",
|
||||||
|
"name": "Informational clock",
|
||||||
|
"version": "0.08",
|
||||||
|
"description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS2"
|
||||||
|
],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"name": "infoclk.app.js",
|
||||||
|
"url": "app.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "infoclk.settings.js",
|
||||||
|
"url": "settings.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "infoclk-font.js",
|
||||||
|
"url": "font.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "infoclk.img",
|
||||||
|
"url": "icon.js",
|
||||||
|
"evaluate": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "infoclk.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,571 @@
|
||||||
|
(function (back) {
|
||||||
|
const SETTINGS_FILE = "infoclk.json";
|
||||||
|
const storage = require('Storage');
|
||||||
|
|
||||||
|
let config = Object.assign({
|
||||||
|
seconds: {
|
||||||
|
// Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display.
|
||||||
|
// The seconds will be shown unless one of these conditions is enabled here, and currently true.
|
||||||
|
hideLocked: false, // Hide the seconds when the display is locked.
|
||||||
|
hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage.
|
||||||
|
hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds
|
||||||
|
hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes
|
||||||
|
hideEnd: 700, // The time when the seconds are shown again
|
||||||
|
hideAlways: false, // Always hide (never show) the seconds
|
||||||
|
},
|
||||||
|
|
||||||
|
date: {
|
||||||
|
// Settings related to the display of the date
|
||||||
|
mmdd: true, // If true, display the month first. If false, display the date first.
|
||||||
|
separator: '-', // The character that goes between the month and date
|
||||||
|
monthName: false, // If false, display the month as a number. If true, display the name.
|
||||||
|
monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name.
|
||||||
|
dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name.
|
||||||
|
},
|
||||||
|
|
||||||
|
bottomLocked: {
|
||||||
|
display: 'weather' // What to display in the bottom row when locked:
|
||||||
|
// 'weather': The current temperature and weather description
|
||||||
|
// 'steps': Step count
|
||||||
|
// 'health': Step count and bpm
|
||||||
|
// 'progress': Day progress bar
|
||||||
|
// false: Nothing
|
||||||
|
},
|
||||||
|
|
||||||
|
shortcuts: [
|
||||||
|
//8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked
|
||||||
|
// false = no shortcut
|
||||||
|
// '#LAUNCHER' = open the launcher
|
||||||
|
// any other string = name of app to open
|
||||||
|
'stlap', 'keytimer', 'pomoplus', 'alarm',
|
||||||
|
'rpnsci', 'calendar', 'torch', 'weather'
|
||||||
|
],
|
||||||
|
|
||||||
|
swipe: {
|
||||||
|
// 3 shortcuts to launch upon swiping:
|
||||||
|
// false = no shortcut
|
||||||
|
// '#LAUNCHER' = open the launcher
|
||||||
|
// any other string = name of app to open
|
||||||
|
up: 'messages', // Swipe up or swipe down, due to limitation of event handler
|
||||||
|
left: '#LAUNCHER',
|
||||||
|
right: '#LAUNCHER',
|
||||||
|
},
|
||||||
|
|
||||||
|
dayProgress: {
|
||||||
|
// A progress bar representing how far through the day you are
|
||||||
|
enabledLocked: true, // Whether this bar is enabled when the watch is locked
|
||||||
|
enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked
|
||||||
|
color: [0, 0, 1], // The color of the bar
|
||||||
|
start: 700, // The time of day that the bar starts filling
|
||||||
|
end: 2200, // The time of day that the bar becomes full
|
||||||
|
reset: 300 // The time of day when the progress bar resets from full to empty
|
||||||
|
},
|
||||||
|
|
||||||
|
lowBattColor: {
|
||||||
|
// The text can change color to indicate that the battery is low
|
||||||
|
level: 20, // The percentage where this happens
|
||||||
|
color: [1, 0, 0] // The color that the text changes to
|
||||||
|
}
|
||||||
|
}, storage.readJSON(SETTINGS_FILE));
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
storage.writeJSON(SETTINGS_FILE, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hourToString(hour) {
|
||||||
|
if (storage.readJSON('setting.json')['12hour']) {
|
||||||
|
if (hour == 0) return '12 AM';
|
||||||
|
else if (hour < 12) return `${hour} AM`;
|
||||||
|
else if (hour == 12) return '12 PM';
|
||||||
|
else return `${hour - 12} PM`;
|
||||||
|
} else return '' + hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The menu for configuring when the seconds are shown
|
||||||
|
function showSecondsMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Seconds display',
|
||||||
|
'back': showMainMenu
|
||||||
|
},
|
||||||
|
'Show seconds': {
|
||||||
|
value: !config.seconds.hideAlways,
|
||||||
|
onchange: value => {
|
||||||
|
config.seconds.hideAlways = !value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'...unless locked': {
|
||||||
|
value: config.seconds.hideLocked,
|
||||||
|
onchange: value => {
|
||||||
|
config.seconds.hideLocked = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'...unless battery below': {
|
||||||
|
value: config.seconds.hideBattery,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
format: value => `${value}%`,
|
||||||
|
onchange: value => {
|
||||||
|
config.seconds.hideBattery = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'...unless between these 2 times...': () => {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Hide seconds between',
|
||||||
|
'back': showSecondsMenu
|
||||||
|
},
|
||||||
|
'Enabled': {
|
||||||
|
value: config.seconds.hideTime,
|
||||||
|
onchange: value => {
|
||||||
|
config.seconds.hideTime = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Start hour': {
|
||||||
|
value: Math.floor(config.seconds.hideStart / 100),
|
||||||
|
format: hourToString,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: hour => {
|
||||||
|
minute = config.seconds.hideStart % 100;
|
||||||
|
config.seconds.hideStart = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Start minute': {
|
||||||
|
value: config.seconds.hideStart % 100,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: minute => {
|
||||||
|
hour = Math.floor(config.seconds.hideStart / 100);
|
||||||
|
config.seconds.hideStart = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'End hour': {
|
||||||
|
value: Math.floor(config.seconds.hideEnd / 100),
|
||||||
|
format: hourToString,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: hour => {
|
||||||
|
minute = config.seconds.hideEnd % 100;
|
||||||
|
config.seconds.hideEnd = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'End minute': {
|
||||||
|
value: config.seconds.hideEnd % 100,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: minute => {
|
||||||
|
hour = Math.floor(config.seconds.hideEnd / 100);
|
||||||
|
config.seconds.hideEnd = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available month/date separators
|
||||||
|
const SEPARATORS = [
|
||||||
|
{ name: 'Slash', char: '/' },
|
||||||
|
{ name: 'Dash', char: '-' },
|
||||||
|
{ name: 'Space', char: ' ' },
|
||||||
|
{ name: 'Comma', char: ',' },
|
||||||
|
{ name: 'None', char: '' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Available bottom row display options
|
||||||
|
const BOTTOM_ROW_OPTIONS = [
|
||||||
|
{ name: 'Weather', val: 'weather' },
|
||||||
|
{ name: 'Step count', val: 'steps' },
|
||||||
|
{ name: 'Steps + BPM', val: 'health' },
|
||||||
|
{ name: 'Day progresss bar', val: 'progress' },
|
||||||
|
{ name: 'Nothing', val: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
// The menu for configuring which apps have shortcut icons
|
||||||
|
function showShortcutMenu() {
|
||||||
|
//Builds the shortcut options
|
||||||
|
let shortcutOptions = [
|
||||||
|
{ name: 'Nothing', val: false },
|
||||||
|
{ name: 'Launcher', val: '#LAUNCHER' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let infoFiles = storage.list(/\.info$/).sort((a, b) => {
|
||||||
|
if (a.name < b.name) return -1;
|
||||||
|
else if (a.name > b.name) return 1;
|
||||||
|
else return 0;
|
||||||
|
});
|
||||||
|
for (let infoFile of infoFiles) {
|
||||||
|
let appInfo = storage.readJSON(infoFile);
|
||||||
|
if (appInfo.src) shortcutOptions.push({
|
||||||
|
name: appInfo.name,
|
||||||
|
val: appInfo.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Shortcuts',
|
||||||
|
'back': showMainMenu
|
||||||
|
},
|
||||||
|
'Top first': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[0]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[0] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Top second': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[1]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[1] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Top third': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[2]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[2] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Top fourth': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[3]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[3] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Bottom first': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[4]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[4] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Bottom second': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[5]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[5] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Bottom third': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[6]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[6] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Bottom fourth': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[7]),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.shortcuts[7] = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Swipe up': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.up),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.swipe.up = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Swipe left': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.left),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.swipe.left = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Swipe right': {
|
||||||
|
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.right),
|
||||||
|
format: value => shortcutOptions[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: shortcutOptions.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.swipe.right = shortcutOptions[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLOR_OPTIONS = [
|
||||||
|
{ name: 'Black', val: [0, 0, 0] },
|
||||||
|
{ name: 'Blue', val: [0, 0, 1] },
|
||||||
|
{ name: 'Green', val: [0, 1, 0] },
|
||||||
|
{ name: 'Cyan', val: [0, 1, 1] },
|
||||||
|
{ name: 'Red', val: [1, 0, 0] },
|
||||||
|
{ name: 'Magenta', val: [1, 0, 1] },
|
||||||
|
{ name: 'Yellow', val: [1, 1, 0] },
|
||||||
|
{ name: 'White', val: [1, 1, 1] }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Workaround for being unable to use == on arrays: convert them into strings
|
||||||
|
function colorString(color) {
|
||||||
|
return `${color[0]} ${color[1]} ${color[2]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Shows the top level menu
|
||||||
|
function showMainMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Informational Clock',
|
||||||
|
'back': back
|
||||||
|
},
|
||||||
|
'Seconds display': showSecondsMenu,
|
||||||
|
'Day of week format': {
|
||||||
|
value: config.date.dayFullName,
|
||||||
|
format: value => value ? 'Full name' : 'Abbreviation',
|
||||||
|
onchange: value => {
|
||||||
|
config.date.dayFullName = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Date format': () => {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Date format',
|
||||||
|
'back': showMainMenu,
|
||||||
|
},
|
||||||
|
'Order': {
|
||||||
|
value: config.date.mmdd,
|
||||||
|
format: value => value ? 'Month first' : 'Date first',
|
||||||
|
onchange: value => {
|
||||||
|
config.date.mmdd = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Separator': {
|
||||||
|
value: SEPARATORS.map(item => item.char).indexOf(config.date.separator),
|
||||||
|
format: value => SEPARATORS[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: SEPARATORS.length - 1,
|
||||||
|
wrap: true,
|
||||||
|
onchange: value => {
|
||||||
|
config.date.separator = SEPARATORS[value].char;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Month format': {
|
||||||
|
// 0 = number only
|
||||||
|
// 1 = abbreviation
|
||||||
|
// 2 = full name
|
||||||
|
value: config.date.monthName ? (config.date.monthFullName ? 2 : 1) : 0,
|
||||||
|
format: value => ['Number', 'Abbreviation', 'Full name'][value],
|
||||||
|
min: 0,
|
||||||
|
max: 2,
|
||||||
|
wrap: true,
|
||||||
|
onchange: value => {
|
||||||
|
if (value == 0) config.date.monthName = false;
|
||||||
|
else {
|
||||||
|
config.date.monthName = true;
|
||||||
|
config.date.monthFullName = (value == 2);
|
||||||
|
}
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'Bottom row': {
|
||||||
|
value: BOTTOM_ROW_OPTIONS.map(item => item.val).indexOf(config.bottomLocked.display),
|
||||||
|
format: value => BOTTOM_ROW_OPTIONS[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: BOTTOM_ROW_OPTIONS.length - 1,
|
||||||
|
wrap: true,
|
||||||
|
onchange: value => {
|
||||||
|
config.bottomLocked.display = BOTTOM_ROW_OPTIONS[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Shortcuts': showShortcutMenu,
|
||||||
|
'Day progress': () => {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Day progress',
|
||||||
|
'back': showMainMenu
|
||||||
|
},
|
||||||
|
'Enable while locked': {
|
||||||
|
value: config.dayProgress.enabledLocked,
|
||||||
|
onchange: value => {
|
||||||
|
config.dayProgress.enableLocked = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Enable while unlocked': {
|
||||||
|
value: config.dayProgress.enabledUnlocked,
|
||||||
|
onchange: value => {
|
||||||
|
config.dayProgress.enabledUnlocked = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Color': {
|
||||||
|
value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.dayProgress.color)),
|
||||||
|
format: value => COLOR_OPTIONS[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: COLOR_OPTIONS.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.dayProgress.color = COLOR_OPTIONS[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Start hour': {
|
||||||
|
value: Math.floor(config.dayProgress.start / 100),
|
||||||
|
format: hourToString,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: hour => {
|
||||||
|
minute = config.dayProgress.start % 100;
|
||||||
|
config.dayProgress.start = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Start minute': {
|
||||||
|
value: config.dayProgress.start % 100,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: minute => {
|
||||||
|
hour = Math.floor(config.dayProgress.start / 100);
|
||||||
|
config.dayProgress.start = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'End hour': {
|
||||||
|
value: Math.floor(config.dayProgress.end / 100),
|
||||||
|
format: hourToString,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: hour => {
|
||||||
|
minute = config.dayProgress.end % 100;
|
||||||
|
config.dayProgress.end = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'End minute': {
|
||||||
|
value: config.dayProgress.end % 100,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: minute => {
|
||||||
|
hour = Math.floor(config.dayProgress.end / 100);
|
||||||
|
config.dayProgress.end = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Reset hour': {
|
||||||
|
value: Math.floor(config.dayProgress.reset / 100),
|
||||||
|
format: hourToString,
|
||||||
|
min: 0,
|
||||||
|
max: 23,
|
||||||
|
wrap: true,
|
||||||
|
onchange: hour => {
|
||||||
|
minute = config.dayProgress.reset % 100;
|
||||||
|
config.dayProgress.reset = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Reset minute': {
|
||||||
|
value: config.dayProgress.reset % 100,
|
||||||
|
min: 0,
|
||||||
|
max: 59,
|
||||||
|
wrap: true,
|
||||||
|
onchange: minute => {
|
||||||
|
hour = Math.floor(config.dayProgress.reset / 100);
|
||||||
|
config.dayProgress.reset = (100 * hour) + minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'Low battery color': () => {
|
||||||
|
E.showMenu({
|
||||||
|
'': {
|
||||||
|
'title': 'Low battery color',
|
||||||
|
back: showMainMenu
|
||||||
|
},
|
||||||
|
'Low battery threshold': {
|
||||||
|
value: config.lowBattColor.level,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
format: value => `${value}%`,
|
||||||
|
onchange: value => {
|
||||||
|
config.lowBattColor.level = value;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Color': {
|
||||||
|
value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.lowBattColor.color)),
|
||||||
|
format: value => COLOR_OPTIONS[value].name,
|
||||||
|
min: 0,
|
||||||
|
max: COLOR_OPTIONS.length - 1,
|
||||||
|
wrap: false,
|
||||||
|
onchange: value => {
|
||||||
|
config.lowBattColor.color = COLOR_OPTIONS[value].val;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showMainMenu();
|
||||||
|
});
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Now keeps user input trace intact by changing how the screen is updated.
|
0.02: Now keeps user input trace intact by changing how the screen is updated.
|
||||||
0.03: Positioning of marker now takes the height of the widget field into account.
|
0.03: Positioning of marker now takes the height of the widget field into account.
|
||||||
0.04: Fix issue if going back without typing.
|
0.04: Fix issue if going back without typing.
|
||||||
|
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
var l;//last event
|
var l;//last event
|
||||||
Bangle.setUI({mode:"custom", drag:e=>{
|
Bangle.setUI({mode:"custom", drag:e=>{
|
||||||
|
"ram";
|
||||||
if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y);
|
if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y);
|
||||||
l = e.b ? e : 0;
|
l = e.b ? e : 0;
|
||||||
},touch:() => {
|
},touch:() => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "kbswipe",
|
{ "id": "kbswipe",
|
||||||
"name": "Swipe keyboard",
|
"name": "Swipe keyboard",
|
||||||
"version":"0.04",
|
"version":"0.05",
|
||||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type":"textinput",
|
"type":"textinput",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New app!
|
||||||
|
0.02: Submitted to the app loader
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
Bangle.keytimer_ACTIVE = true;
|
||||||
|
const common = require("keytimer-com.js");
|
||||||
|
const storage = require("Storage");
|
||||||
|
|
||||||
|
const keypad = require("keytimer-keys.js");
|
||||||
|
const timerView = require("keytimer-tview.js");
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
//Save our state when the app is closed
|
||||||
|
E.on('kill', () => {
|
||||||
|
storage.writeJSON(common.STATE_PATH, common.state);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Handle touch here. I would implement these separately in each view, but I can't figure out how to clear the event listeners.
|
||||||
|
Bangle.on('touch', (button, xy) => {
|
||||||
|
if (common.state.wasRunning) timerView.touch(button, xy);
|
||||||
|
else keypad.touch(button, xy);
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.on('swipe', dir => {
|
||||||
|
if (!common.state.wasRunning) keypad.swipe(dir);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (common.state.wasRunning) timerView.show(common);
|
||||||
|
else keypad.show(common);
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
const keytimer_common = require("keytimer-com.js");
|
||||||
|
|
||||||
|
//Only start the timeout if the timer is running
|
||||||
|
if (keytimer_common.state.running) {
|
||||||
|
setTimeout(() => {
|
||||||
|
//Check now to avoid race condition
|
||||||
|
if (Bangle.keytimer_ACTIVE === undefined) {
|
||||||
|
load('keytimer-ring.js');
|
||||||
|
}
|
||||||
|
}, keytimer_common.getTimeLeft());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
const storage = require("Storage");
|
||||||
|
const heatshrink = require("heatshrink");
|
||||||
|
|
||||||
|
exports.STATE_PATH = "keytimer.state.json";
|
||||||
|
|
||||||
|
exports.BUTTON_ICONS = {
|
||||||
|
play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")),
|
||||||
|
pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")),
|
||||||
|
reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI="))
|
||||||
|
};
|
||||||
|
|
||||||
|
//Store the minimal amount of information to be able to reconstruct the state of the timer at any given time.
|
||||||
|
//This is necessary because it is necessary to write to flash to let the timer run in the background, so minimizing the writes is necessary.
|
||||||
|
exports.STATE_DEFAULT = {
|
||||||
|
wasRunning: false, //If the timer ever was running. Used to determine whether to display a reset button
|
||||||
|
running: false, //Whether the timer is currently running
|
||||||
|
startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously.
|
||||||
|
pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused.
|
||||||
|
elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages.
|
||||||
|
setTime: 0, //How long the user wants the timer to run for
|
||||||
|
inputString: '0' //The string of numbers the user typed in.
|
||||||
|
};
|
||||||
|
exports.state = storage.readJSON(exports.STATE_PATH);
|
||||||
|
if (!exports.state) {
|
||||||
|
exports.state = exports.STATE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the number of milliseconds until the timer expires
|
||||||
|
exports.getTimeLeft = function () {
|
||||||
|
if (!exports.state.wasRunning) {
|
||||||
|
//If the timer never ran, the time left is just the set time
|
||||||
|
return exports.setTime
|
||||||
|
} else if (exports.state.running) {
|
||||||
|
//If the timer is running, the time left is current time - start time + preexisting time
|
||||||
|
var runningTime = (new Date()).getTime() - exports.state.startTime + exports.state.elapsedTime;
|
||||||
|
} else {
|
||||||
|
//If the timer is not running, the same as above but use when the timer was paused instead of now.
|
||||||
|
var runningTime = exports.state.pausedTime - exports.state.startTime + exports.state.elapsedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.state.setTime - runningTime;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwcAkmSpICOggRPpEACJ9AgESCJxMBhu27dtARVgCIMBCJpxDmwRL7ARDgwRL4CWECJaoFjYRJ2ARFgYRJwDNGCJFsb46SIRgQAFSRAQHSRCMEAAqSGRgoAFRhaSKRgySKRg6SIRhCSIRhCSICBqSCRhSSGRhY2FkARPhMkCJ9JkiONgECCIOQCJsSCIOSCJuSCIVACBcECIdICJYOBCIVJRhYRFSRSMBCIiSKBwgCCSRCMCCIqSIRgYCFRhYCFSQyMEAQqSGBw6SIRgySKRgtO4iSJBAmT23bOIqSCRgvtCINsSQ4aEndtCINt2KSGIggOBCIW2JQlARgZECCIhKEpBEGCIpKEA=="))
|
||||||
|
After Width: | Height: | Size: 414 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1,136 @@
|
||||||
|
let common;
|
||||||
|
|
||||||
|
function inputStringToTime(inputString) {
|
||||||
|
let number = parseInt(inputString);
|
||||||
|
let hours = Math.floor(number / 10000);
|
||||||
|
let minutes = Math.floor((number % 10000) / 100);
|
||||||
|
let seconds = number % 100;
|
||||||
|
|
||||||
|
return 3600000 * hours +
|
||||||
|
60000 * minutes +
|
||||||
|
1000 * seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(number) {
|
||||||
|
return ('00' + parseInt(number)).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputStringToDisplayString(inputString) {
|
||||||
|
let number = parseInt(inputString);
|
||||||
|
let hours = Math.floor(number / 10000);
|
||||||
|
let minutes = Math.floor((number % 10000) / 100);
|
||||||
|
let seconds = number % 100;
|
||||||
|
|
||||||
|
if (hours == 0 && minutes == 0) return '' + seconds;
|
||||||
|
else if (hours == 0) return `${pad(minutes)}:${pad(seconds)}`;
|
||||||
|
else return `${hours}:${pad(minutes)}:${pad(seconds)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberButton {
|
||||||
|
constructor(number) {
|
||||||
|
this.label = '' + number;
|
||||||
|
}
|
||||||
|
|
||||||
|
onclick() {
|
||||||
|
if (common.state.inputString == '0') common.state.inputString = this.label;
|
||||||
|
else common.state.inputString += this.label;
|
||||||
|
common.state.setTime = inputStringToTime(common.state.inputString);
|
||||||
|
feedback(true);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ClearButton = {
|
||||||
|
label: 'Clr',
|
||||||
|
onclick: () => {
|
||||||
|
common.state.inputString = '0';
|
||||||
|
common.state.setTime = 0;
|
||||||
|
updateDisplay();
|
||||||
|
feedback(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let StartButton = {
|
||||||
|
label: 'Go',
|
||||||
|
onclick: () => {
|
||||||
|
common.state.startTime = (new Date()).getTime();
|
||||||
|
common.state.elapsedTime = 0;
|
||||||
|
common.state.wasRunning = true;
|
||||||
|
common.state.running = true;
|
||||||
|
feedback(true);
|
||||||
|
require('keytimer-tview.js').show(common);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const BUTTONS = [
|
||||||
|
[new NumberButton(7), new NumberButton(8), new NumberButton(9), ClearButton],
|
||||||
|
[new NumberButton(4), new NumberButton(5), new NumberButton(6), new NumberButton(0)],
|
||||||
|
[new NumberButton(1), new NumberButton(2), new NumberButton(3), StartButton]
|
||||||
|
];
|
||||||
|
|
||||||
|
function feedback(acceptable) {
|
||||||
|
if (acceptable) Bangle.buzz(50, 0.5);
|
||||||
|
else Bangle.buzz(200, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawButtons() {
|
||||||
|
g.reset().clearRect(0, 44, 175, 175).setFont("Vector", 15).setFontAlign(0, 0);
|
||||||
|
//Draw lines
|
||||||
|
for (let x = 44; x <= 176; x += 44) {
|
||||||
|
g.drawLine(x, 44, x, 175);
|
||||||
|
}
|
||||||
|
for (let y = 44; y <= 176; y += 44) {
|
||||||
|
g.drawLine(0, y, 175, y);
|
||||||
|
}
|
||||||
|
for (let row = 0; row < 3; row++) {
|
||||||
|
for (let col = 0; col < 4; col++) {
|
||||||
|
g.drawString(BUTTONS[row][col].label, 22 + 44 * col, 66 + 44 * row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFontSize(length) {
|
||||||
|
let size = Math.floor(176 / length); //Characters of width needed per pixel
|
||||||
|
size *= (20 / 12); //Convert to height
|
||||||
|
// Clamp to between 6 and 20
|
||||||
|
if (size < 6) return 6;
|
||||||
|
else if (size > 20) return 20;
|
||||||
|
else return Math.floor(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDisplay() {
|
||||||
|
let displayString = inputStringToDisplayString(common.state.inputString);
|
||||||
|
g.clearRect(0, 24, 175, 43).setColor(storage.readJSON('setting.json').theme.fg2).setFontAlign(1, -1).setFont("Vector", getFontSize(displayString.length)).drawString(displayString, 176, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.show = function (callerCommon) {
|
||||||
|
common = callerCommon;
|
||||||
|
g.reset();
|
||||||
|
drawButtons();
|
||||||
|
updateDisplay();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.touch = function (button, xy) {
|
||||||
|
let row = Math.floor((xy.y - 44) / 44);
|
||||||
|
let col = Math.floor(xy.x / 44);
|
||||||
|
if (row < 0) return;
|
||||||
|
if (row > 2) row = 2;
|
||||||
|
if (col < 0) col = 0;
|
||||||
|
if (col > 3) col = 3;
|
||||||
|
|
||||||
|
BUTTONS[row][col].onclick();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.swipe = function (dir) {
|
||||||
|
if (dir == -1) {
|
||||||
|
if (common.state.inputString.length == 1) common.state.inputString = '0';
|
||||||
|
else common.state.inputString = common.state.inputString.substring(0, common.state.inputString.length - 1);
|
||||||
|
|
||||||
|
common.state.setTime = inputStringToTime(common.state.inputString);
|
||||||
|
|
||||||
|
feedback(true);
|
||||||
|
updateDisplay();
|
||||||
|
} else if (dir == 0) {
|
||||||
|
EnterButton.onclick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"id": "keytimer",
|
||||||
|
"name": "Keypad Timer",
|
||||||
|
"version": "0.02",
|
||||||
|
"description": "A timer with a keypad that runs in the background",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tools",
|
||||||
|
"supports": [
|
||||||
|
"BANGLEJS2"
|
||||||
|
],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"name": "keytimer.app.js",
|
||||||
|
"url": "app.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer.img",
|
||||||
|
"url": "icon.js",
|
||||||
|
"evaluate": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer.boot.js",
|
||||||
|
"url": "boot.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer-com.js",
|
||||||
|
"url": "common.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer-ring.js",
|
||||||
|
"url": "ring.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer-keys.js",
|
||||||
|
"url": "keypad.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keytimer-tview.js",
|
||||||
|
"url": "timerview.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
const common = require('keytimer-com.js');
|
||||||
|
|
||||||
|
Bangle.loadWidgets()
|
||||||
|
Bangle.drawWidgets()
|
||||||
|
|
||||||
|
Bangle.setLocked(false);
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
|
||||||
|
let brightness = 0;
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
Bangle.buzz(200);
|
||||||
|
Bangle.setLCDBrightness(1 - brightness);
|
||||||
|
brightness = 1 - brightness;
|
||||||
|
}, 400);
|
||||||
|
Bangle.buzz(200);
|
||||||
|
|
||||||
|
function stopTimer() {
|
||||||
|
common.state.wasRunning = false;
|
||||||
|
common.state.running = false;
|
||||||
|
require("Storage").writeJSON(common.STATE_PATH, common.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showAlert("Timer expired!").then(() => {
|
||||||
|
stopTimer();
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
E.on('kill', stopTimer);
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
let common;
|
||||||
|
|
||||||
|
function drawButtons() {
|
||||||
|
//Draw the backdrop
|
||||||
|
const BAR_TOP = g.getHeight() - 24;
|
||||||
|
g.setColor(0, 0, 1).setFontAlign(0, -1)
|
||||||
|
.clearRect(0, BAR_TOP, g.getWidth(), g.getHeight())
|
||||||
|
.fillRect(0, BAR_TOP, g.getWidth(), g.getHeight())
|
||||||
|
.setColor(1, 1, 1)
|
||||||
|
.drawLine(g.getWidth() / 2, BAR_TOP, g.getWidth() / 2, g.getHeight())
|
||||||
|
|
||||||
|
//Draw the buttons
|
||||||
|
.drawImage(common.BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP);
|
||||||
|
if (common.state.running) {
|
||||||
|
g.drawImage(common.BUTTON_ICONS.pause, g.getWidth() * 3 / 4, BAR_TOP);
|
||||||
|
} else {
|
||||||
|
g.drawImage(common.BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTimer() {
|
||||||
|
let timeLeft = common.getTimeLeft();
|
||||||
|
g.reset()
|
||||||
|
.setFontAlign(0, 0)
|
||||||
|
.setFont("Vector", 36)
|
||||||
|
.clearRect(0, 24, 176, 152)
|
||||||
|
|
||||||
|
//Draw the timer
|
||||||
|
.drawString((() => {
|
||||||
|
let hours = timeLeft / 3600000;
|
||||||
|
let minutes = (timeLeft % 3600000) / 60000;
|
||||||
|
let seconds = (timeLeft % 60000) / 1000;
|
||||||
|
|
||||||
|
function pad(number) {
|
||||||
|
return ('00' + parseInt(number)).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours >= 1) return `${parseInt(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
||||||
|
else return `${parseInt(minutes)}:${pad(seconds)}`;
|
||||||
|
})(), g.getWidth() / 2, g.getHeight() / 2)
|
||||||
|
|
||||||
|
if (timeLeft <= 0) load('keytimer-ring.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
let timerInterval;
|
||||||
|
|
||||||
|
function setupTimerInterval() {
|
||||||
|
if (timerInterval !== undefined) {
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
timerInterval = setInterval(drawTimer, 1000);
|
||||||
|
drawTimer();
|
||||||
|
}, common.timeLeft % 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.show = function (callerCommon) {
|
||||||
|
common = callerCommon;
|
||||||
|
drawButtons();
|
||||||
|
drawTimer();
|
||||||
|
if (common.state.running) {
|
||||||
|
setupTimerInterval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimerInterval() {
|
||||||
|
if (timerInterval !== undefined) {
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
timerInterval = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.touch = (button, xy) => {
|
||||||
|
if (xy.y < 152) return;
|
||||||
|
|
||||||
|
if (button == 1) {
|
||||||
|
//Reset the timer
|
||||||
|
let setTime = common.state.setTime;
|
||||||
|
let inputString = common.state.inputString;
|
||||||
|
common.state = common.STATE_DEFAULT;
|
||||||
|
common.state.setTime = setTime;
|
||||||
|
common.state.inputString = inputString;
|
||||||
|
clearTimerInterval();
|
||||||
|
require('keytimer-keys.js').show(common);
|
||||||
|
} else {
|
||||||
|
if (common.state.running) {
|
||||||
|
//Record the exact moment that we paused
|
||||||
|
let now = (new Date()).getTime();
|
||||||
|
common.state.pausedTime = now;
|
||||||
|
|
||||||
|
//Stop the timer
|
||||||
|
common.state.running = false;
|
||||||
|
clearTimerInterval();
|
||||||
|
drawTimer();
|
||||||
|
drawButtons();
|
||||||
|
} else {
|
||||||
|
//Start the timer and record when we started
|
||||||
|
let now = (new Date()).getTime();
|
||||||
|
common.state.elapsedTime += common.state.pausedTime - common.state.startTime;
|
||||||
|
common.state.startTime = now;
|
||||||
|
common.state.running = true;
|
||||||
|
drawTimer();
|
||||||
|
setupTimerInterval();
|
||||||
|
drawButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -17,3 +17,5 @@
|
||||||
0.15: Support for unload and quick return to the clock on 2v16
|
0.15: Support for unload and quick return to the clock on 2v16
|
||||||
0.16: Use a cache of app.info files to speed up loading the launcher
|
0.16: Use a cache of app.info files to speed up loading the launcher
|
||||||
0.17: Don't display 'Loading...' now the watch has its own loading screen
|
0.17: Don't display 'Loading...' now the watch has its own loading screen
|
||||||
|
0.18: Add 'back' icon in top-left to go back to clock
|
||||||
|
0.19: Fix regression after back button added (returnToClock was called twice!)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,17 @@ let apps = launchCache.apps;
|
||||||
// Now apps list is loaded - render
|
// Now apps list is loaded - render
|
||||||
if (!settings.fullscreen)
|
if (!settings.fullscreen)
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
|
let returnToClock = function() {
|
||||||
|
// unload everything manually
|
||||||
|
// ... or we could just call `load();` but it will be slower
|
||||||
|
Bangle.setUI(); // remove scroller's handling
|
||||||
|
if (lockTimeout) clearTimeout(lockTimeout);
|
||||||
|
Bangle.removeListener("lock", lockHandler);
|
||||||
|
// now load the default clock - just call .bootcde as this has the code already
|
||||||
|
setTimeout(eval,0,s.read(".bootcde"));
|
||||||
|
}
|
||||||
|
|
||||||
E.showScroller({
|
E.showScroller({
|
||||||
h : 64*scaleval, c : apps.length,
|
h : 64*scaleval, c : apps.length,
|
||||||
draw : (i, r) => {
|
draw : (i, r) => {
|
||||||
|
|
@ -62,26 +73,11 @@ E.showScroller({
|
||||||
} else {
|
} else {
|
||||||
load(app.src);
|
load(app.src);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
back : returnToClock // button press or tap in top left calls returnToClock now
|
||||||
});
|
});
|
||||||
g.flip(); // force a render before widgets have finished drawing
|
g.flip(); // force a render before widgets have finished drawing
|
||||||
|
|
||||||
let returnToClock = function() {
|
|
||||||
// unload everything manually
|
|
||||||
// ... or we could just call `load();` but it will be slower
|
|
||||||
Bangle.setUI(); // remove scroller's handling
|
|
||||||
if (lockTimeout) clearTimeout(lockTimeout);
|
|
||||||
Bangle.removeListener("lock", lockHandler);
|
|
||||||
// now load the default clock - just call .bootcde as this has the code already
|
|
||||||
setTimeout(eval,0,s.read(".bootcde"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// on bangle.js 2, the screen is used for navigating, so the single button goes back
|
|
||||||
// on bangle.js 1, the buttons are used for navigating
|
|
||||||
if (process.env.HWVERSION==2) {
|
|
||||||
setWatch(returnToClock, BTN1, {edge:"falling"});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10s of inactivity goes back to clock
|
// 10s of inactivity goes back to clock
|
||||||
Bangle.setLocked(false); // unlock initially
|
Bangle.setLocked(false); // unlock initially
|
||||||
let lockTimeout;
|
let lockTimeout;
|
||||||
|
|
|
||||||