mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into feat/messages-no-show-1st-unread
Conflicts: apps/messagegui/ChangeLogpull/3622/head
commit
03b1519648
|
@ -29,5 +29,4 @@
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
|
||||
})
|
||||
|
|
|
@ -109,4 +109,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -68,4 +68,4 @@ function buildMainMenu() {
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
})
|
||||
|
|
|
@ -48,4 +48,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -25,4 +25,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -38,4 +38,5 @@
|
|||
0.36: Move from wrapper function to {} and let - faster execution at boot
|
||||
Allow `calendar-` to take an array of items to remove
|
||||
0.37: Support Gadgetbridge canned responses
|
||||
0.38: Don't rewrite settings file on every boot!
|
||||
0.38: Don't rewrite settings file on every boot!
|
||||
0.39: Move GB message handling into a library to reduce boot time from 40ms->13ms
|
|
@ -1,349 +1,24 @@
|
|||
/* global GB */
|
||||
{
|
||||
let gbSend = function(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
let lastMsg; // for music messages - may not be needed now...
|
||||
let actInterval; // Realtime activity reporting interval when `act` is true
|
||||
let actHRMHandler; // For Realtime activity reporting
|
||||
let gpsState = {}; // keep information on GPS via Gadgetbridge
|
||||
|
||||
// this settings var is deleted after this executes to save memory
|
||||
// settings var is deleted after this executes to save memory
|
||||
let settings = Object.assign({rp:true,as:true,vibrate:".."},
|
||||
require("Storage").readJSON("android.settings.json",1)||{}
|
||||
);
|
||||
let _GB = global.GB;
|
||||
let fetchRecInterval;
|
||||
global.GB = (event) => {
|
||||
global.GB = e => {
|
||||
// feed a copy to other handlers if there were any
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},event));
|
||||
|
||||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() {
|
||||
Object.assign(event,{t:"add",positive:true, negative:true});
|
||||
// Detect a weird GadgetBridge bug and fix it
|
||||
// For some reason SMS messages send two GB notifications, with different sets of info
|
||||
if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
|
||||
// Mutate the other message
|
||||
event.id = lastMsg.id;
|
||||
}
|
||||
lastMsg = event;
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
Object.assign(event, {
|
||||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"canned_responses_sync" : function() {
|
||||
require("Storage").writeJSON("replies.json", event.d);
|
||||
},
|
||||
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
var sched;
|
||||
try { sched = require("sched"); } catch (e) {}
|
||||
if (!sched) return; // alarms may not be installed
|
||||
var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
|
||||
for (var i = 0; i < gbalarms.length; i++)
|
||||
sched.setAlarm(gbalarms[i].id, undefined);
|
||||
var alarms = sched.getAlarms();
|
||||
var time = new Date();
|
||||
var currentTime = time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000;
|
||||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
var rp = false;
|
||||
if (!dow) {
|
||||
dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
} else {
|
||||
rp = true;
|
||||
}
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = require("sched").newDefaultAlarm();
|
||||
a.id = "gb"+j;
|
||||
a.appid = "gbalarms";
|
||||
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
|
||||
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||
a.rp = rp;
|
||||
a.last = last;
|
||||
alarms.push(a);
|
||||
}
|
||||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
// {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
var i = cal.findIndex(e=>e.id==event.id);
|
||||
if(i<0)
|
||||
cal.push(event);
|
||||
else
|
||||
cal[i] = event;
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
// {t:"calendar-", id:int}
|
||||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
if (Array.isArray(event.id))
|
||||
cal = cal.filter(e=>!event.id.includes(e.id));
|
||||
else
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
//triggered by GB, send all ids
|
||||
// { t:"force_calendar_sync_start" }
|
||||
"force_calendar_sync_start" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
|
||||
},
|
||||
// {t:"http",resp:"......",[id:"..."]}
|
||||
"http":function() {
|
||||
//get the promise and call the promise resolve
|
||||
if (Bangle.httpRequest === undefined) return;
|
||||
var request=Bangle.httpRequest[event.id];
|
||||
if (request === undefined) return; //already timedout or wrong id
|
||||
delete Bangle.httpRequest[event.id];
|
||||
clearTimeout(request.t); //t = timeout variable
|
||||
if(event.err!==undefined) //if is error
|
||||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
request.r(event); //r = resolve function
|
||||
},
|
||||
// {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
|
||||
"gps": function() {
|
||||
if (!settings.overwriteGps) return;
|
||||
// modify event for using it as Bangle GPS event
|
||||
delete event.t;
|
||||
if (!isFinite(event.satellites)) event.satellites = NaN;
|
||||
if (!isFinite(event.course)) event.course = NaN;
|
||||
event.fix = 1;
|
||||
if (event.long!==undefined) { // for earlier Gadgetbridge implementations
|
||||
event.lon = event.long;
|
||||
delete event.long;
|
||||
}
|
||||
if (event.time){
|
||||
event.time = new Date(event.time);
|
||||
}
|
||||
|
||||
if (!gpsState.lastGPSEvent) {
|
||||
// this is the first event, save time of arrival and deactivate internal GPS
|
||||
Bangle.moveGPSPower(0);
|
||||
} else {
|
||||
// this is the second event, store the intervall for expecting the next GPS event
|
||||
gpsState.interval = Date.now() - gpsState.lastGPSEvent;
|
||||
}
|
||||
gpsState.lastGPSEvent = Date.now();
|
||||
// in any case, cleanup the GPS state in case no new events arrive
|
||||
if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS);
|
||||
gpsState.timeoutGPS = setTimeout(()=>{
|
||||
// reset state
|
||||
gpsState.lastGPSEvent = undefined;
|
||||
gpsState.timeoutGPS = undefined;
|
||||
gpsState.interval = undefined;
|
||||
// did not get an expected GPS event but have GPS clients, switch back to internal GPS
|
||||
if (Bangle.isGPSOn()) Bangle.moveGPSPower(1);
|
||||
}, (gpsState.interval || 10000) + 1000);
|
||||
Bangle.emit('GPS', event);
|
||||
},
|
||||
// {t:"is_gps_active"}
|
||||
"is_gps_active": function() {
|
||||
gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
|
||||
},
|
||||
// {t:"act", hrm:bool, stp:bool, int:int}
|
||||
"act": function() {
|
||||
if (actInterval) clearInterval(actInterval);
|
||||
actInterval = undefined;
|
||||
if (actHRMHandler)
|
||||
actHRMHandler = undefined;
|
||||
Bangle.setHRMPower(event.hrm,"androidact");
|
||||
if (!(event.hrm || event.stp)) return;
|
||||
if (!isFinite(event.int)) event.int=1;
|
||||
var lastSteps = Bangle.getStepCount();
|
||||
var lastBPM = 0;
|
||||
actHRMHandler = function(e) {
|
||||
lastBPM = e.bpm;
|
||||
};
|
||||
Bangle.on('HRM',actHRMHandler);
|
||||
actInterval = setInterval(function() {
|
||||
var steps = Bangle.getStepCount();
|
||||
gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
|
||||
lastSteps = steps;
|
||||
}, event.int*1000);
|
||||
},
|
||||
// {t:"actfetch", ts:long}
|
||||
"actfetch": function() {
|
||||
gbSend({t: "actfetch", state: "start"});
|
||||
var actCount = 0;
|
||||
var actCb = function(r) {
|
||||
// The health lib saves the samples at the start of the 10-minute block
|
||||
// However, GB expects them at the end of the block, so let's offset them
|
||||
// here to keep a consistent API in the health lib
|
||||
var sampleTs = r.date.getTime() + 600000;
|
||||
if (sampleTs >= event.ts) {
|
||||
gbSend({
|
||||
t: "act",
|
||||
ts: sampleTs,
|
||||
stp: r.steps,
|
||||
hrm: r.bpm,
|
||||
mov: r.movement
|
||||
});
|
||||
actCount++;
|
||||
}
|
||||
}
|
||||
if (event.ts != 0) {
|
||||
require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
|
||||
} else {
|
||||
require("health").readFullDatabase(actCb);
|
||||
}
|
||||
gbSend({t: "actfetch", state: "end", count: actCount});
|
||||
},
|
||||
//{t:"listRecs", id:"20230616a"}
|
||||
"listRecs": function() {
|
||||
let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21));
|
||||
if (event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
|
||||
let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
|
||||
if (-1 == firstNonsyncedIdx) {
|
||||
recs = []
|
||||
} else {
|
||||
recs = recs.slice(firstNonsyncedIdx);
|
||||
}
|
||||
}
|
||||
gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
|
||||
},
|
||||
//{t:"fetchRec", id:"20230616a"}
|
||||
"fetchRec": function() {
|
||||
// TODO: Decide on what names keys should have.
|
||||
if (fetchRecInterval) {
|
||||
clearInterval(fetchRecInterval);
|
||||
fetchRecInterval = undefined;
|
||||
}
|
||||
if (event.id=="stop") {
|
||||
return
|
||||
} else {
|
||||
let log = require("Storage").open("recorder.log"+event.id+".csv","r");
|
||||
let lines = "init";// = log.readLine();
|
||||
let pkgcnt = 0;
|
||||
gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
|
||||
let sendlines = ()=>{
|
||||
lines = log.readLine();
|
||||
for (var i = 0; i < 3; i++) {
|
||||
let line = log.readLine();
|
||||
if (line) lines += line;
|
||||
}
|
||||
pkgcnt++;
|
||||
gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
|
||||
if (!lines && fetchRecInterval) {
|
||||
clearInterval(fetchRecInterval);
|
||||
fetchRecInterval = undefined;
|
||||
}
|
||||
}
|
||||
fetchRecInterval = setInterval(sendlines, 50)
|
||||
}
|
||||
},
|
||||
"nav": function() {
|
||||
event.id="nav";
|
||||
if (event.instr) {
|
||||
event.t="add";
|
||||
event.src="maps"; // for the icon
|
||||
event.title="Navigation";
|
||||
if (require("messages").getMessages().find(m=>m.id=="nav"))
|
||||
event.t = "modify";
|
||||
} else {
|
||||
event.t="remove";
|
||||
}
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"cards" : function() {
|
||||
// we receive all, just override what we have
|
||||
if (Array.isArray(event.d))
|
||||
require("Storage").writeJSON("android.cards.json", event.d);
|
||||
},
|
||||
"accelsender": function () {
|
||||
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||
load();
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},e));
|
||||
Bangle.emit("GB",e);
|
||||
require("android").gbHandler(e);
|
||||
};
|
||||
// HTTP request handling - see the readme
|
||||
// options = {id,timeout,xpath}
|
||||
Bangle.http = (url,options)=>{
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
// try and create a unique ID
|
||||
do {
|
||||
options.id = Math.random().toString().substr(2);
|
||||
} while( Bangle.httpRequest[options.id]!==undefined);
|
||||
}
|
||||
//send the request
|
||||
var req = {t: "http", url:url, id:options.id};
|
||||
if (options.xpath) req.xpath = options.xpath;
|
||||
if (options.return) req.return = options.return; // for xpath
|
||||
if (options.method) req.method = options.method;
|
||||
if (options.body) req.body = options.body;
|
||||
if (options.headers) req.headers = options.headers;
|
||||
gbSend(req);
|
||||
//create the promise
|
||||
var promise = new Promise(function(resolve,reject) {
|
||||
//save the resolve function in the dictionary and create a timeout (30 seconds default)
|
||||
Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
|
||||
//if after "timeoutMillisec" it still hasn't answered -> reject
|
||||
delete Bangle.httpRequest[options.id];
|
||||
reject("Timeout");
|
||||
},options.timeout||30000)};
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
Bangle.http = (url,options)=>require("android").httpHandler(url,options);
|
||||
// Battery monitor
|
||||
let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
let sendBattery = function() { require("android").gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
Bangle.on("charging", sendBattery);
|
||||
NRF.on("connect", () => setTimeout(function() {
|
||||
sendBattery();
|
||||
gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
|
||||
require("android").gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION});
|
||||
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
|
||||
}, 2000));
|
||||
NRF.on("disconnect", () => {
|
||||
|
@ -357,81 +32,24 @@
|
|||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking - if 'realtime' data is sent with 'rt:1', but let's still send our activity log every 10 mins
|
||||
Bangle.on('health', h=>{
|
||||
gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement });
|
||||
require("android").gbSend({ t: "act", stp: h.steps, hrm: h.bpm, mov: h.movement });
|
||||
});
|
||||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", n:cmd });
|
||||
require("android").gbSend({ t: "music", n:cmd });
|
||||
};
|
||||
// Message response
|
||||
Bangle.messageResponse = (msg,response) => {
|
||||
if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
if (msg.id=="call") return require("android").gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return require("android").gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
Bangle.messageIgnore = msg => {
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:"MUTE", id: msg.id });
|
||||
if (isFinite(msg.id)) return require("android").gbSend({ t: "notify", n:"MUTE", id: msg.id });
|
||||
};
|
||||
// GPS overwrite logic
|
||||
if (settings.overwriteGps) { // if the overwrite option is set..
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.moveGPSPower = (state) => {
|
||||
if (Bangle.isGPSOn()){
|
||||
let orig = Bangle._PWR.GPS;
|
||||
delete Bangle._PWR.GPS;
|
||||
origSetGPSPower(state);
|
||||
Bangle._PWR.GPS = orig;
|
||||
}
|
||||
};
|
||||
|
||||
// work around Serial1 for GPS not working when connected to something
|
||||
let serialTimeout;
|
||||
let wrap = function(f){
|
||||
return (s)=>{
|
||||
if (serialTimeout) clearTimeout(serialTimeout);
|
||||
origSetGPSPower(1, "androidgpsserial");
|
||||
f(s);
|
||||
serialTimeout = setTimeout(()=>{
|
||||
serialTimeout = undefined;
|
||||
origSetGPSPower(0, "androidgpsserial");
|
||||
}, 10000);
|
||||
};
|
||||
};
|
||||
Serial1.println = wrap(Serial1.println);
|
||||
Serial1.write = wrap(Serial1.write);
|
||||
|
||||
// replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = ((isOn, appID) => {
|
||||
let pwr;
|
||||
if (!this.lastGPSEvent){
|
||||
// use internal GPS power function if no gps event has arrived from GadgetBridge
|
||||
pwr = origSetGPSPower(isOn, appID);
|
||||
} else {
|
||||
// we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
if (!appID) appID="?";
|
||||
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||
pwr = Bangle._PWR.GPS.length>0;
|
||||
// stop internal GPS, no clients left
|
||||
if (!pwr) origSetGPSPower(0);
|
||||
}
|
||||
// always update Gadgetbridge on current power state
|
||||
gbSend({ t: "gps_power", status: pwr });
|
||||
return pwr;
|
||||
}).bind(gpsState);
|
||||
// allow checking for GPS via GadgetBridge
|
||||
Bangle.isGPSOn = () => {
|
||||
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
|
||||
};
|
||||
// stop GPS on boot if not activated
|
||||
setTimeout(()=>{
|
||||
if (!Bangle.isGPSOn()) gbSend({ t: "gps_power", status: false });
|
||||
},3000);
|
||||
}
|
||||
|
||||
if (settings.overwriteGps) require("android").overwriteGPS();
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,388 @@
|
|||
exports.gbSend = function(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
let lastMsg, // for music messages - may not be needed now...
|
||||
gpsState = {}, // keep information on GPS via Gadgetbridge
|
||||
settings = Object.assign({rp:true,as:true,vibrate:".."},
|
||||
require("Storage").readJSON("android.settings.json",1)||{}
|
||||
);
|
||||
|
||||
exports.gbHandler = (event) => {
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() {
|
||||
print("notify",event);
|
||||
Object.assign(event,{t:"add",positive:true, negative:true});
|
||||
// Detect a weird GadgetBridge bug and fix it
|
||||
// For some reason SMS messages send two GB notifications, with different sets of info
|
||||
if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
|
||||
// Mutate the other message
|
||||
event.id = lastMsg.id;
|
||||
}
|
||||
lastMsg = event;
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
},
|
||||
// {"t":"call","cmd":"incoming/end/start/outgoing","name":"Bob","number":"12421312"})
|
||||
"call" : function() {
|
||||
Object.assign(event, {
|
||||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"canned_responses_sync" : function() {
|
||||
require("Storage").writeJSON("replies.json", event.d);
|
||||
},
|
||||
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
var sched;
|
||||
try { sched = require("sched"); } catch (e) {}
|
||||
if (!sched) return; // alarms may not be installed
|
||||
var gbalarms = sched.getAlarms().filter(a=>a.appid=="gbalarms");
|
||||
for (var i = 0; i < gbalarms.length; i++)
|
||||
sched.setAlarm(gbalarms[i].id, undefined);
|
||||
var alarms = sched.getAlarms();
|
||||
var time = new Date();
|
||||
var currentTime = time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000;
|
||||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
var rp = false;
|
||||
if (!dow) {
|
||||
dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
} else {
|
||||
rp = true;
|
||||
}
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = require("sched").newDefaultAlarm();
|
||||
a.id = "gb"+j;
|
||||
a.appid = "gbalarms";
|
||||
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
|
||||
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||
a.rp = rp;
|
||||
a.last = last;
|
||||
alarms.push(a);
|
||||
}
|
||||
sched.setAlarms(alarms);
|
||||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
// {t:"calendar", id:int, type:int, timestamp:seconds, durationInSeconds, title:string, description:string,location:string,calName:string.color:int,allDay:bool
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
var i = cal.findIndex(e=>e.id==event.id);
|
||||
if(i<0)
|
||||
cal.push(event);
|
||||
else
|
||||
cal[i] = event;
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
// {t:"calendar-", id:int}
|
||||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
if (Array.isArray(event.id))
|
||||
cal = cal.filter(e=>!event.id.includes(e.id));
|
||||
else
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
//triggered by GB, send all ids
|
||||
// { t:"force_calendar_sync_start" }
|
||||
"force_calendar_sync_start" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
exports.gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)});
|
||||
},
|
||||
// {t:"http",resp:"......",[id:"..."]}
|
||||
"http":function() {
|
||||
//get the promise and call the promise resolve
|
||||
if (Bangle.httpRequest === undefined) return;
|
||||
var request=Bangle.httpRequest[event.id];
|
||||
if (request === undefined) return; //already timedout or wrong id
|
||||
delete Bangle.httpRequest[event.id];
|
||||
clearTimeout(request.t); //t = timeout variable
|
||||
if(event.err!==undefined) //if is error
|
||||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
request.r(event); //r = resolve function
|
||||
},
|
||||
// {t:"gps", lat, lon, alt, speed, course, time, satellites, hdop, externalSource:true }
|
||||
"gps": function() {
|
||||
if (!settings.overwriteGps) return;
|
||||
// modify event for using it as Bangle GPS event
|
||||
delete event.t;
|
||||
if (!isFinite(event.satellites)) event.satellites = NaN;
|
||||
if (!isFinite(event.course)) event.course = NaN;
|
||||
event.fix = 1;
|
||||
if (event.long!==undefined) { // for earlier Gadgetbridge implementations
|
||||
event.lon = event.long;
|
||||
delete event.long;
|
||||
}
|
||||
if (event.time){
|
||||
event.time = new Date(event.time);
|
||||
}
|
||||
|
||||
if (!gpsState.lastGPSEvent) {
|
||||
// this is the first event, save time of arrival and deactivate internal GPS
|
||||
Bangle.moveGPSPower(0);
|
||||
} else {
|
||||
// this is the second event, store the intervall for expecting the next GPS event
|
||||
gpsState.interval = Date.now() - gpsState.lastGPSEvent;
|
||||
}
|
||||
gpsState.lastGPSEvent = Date.now();
|
||||
// in any case, cleanup the GPS state in case no new events arrive
|
||||
if (gpsState.timeoutGPS) clearTimeout(gpsState.timeoutGPS);
|
||||
gpsState.timeoutGPS = setTimeout(()=>{
|
||||
// reset state
|
||||
gpsState.lastGPSEvent = undefined;
|
||||
gpsState.timeoutGPS = undefined;
|
||||
gpsState.interval = undefined;
|
||||
// did not get an expected GPS event but have GPS clients, switch back to internal GPS
|
||||
if (Bangle.isGPSOn()) Bangle.moveGPSPower(1);
|
||||
}, (gpsState.interval || 10000) + 1000);
|
||||
Bangle.emit('GPS', event);
|
||||
},
|
||||
// {t:"is_gps_active"}
|
||||
"is_gps_active": function() {
|
||||
exports.gbSend({ t: "gps_power", status: Bangle.isGPSOn() });
|
||||
},
|
||||
// {t:"act", hrm:bool, stp:bool, int:int}
|
||||
"act": function() {
|
||||
if (exports.actInterval) clearInterval(exports.actInterval);
|
||||
exports.actInterval = undefined;
|
||||
if (exports.actHRMHandler)
|
||||
exports.actHRMHandler = undefined;
|
||||
Bangle.setHRMPower(event.hrm,"androidact");
|
||||
if (!(event.hrm || event.stp)) return;
|
||||
if (!isFinite(event.int)) event.int=1;
|
||||
var lastSteps = Bangle.getStepCount();
|
||||
var lastBPM = 0;
|
||||
exports.actHRMHandler = function(e) {
|
||||
lastBPM = e.bpm;
|
||||
};
|
||||
Bangle.on('HRM',exports.actHRMHandler);
|
||||
exports.actInterval = setInterval(function() {
|
||||
var steps = Bangle.getStepCount();
|
||||
exports.gbSend({ t: "act", stp: steps-lastSteps, hrm: lastBPM, rt:1 });
|
||||
lastSteps = steps;
|
||||
}, event.int*1000);
|
||||
},
|
||||
// {t:"actfetch", ts:long}
|
||||
"actfetch": function() {
|
||||
exports.gbSend({t: "actfetch", state: "start"});
|
||||
var actCount = 0;
|
||||
var actCb = function(r) {
|
||||
// The health lib saves the samples at the start of the 10-minute block
|
||||
// However, GB expects them at the end of the block, so let's offset them
|
||||
// here to keep a consistent API in the health lib
|
||||
var sampleTs = r.date.getTime() + 600000;
|
||||
if (sampleTs >= event.ts) {
|
||||
exports.gbSend({
|
||||
t: "act",
|
||||
ts: sampleTs,
|
||||
stp: r.steps,
|
||||
hrm: r.bpm,
|
||||
mov: r.movement
|
||||
});
|
||||
actCount++;
|
||||
}
|
||||
}
|
||||
if (event.ts != 0) {
|
||||
require("health").readAllRecordsSince(new Date(event.ts - 600000), actCb);
|
||||
} else {
|
||||
require("health").readFullDatabase(actCb);
|
||||
}
|
||||
exports.gbSend({t: "actfetch", state: "end", count: actCount});
|
||||
},
|
||||
//{t:"listRecs", id:"20230616a"}
|
||||
"listRecs": function() {
|
||||
let recs = require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).map(s => s.slice(12, 21));
|
||||
if (event.id.length > 2) { // Handle if there was no id supplied. Then we send a list all available recorder logs back.
|
||||
let firstNonsyncedIdx = recs.findIndex((logId) => logId > event.id);
|
||||
if (-1 == firstNonsyncedIdx) {
|
||||
recs = []
|
||||
} else {
|
||||
recs = recs.slice(firstNonsyncedIdx);
|
||||
}
|
||||
}
|
||||
exports.gbSend({t:"actTrksList", list: recs}); // TODO: split up in multiple transmissions?
|
||||
},
|
||||
//{t:"fetchRec", id:"20230616a"}
|
||||
"fetchRec": function() {
|
||||
// TODO: Decide on what names keys should have.
|
||||
if (exports.fetchRecInterval) {
|
||||
clearInterval(exports.fetchRecInterval);
|
||||
exports.fetchRecInterval = undefined;
|
||||
}
|
||||
if (event.id=="stop") {
|
||||
return;
|
||||
} else {
|
||||
let log = require("Storage").open("recorder.log"+event.id+".csv","r");
|
||||
let lines = "init";// = log.readLine();
|
||||
let pkgcnt = 0;
|
||||
exports.gbSend({t:"actTrk", log:event.id, lines:"erase", cnt:pkgcnt}); // "erase" will prompt Gadgetbridge to erase the contents of a already fetched log so we can rewrite it without keeping lines from the previous (probably failed) fetch.
|
||||
let sendlines = ()=>{
|
||||
lines = log.readLine();
|
||||
for (var i = 0; i < 3; i++) {
|
||||
let line = log.readLine();
|
||||
if (line) lines += line;
|
||||
}
|
||||
pkgcnt++;
|
||||
exports.gbSend({t:"actTrk", log:event.id, lines:lines, cnt:pkgcnt});
|
||||
if (!lines && exports.fetchRecInterval) {
|
||||
clearInterval(exports.fetchRecInterval);
|
||||
exports.fetchRecInterval = undefined;
|
||||
}
|
||||
};
|
||||
exports.fetchRecInterval = setInterval(sendlines, 50);
|
||||
}
|
||||
},
|
||||
"nav": function() {
|
||||
event.id="nav";
|
||||
if (event.instr) {
|
||||
event.t="add";
|
||||
event.src="maps"; // for the icon
|
||||
event.title="Navigation";
|
||||
if (require("messages").getMessages().find(m=>m.id=="nav"))
|
||||
event.t = "modify";
|
||||
} else {
|
||||
event.t="remove";
|
||||
}
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"cards" : function() {
|
||||
// we receive all, just override what we have
|
||||
if (Array.isArray(event.d))
|
||||
require("Storage").writeJSON("android.cards.json", event.d);
|
||||
},
|
||||
"accelsender": function () {
|
||||
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
|
||||
load();
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
};
|
||||
|
||||
// HTTP request handling - see the readme
|
||||
// options = {id,timeout,xpath}
|
||||
exports.httpHandler = (url,options) => {
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
// try and create a unique ID
|
||||
do {
|
||||
options.id = Math.random().toString().substr(2);
|
||||
} while( Bangle.httpRequest[options.id]!==undefined);
|
||||
}
|
||||
//send the request
|
||||
var req = {t: "http", url:url, id:options.id};
|
||||
if (options.xpath) req.xpath = options.xpath;
|
||||
if (options.return) req.return = options.return; // for xpath
|
||||
if (options.method) req.method = options.method;
|
||||
if (options.body) req.body = options.body;
|
||||
if (options.headers) req.headers = options.headers;
|
||||
exports.gbSend(req);
|
||||
//create the promise
|
||||
var promise = new Promise(function(resolve,reject) {
|
||||
//save the resolve function in the dictionary and create a timeout (30 seconds default)
|
||||
Bangle.httpRequest[options.id]={r:resolve,j:reject,t:setTimeout(()=>{
|
||||
//if after "timeoutMillisec" it still hasn't answered -> reject
|
||||
delete Bangle.httpRequest[options.id];
|
||||
reject("Timeout");
|
||||
},options.timeout||30000)};
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
exports.overwriteGPS = () => { // if the overwrite option is set, call this on init..
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.moveGPSPower = (state) => {
|
||||
if (Bangle.isGPSOn()){
|
||||
let orig = Bangle._PWR.GPS;
|
||||
delete Bangle._PWR.GPS;
|
||||
origSetGPSPower(state);
|
||||
Bangle._PWR.GPS = orig;
|
||||
}
|
||||
};
|
||||
|
||||
// work around Serial1 for GPS not working when connected to something
|
||||
let serialTimeout;
|
||||
let wrap = function(f){
|
||||
return (s)=>{
|
||||
if (serialTimeout) clearTimeout(serialTimeout);
|
||||
origSetGPSPower(1, "androidgpsserial");
|
||||
f(s);
|
||||
serialTimeout = setTimeout(()=>{
|
||||
serialTimeout = undefined;
|
||||
origSetGPSPower(0, "androidgpsserial");
|
||||
}, 10000);
|
||||
};
|
||||
};
|
||||
Serial1.println = wrap(Serial1.println);
|
||||
Serial1.write = wrap(Serial1.write);
|
||||
|
||||
// replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = ((isOn, appID) => {
|
||||
let pwr;
|
||||
if (!this.lastGPSEvent){
|
||||
// use internal GPS power function if no gps event has arrived from GadgetBridge
|
||||
pwr = origSetGPSPower(isOn, appID);
|
||||
} else {
|
||||
// we are currently expecting the next GPS event from GadgetBridge, keep track of GPS state per app
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
if (!appID) appID="?";
|
||||
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||
pwr = Bangle._PWR.GPS.length>0;
|
||||
// stop internal GPS, no clients left
|
||||
if (!pwr) origSetGPSPower(0);
|
||||
}
|
||||
// always update Gadgetbridge on current power state
|
||||
require("android").gbSend({ t: "gps_power", status: pwr });
|
||||
return pwr;
|
||||
}).bind(gpsState);
|
||||
// allow checking for GPS via GadgetBridge
|
||||
Bangle.isGPSOn = () => {
|
||||
return !!(Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0);
|
||||
};
|
||||
// stop GPS on boot if not activated
|
||||
setTimeout(()=>{
|
||||
if (!Bangle.isGPSOn()) require("android").gbSend({ t: "gps_power", status: false });
|
||||
},3000);
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.38",
|
||||
"version": "0.39",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
@ -13,7 +13,8 @@
|
|||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.settings.js","url":"settings.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
{"name":"android.boot.js","url":"boot.js"},
|
||||
{"name":"android","url":"lib.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
|
||||
"sortorder": -8
|
||||
|
|
|
@ -94,4 +94,4 @@
|
|||
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
0.04: Now turns GPS off after upload
|
||||
0.05: Fix regression in 0.04 that caused AGPS data not to get loaded
|
||||
0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
|
||||
0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
|
||||
0.07: Bangle.js 2 now gets estimated time + lat/lon from the browser (~3x faster fix)
|
|
@ -60,6 +60,7 @@
|
|||
<script>
|
||||
var isB1; // is Bangle.js 1?
|
||||
var isB2; // is Bangle.js 2?
|
||||
var currentPosition;
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
|
@ -120,6 +121,7 @@
|
|||
}
|
||||
|
||||
// =================================================== Bangle.js 2 CASIC
|
||||
// https://www.espruino.com/Bangle.js2+Technical#gps
|
||||
|
||||
function CASIC_CHECKSUM(cmd) {
|
||||
var cs = 0;
|
||||
|
@ -128,6 +130,61 @@
|
|||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||
}
|
||||
|
||||
// Send a binary CASIC packet, eg: {classId:6, messageId:0, payload:[]}
|
||||
function CASIC_PKT(pkt) {
|
||||
pkt.payload = pkt.payload || [];
|
||||
var plen = pkt.payload.length;
|
||||
var msg = new Uint8Array(10+pkt.payload.length);
|
||||
msg.set([0xBA,0xCE,
|
||||
plen, // LENGTH
|
||||
0x00,
|
||||
pkt.classId, // CLASS ID
|
||||
pkt.messageId]); // MESSAGE ID
|
||||
msg.set(pkt.payload, 6);
|
||||
var dv = new DataView(msg.buffer);
|
||||
// checksum
|
||||
var ckSum = 0;
|
||||
for (i = -4; i < plen; i+=4)
|
||||
ckSum = 0|(ckSum+dv.getUint32(6+i, true));
|
||||
dv.setUint32(6+plen, ckSum, true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Send AID_INI message, {lat,lon,alt}
|
||||
function AID_INI(pos) {
|
||||
var msg = new Uint8Array(56);
|
||||
var dv = new DataView(msg.buffer);
|
||||
/*
|
||||
double xOrLat, yOrLon, zOrAlt;
|
||||
double tow; // 24
|
||||
float df; // 32
|
||||
float posAcc; // 36
|
||||
float tAcc; // 40
|
||||
float fAcc; // 44
|
||||
unsigned int res; // 48
|
||||
unsigned short int wn; // 52
|
||||
unsigned char timeSource; // 54
|
||||
unsigned char flags; // 55
|
||||
*/
|
||||
var ms = Date.now();
|
||||
var wk = (ms-new Date("1980-01-06T00:00:00Z")) / 604800000;
|
||||
var wn = Math.floor(wk); // week number
|
||||
var tow = (wk-wn) * 604800; // seconds in week
|
||||
dv.setFloat64(0, pos.lat, true); // xOrLat
|
||||
dv.setFloat64(8, pos.lon, true); // yOrLon
|
||||
dv.setFloat64(16, pos.alt, true); // zOrAlt
|
||||
dv.setFloat64(24, tow, true); // tow
|
||||
dv.setFloat32(32, 0, true); // df
|
||||
dv.setFloat32(36, 0, true); // posAcc
|
||||
dv.setFloat32(40, 0, true); // tAcc
|
||||
dv.setFloat32(44, 0, true); // fAcc
|
||||
dv.setUint32(48, 0, true); // res
|
||||
dv.setUint16(52, wn, true); // wn
|
||||
dv.setUint8(54,0); // timeSource
|
||||
dv.setUint8(55, 0x23); // flags ( lat/lon and clock valid, no drift data )
|
||||
return CASIC_PKT({classId:0x0B, messageId:0x01, payload:msg});
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
|
@ -140,7 +197,6 @@
|
|||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
}
|
||||
if (isB2) { // CASIC
|
||||
|
||||
// Select what GNSS System to use for decreased fix time.
|
||||
var radios = document.getElementsByName('gnss_select');
|
||||
var gnss_select="1";
|
||||
|
@ -150,11 +206,11 @@
|
|||
js += `\x10var t=getTime()+0.5;while (getTime()<t);\n`; // This is nasty - but we just wait here until the GPS has had time to boot
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS03,1,0,0,1,1,0,0,0")}")\n`; // enable GGA,GSV,RMC packets (new Bangle.js 2 GPS firmwares don't include RMC by default!)
|
||||
// If the browser let us have the current location, give it to the GPS chip to get a faster fix
|
||||
if (currentPosition) {
|
||||
js += `\x10Serial1.write([${AID_INI(currentPosition).join(",")}])\n`;
|
||||
}
|
||||
// Serial1.println("$PCAS06,0*1B") gets the current firmware version
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
}
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
|
@ -184,8 +240,20 @@
|
|||
document.getElementById("banglejs1-info").style = isB1?"":"display:none";
|
||||
document.getElementById("banglejs2-info").style = isB2?"":"display:none";
|
||||
document.getElementById("upload-wrap").style = "";
|
||||
|
||||
if (isB2) {
|
||||
// get current position for AGPS improvement
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
currentPosition = {
|
||||
lat : position.coords.latitude,
|
||||
lon : position.coords.longitude,
|
||||
alt : 0|position.coords.altitude
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"shortName": "AGPS",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"sortorder": -1,
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New app!
|
||||
0.02: Minor code improvements
|
||||
0.03: Remove clearing of the screen (will break running apps) and fix lint errors
|
|
@ -2,8 +2,8 @@
|
|||
"id": "banglebridge",
|
||||
"name": "BangleBridge",
|
||||
"shortName": "BangleBridge",
|
||||
"version": "0.02",
|
||||
"description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App",
|
||||
"version": "0.03",
|
||||
"description": "Widget that allows Bangle.js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App (**Note:** this has nothing to do with Gadgetbridge)",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
(() => {
|
||||
/**
|
||||
* Widget measurements
|
||||
* Description:
|
||||
* Description:
|
||||
* name: connection.wid.js
|
||||
*icon: conectionIcon.icon
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
//Font
|
||||
|
@ -24,7 +24,7 @@
|
|||
|
||||
//Sensors code
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Jorge
|
||||
*/
|
||||
function accel() {
|
||||
|
@ -35,8 +35,7 @@
|
|||
});
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||
//acclS = accelN.x + "##" + accelN.y + "##" + accelN.z + "\n" + accelN.diff + "##" + accelN.mag;
|
||||
data[3] = accelN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
@ -45,8 +44,7 @@
|
|||
function btt() {
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
bttS = E.getBattery(); //return String
|
||||
//bttS = E.getBattery(); //return String
|
||||
data[2] = E.getBattery();
|
||||
}, 15 * 1000);
|
||||
|
||||
|
@ -65,9 +63,9 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||
/*compssS = "A: " + compssN.x + " ## " + compssN.y + " ## " + compssN.z + "\n" +
|
||||
"B: " + compssN.dx + " ## " + compssN.dy + " ## " + compssN.dz + " ## " + "\n" +
|
||||
"C: " + compssN.heading; //return String
|
||||
"C: " + compssN.heading; *///return String
|
||||
data[4] = compssN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
@ -86,8 +84,8 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||
"C: " + gpsN.satellites + " ## " + gpsN.fix; //return String
|
||||
/*gpsS = "A: " + gpsN.lat + " ## " + gpsN.lon + " ## " + gpsN.alt + "\n" + "B: " + gpsN.speed + " ## " + gpsN.course + " ## " + gpsN.time + "\n" +
|
||||
"C: " + gpsN.satellites + " ## " + gpsN.fix; *///return String
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var year = d.getFullYear();
|
||||
|
@ -150,7 +148,7 @@
|
|||
//console.log("Index ==> "+ index);
|
||||
msr[indexFinal] = nueva;
|
||||
|
||||
item = nueva;
|
||||
//item = nueva;
|
||||
lastInsert = indexFinal;
|
||||
|
||||
}
|
||||
|
@ -180,7 +178,7 @@
|
|||
|
||||
hrmN = normalize(hrmN);
|
||||
var roundedRate = parseFloat(hrmN).toFixed(2);
|
||||
hrmS = String.valueOf(roundedRate); //return String
|
||||
//hrmS = String.valueOf(roundedRate); //return String
|
||||
//console.log("array----->" + msr);
|
||||
data[0] = roundedRate;
|
||||
|
||||
|
@ -205,7 +203,7 @@
|
|||
|
||||
setInterval(function () {
|
||||
|
||||
stepS = String.valueOf(stepN); //return String
|
||||
//stepS = String.valueOf(stepN); //return String
|
||||
data[1] = stepN;
|
||||
}, 2 * 1000);
|
||||
|
||||
|
@ -240,12 +238,11 @@
|
|||
g.setFont("Vector", 45);
|
||||
g.drawString(prueba,100,200);*/
|
||||
if (flip == 1) { //when off
|
||||
|
||||
|
||||
flip = 0;
|
||||
//Bangle.buzz(1000);
|
||||
g.clear();
|
||||
} else { //when on
|
||||
|
||||
|
||||
flip = 1;
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(data[0], 65, 180);
|
||||
|
@ -283,7 +280,7 @@
|
|||
com: data[4],
|
||||
gps: data[5]
|
||||
};
|
||||
/* g.clear();
|
||||
/*
|
||||
g.drawString(compssS,100,200);
|
||||
*/
|
||||
|
||||
|
@ -293,7 +290,7 @@
|
|||
//draw();
|
||||
|
||||
}, 5 * 1000);
|
||||
|
||||
|
||||
WIDGETS["banglebridge"]={
|
||||
area: "tl",
|
||||
width: 10,
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -32,4 +32,4 @@
|
|||
}
|
||||
require("ClockFace_menu").addItems(menu, save, items);
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -23,4 +23,4 @@
|
|||
};
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -68,4 +68,4 @@
|
|||
|
||||
E.showMenu(appMenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -69,4 +69,4 @@
|
|||
//};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -70,5 +70,3 @@
|
|||
};
|
||||
return ci;
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -72,3 +72,7 @@
|
|||
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
||||
0.62: Handle setting for configuring BLE privacy
|
||||
0.63: Only set BLE `display:1` if we have a passkey
|
||||
0.64: Automatically create .widcache and .clkinfocache to speed up loads
|
||||
Bangle.loadWidgets overwritten with fast version on success
|
||||
Refuse to work on firmware <2v16 and remove old polyfills
|
||||
0.65: Only display interpreter errors if log is nonzero
|
|
@ -12,14 +12,13 @@ if (DEBUG) {
|
|||
boot += "var _tm=Date.now()\n";
|
||||
bootPost += "delete _tm;";
|
||||
}
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
} else {
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
if (FWVERSION < 216) {
|
||||
E.showMessage(/*LANG*/"Please update Bangle.js firmware\n\nCurrent = "+process.env.VERSION,{title:"ERROR"});
|
||||
throw new Error("Old firmware");
|
||||
}
|
||||
boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`;
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.js$/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.js$/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
boot += `{eval(require('Storage').read('bootupdate.js'));}else{\n`;
|
||||
boot += `E.setFlags({pretokenise:1});\n`;
|
||||
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
||||
|
@ -44,7 +43,7 @@ LoopbackA.setConsole(true);\n`;
|
|||
boot += `
|
||||
Bluetooth.line="";
|
||||
Bluetooth.on('data',function(d) {
|
||||
var l = (Bluetooth.line + d).split(/[\\n\\r]/);
|
||||
let l = (Bluetooth.line + d).split(/[\\n\\r]/);
|
||||
Bluetooth.line = l.pop();
|
||||
l.forEach(n=>Bluetooth.emit("line",n));
|
||||
});
|
||||
|
@ -67,12 +66,12 @@ if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep();
|
|||
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
||||
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
||||
boot += `E.setTimeZone(${s.timezone});`;
|
||||
// Draw out of memory errors onto the screen
|
||||
boot += `E.on('errorFlag', function(errorFlags) {
|
||||
// Draw out of memory errors onto the screen if logging enabled
|
||||
if (s.log) boot += `E.on('errorFlag', function(errorFlags) {
|
||||
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
||||
print("Interpreter error:", errorFlags);
|
||||
E.getErrorFlags(); // clear flags so we get called next time
|
||||
});\n`;
|
||||
E.getErrorFlags();
|
||||
});\n`;// E.getErrorFlags() -> clear flags so we get called next time
|
||||
// stop users doing bad things!
|
||||
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
||||
// Apply any settings-specific stuff
|
||||
|
@ -86,30 +85,18 @@ if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
|
|||
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
|
||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
|
||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
boot+=`Bangle.loadWidgets=function(){if(!global.WIDGETS)eval(require("Storage").read(".widcache"))};\n`;
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;});
|
||||
Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
|
||||
|
||||
// deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
// this is a polyfill without fastloading capability
|
||||
delete Bangle.showClock;
|
||||
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||
delete Bangle.load;
|
||||
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||
let date = new Date();
|
||||
delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15
|
||||
if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() {
|
||||
var o = this.getTimezoneOffset();
|
||||
var d = new Date(this.getTime() - o*60000);
|
||||
var sign = o>0?"-":"+";
|
||||
o = Math.abs(o);
|
||||
return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0);
|
||||
};\n`;
|
||||
|
||||
// show timings
|
||||
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
// ================================================== BOOT.JS
|
||||
// ================================================== .BOOT0
|
||||
// Append *.boot.js files.
|
||||
// Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
|
@ -128,17 +115,47 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
|||
});
|
||||
// precalculate file size
|
||||
bootPost += "}";
|
||||
let fileSize = boot.length + bootPost.length;
|
||||
bootFiles.forEach(bootFile=>{
|
||||
// match the size of data we're adding below in bootFiles.forEach
|
||||
if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment
|
||||
fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n"
|
||||
if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
});
|
||||
// write file in chunks (so as not to use up all RAM)
|
||||
require('Storage').write('.boot0',boot,0,fileSize);
|
||||
let fileOffset = boot.length;
|
||||
bootFiles.forEach(bootFile=>{
|
||||
let fileOffset,fileSize;
|
||||
/* code to output a file, plus preable and postable
|
||||
when called with dst==undefined it just increments
|
||||
fileOffset so we can see ho wbig the file has to be */
|
||||
let outputFile = (dst,src,pre,post) => {"ram";
|
||||
if (DEBUG) {
|
||||
if (dst) require('Storage').write(dst,`//${src}\n`,fileOffset);
|
||||
fileOffset+=2+src.length+1;
|
||||
}
|
||||
if (pre) {
|
||||
if (dst) require('Storage').write(dst,pre,fileOffset);
|
||||
fileOffset+=pre.length;
|
||||
}
|
||||
let f = require('Storage').read(src);
|
||||
if (src.endsWith("clkinfo.js") && f[0]!="(") {
|
||||
/* we shouldn't have to do this but it seems sometimes (sched 0.28) folks have
|
||||
used libraries which get added into the clockinfo, and we can't use them directly
|
||||
to we have to revert back to eval */
|
||||
f = `eval(require('Storage').read(${E.toJS(src)}))`;
|
||||
}
|
||||
if (dst) {
|
||||
// we can't just write 'f' in one go because it can be too big
|
||||
let len = f.length;
|
||||
let offset = 0;
|
||||
while (len) {
|
||||
let chunk = Math.min(len, 2048);
|
||||
require('Storage').write(dst,f.substr(offset, chunk),fileOffset);
|
||||
fileOffset+=chunk;
|
||||
offset+=chunk;
|
||||
len-=chunk;
|
||||
}
|
||||
} else
|
||||
fileOffset+=f.length;
|
||||
if (dst) require('Storage').write(dst,post,fileOffset);
|
||||
fileOffset+=post.length;
|
||||
if (DEBUG) {
|
||||
if (dst) require('Storage').write(dst,`print(${E.toJS(src)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
|
||||
fileOffset += 48+E.toJS(src).length;
|
||||
}
|
||||
};
|
||||
let outputFileComplete = (dst,fn) => {
|
||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||
// which would cause an error!
|
||||
|
@ -146,31 +163,48 @@ bootFiles.forEach(bootFile=>{
|
|||
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
||||
// but we need to do this without ever loading everything into RAM as some
|
||||
// boot files seem to be getting pretty big now.
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset);
|
||||
fileOffset+=2+bootFile.length+1;
|
||||
}
|
||||
let bf = require('Storage').read(bootFile);
|
||||
// we can't just write 'bf' in one go because at least in 2v13 and earlier
|
||||
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1
|
||||
// it can be too big (especially BTHRM).
|
||||
let bflen = bf.length;
|
||||
let bfoffset = 0;
|
||||
while (bflen) {
|
||||
let bfchunk = Math.min(bflen, 2048);
|
||||
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
|
||||
fileOffset+=bfchunk;
|
||||
bfoffset+=bfchunk;
|
||||
bflen-=bfchunk;
|
||||
}
|
||||
require('Storage').write('.boot0',";\n",fileOffset);
|
||||
fileOffset+=2;
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
|
||||
fileOffset += 48+E.toJS(bootFile).length
|
||||
}
|
||||
});
|
||||
outputFile(dst,fn,"",";\n");
|
||||
};
|
||||
fileOffset = boot.length + bootPost.length;
|
||||
bootFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
|
||||
fileSize = fileOffset;
|
||||
require('Storage').write('.boot0',boot,0,fileSize);
|
||||
fileOffset = boot.length;
|
||||
bootFiles.forEach(fn=>outputFileComplete('.boot0',fn));
|
||||
require('Storage').write('.boot0',bootPost,fileOffset);
|
||||
delete boot,bootPost,bootFiles;
|
||||
// ================================================== .WIDCACHE for widgets
|
||||
let widgetFiles = require("Storage").list(/\.wid\.js$/);
|
||||
let widget = `// Made by bootupdate.js\nglobal.WIDGETS={};`, widgetPost = `var W=WIDGETS;WIDGETS={};
|
||||
Object.keys(W).sort((a,b)=>(0|W[b].sortorder)-(0|W[a].sortorder)).forEach(k=>WIDGETS[k]=W[k]);`; // sort
|
||||
if (DEBUG) widget+="var _tm=Date.now();";
|
||||
outputFileComplete = (dst,fn) => {
|
||||
outputFile(dst,fn,"try{",`}catch(e){print(${E.toJS(fn)},e,e.stack)}\n`);
|
||||
};
|
||||
fileOffset = widget.length + widgetPost.length;
|
||||
widgetFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
|
||||
fileSize = fileOffset;
|
||||
require('Storage').write('.widcache',widget,0,fileSize);
|
||||
fileOffset = widget.length;
|
||||
widgetFiles.forEach(fn=>outputFileComplete('.widcache',fn));
|
||||
require('Storage').write('.widcache',widgetPost,fileOffset);
|
||||
delete widget,widgetPost,widgetFiles;
|
||||
// ================================================== .clkinfocache for clockinfos
|
||||
let ciFiles = require("Storage").list(/\.clkinfo\.js$/);
|
||||
let ci = `// Made by bootupdate.js\n`;
|
||||
if (DEBUG) ci+="var _tm=Date.now();";
|
||||
outputFileComplete = (dst,fn) => {
|
||||
outputFile(dst,fn,"try{let fn=",`;let a=fn(),b=menu.find(x=>x.name===a.name);if(b)b.items=b.items.concat(a.items)else menu=menu.concat(a);}catch(e){print(${E.toJS(fn)},e,e.stack)}\n`);
|
||||
};
|
||||
fileOffset = ci.length;
|
||||
ciFiles.forEach(fn=>outputFileComplete(undefined,fn)); // just get sizes
|
||||
fileSize = fileOffset;
|
||||
require('Storage').write('.clkinfocache',ci,0,fileSize);
|
||||
fileOffset = ci.length;
|
||||
ciFiles.forEach(fn=>outputFileComplete('.clkinfocache',fn));
|
||||
delete ci,ciFiles;
|
||||
// test with require("clock_info").load()
|
||||
// ================================================== END
|
||||
E.showMessage(/*LANG*/"Reloading...");
|
||||
}
|
||||
// .bootcde should be run automatically after if required, since
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.63",
|
||||
"version": "0.65",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -11,6 +11,9 @@
|
|||
{"name":".boot0","url":"boot0.js"},
|
||||
{"name":".bootcde","url":"bootloader.js"},
|
||||
{"name":"bootupdate.js","url":"bootupdate.js"}
|
||||
],"data": [
|
||||
{"name":".widcache"},
|
||||
{"name":".clkinfocache"}
|
||||
],
|
||||
"sortorder": -10
|
||||
}
|
||||
|
|
|
@ -91,4 +91,4 @@
|
|||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -371,4 +371,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
})
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -91,4 +91,4 @@
|
|||
|
||||
settings = readSettings();
|
||||
showMainMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -92,4 +92,4 @@
|
|||
}
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -47,4 +47,4 @@
|
|||
},
|
||||
'< Back': back,
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -34,4 +34,4 @@
|
|||
}
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -124,4 +124,4 @@
|
|||
};
|
||||
|
||||
return info;
|
||||
});
|
||||
})
|
||||
|
|
|
@ -58,4 +58,4 @@
|
|||
};
|
||||
|
||||
return info;
|
||||
});
|
||||
})
|
||||
|
|
|
@ -74,4 +74,4 @@
|
|||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
})
|
||||
|
|
|
@ -80,4 +80,4 @@
|
|||
}
|
||||
]
|
||||
};
|
||||
}) satisfies ClockInfoFunc
|
||||
}) satisfies ClockInfoFunc // FIXME: semi-colon added automatically when Typescript generates Javascript file?
|
||||
|
|
|
@ -10,4 +10,6 @@
|
|||
0.09: Save clkinfo settings on kill and remove
|
||||
0.10: Fix focus bug when changing focus between two clock infos
|
||||
0.11: Prepend swipe listener if possible
|
||||
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle
|
||||
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle
|
||||
0.13: Cache loaded ClockInfos so if we have clockInfoWidget and a clock, we don't load them twice (saves ~300ms)
|
||||
0.14: Check for .clkinfocache and use that if exists (from boot 0.64)
|
|
@ -14,6 +14,8 @@ if (stepGoal == undefined) {
|
|||
exports.loadCount = 0;
|
||||
/// A list of all the instances returned by addInteractive
|
||||
exports.clockInfos = [];
|
||||
/// A list of loaded clockInfos
|
||||
exports.clockInfoMenus = undefined;
|
||||
|
||||
/// Load the settings, with defaults
|
||||
exports.loadSettings = function() {
|
||||
|
@ -29,6 +31,8 @@ exports.loadSettings = function() {
|
|||
|
||||
/// Load a list of ClockInfos - this does not cache and reloads each time
|
||||
exports.load = function() {
|
||||
if (exports.clockInfoMenus)
|
||||
return exports.clockInfoMenus;
|
||||
var settings = exports.loadSettings();
|
||||
delete settings.apps; // keep just the basic settings in memory
|
||||
// info used for drawing...
|
||||
|
@ -131,10 +135,14 @@ exports.load = function() {
|
|||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||
});
|
||||
}
|
||||
|
||||
var clkInfoCache = require('Storage').read('.clkinfocache');
|
||||
if (clkInfoCache!==undefined) {
|
||||
// note: code below is included in clkinfocache by bootupdate.js
|
||||
// we use clkinfocache if it exists as it's faster
|
||||
eval(clkInfoCache);
|
||||
} else require("Storage").list(/clkinfo\.js$/).forEach(fn => {
|
||||
// In case there exists already a menu object b with the same name as the next
|
||||
// object a, we append the items. Otherwise we add the new object a to the list.
|
||||
require("Storage").list(/clkinfo.js$/).forEach(fn => {
|
||||
try{
|
||||
var a = eval(require("Storage").read(fn))();
|
||||
var b = menu.find(x => x.name === a.name);
|
||||
|
@ -146,6 +154,7 @@ exports.load = function() {
|
|||
});
|
||||
|
||||
// return it all!
|
||||
exports.clockInfoMenus = menu;
|
||||
return menu;
|
||||
};
|
||||
|
||||
|
@ -345,6 +354,9 @@ exports.addInteractive = function(menu, options) {
|
|||
menuHideItem(menu[options.menuA].items[options.menuB]);
|
||||
exports.loadCount--;
|
||||
delete exports.clockInfos[options.index];
|
||||
// If nothing loaded now, clear our list of loaded menus
|
||||
if (exports.loadCount==0)
|
||||
exports.clockInfoMenus = undefined;
|
||||
};
|
||||
options.redraw = function() {
|
||||
drawItem(menu[options.menuA].items[options.menuB]);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "clock_info",
|
||||
"name": "Clock Info Module",
|
||||
"shortName": "Clock Info",
|
||||
"version":"0.12",
|
||||
"version":"0.14",
|
||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
0.04: More options for different background colors
|
||||
'Plasma' generative background
|
||||
Add a 'view' option in settings menu to view the current background
|
||||
0.05: Random square+plasma speed improvements (~2x faster)
|
||||
0.05: Random square+plasma speed improvements (~2x faster)
|
||||
0.06: 25% speed improvement if Math.randInt exists (2v25 fw)
|
|
@ -1,6 +1,7 @@
|
|||
let settings;
|
||||
|
||||
exports.reload = function() {
|
||||
//let t = Date.now();
|
||||
settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
|
@ -17,7 +18,8 @@ exports.reload = function() {
|
|||
let bpp = (settings.colors.length>4)?4:2;
|
||||
let bg = Graphics.createArrayBuffer(11,11,bpp,{msb:true});
|
||||
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
|
||||
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
if (Math.randInt) E.mapInPlace(u32, u32, Math.randInt); // random pixels
|
||||
else E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
bg.buffer[bg.buffer.length-1]=Math.random()*256; // 11x11 isn't a multiple of 4 bytes - we need to set the last one!
|
||||
bg.palette = new Uint16Array(1<<bpp);
|
||||
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
|
||||
|
@ -28,7 +30,8 @@ exports.reload = function() {
|
|||
settings.style = "image";
|
||||
let bg = Graphics.createArrayBuffer(16,16,4,{msb:true});
|
||||
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
|
||||
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
if (Math.randInt) E.mapInPlace(u32, u32, Math.randInt); // random pixels
|
||||
else E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
bg.filter([ // a gaussian filter to smooth out
|
||||
1, 4, 7, 4, 1,
|
||||
4,16,26,16, 4,
|
||||
|
@ -42,6 +45,7 @@ exports.reload = function() {
|
|||
settings.imgOpt = {scale:11};
|
||||
delete settings.colors;
|
||||
}
|
||||
//console.log("bg",Date.now()-t);
|
||||
};
|
||||
exports.reload();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "clockbg",
|
||||
"name": "Clock Backgrounds",
|
||||
"shortName":"Backgrounds",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Library that allows clocks to include a custom background (generated on demand or uploaded).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
|
|
|
@ -114,4 +114,4 @@
|
|||
};
|
||||
// Show the menu
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
"showDate", "hideWidgets"
|
||||
]);
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -52,4 +52,4 @@
|
|||
};
|
||||
// Show the menu
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -61,4 +61,4 @@
|
|||
}
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -56,4 +56,3 @@
|
|||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
|
|
@ -54,4 +54,4 @@
|
|||
},
|
||||
'< Back': back
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -45,4 +45,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -41,4 +41,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.04: Enhance menu: enable bluetooth, visit settings & visit recovery
|
||||
0.05: Enhance menu: permit toggling bluetooth
|
||||
0.06: Display clock in green when charging, with "charging" text
|
||||
0.07: Correctly restore full power when the charged threshold is reached
|
||||
|
|
|
@ -88,7 +88,7 @@ var reload = function () {
|
|||
};
|
||||
reload();
|
||||
Bangle.emit("drained", E.getBattery());
|
||||
var _a = require("Storage").readJSON("".concat(app, ".setting.json"), true) || {}, _b = _a.keepStartup, keepStartup = _b === void 0 ? true : _b, _c = _a.restore, restore = _c === void 0 ? 20 : _c, _d = _a.exceptions, exceptions = _d === void 0 ? ["widdst.0"] : _d;
|
||||
var _a = require("Storage").readJSON("".concat(app, ".setting.json"), true) || {}, _b = _a.keepStartup, keepStartup = _b === void 0 ? true : _b, _c = _a.restore, restore = _c === void 0 ? 20 : _c, _d = _a.exceptions, exceptions = _d === void 0 ? ["widdst.0"] : _d, _e = _a.interval, interval = _e === void 0 ? 10 : _e;
|
||||
function drainedRestore() {
|
||||
if (!keepStartup) {
|
||||
try {
|
||||
|
@ -110,8 +110,10 @@ var checkCharge = function () {
|
|||
if (Bangle.isCharging())
|
||||
checkCharge();
|
||||
Bangle.on("charging", function (charging) {
|
||||
if (drainedInterval)
|
||||
drainedInterval = clearInterval(drainedInterval);
|
||||
if (charging)
|
||||
checkCharge();
|
||||
drainedInterval = setInterval(checkCharge, interval * 60 * 1000);
|
||||
});
|
||||
if (!keepStartup) {
|
||||
var storage = require("Storage");
|
||||
|
|
|
@ -115,7 +115,7 @@ reload();
|
|||
Bangle.emit("drained", E.getBattery());
|
||||
|
||||
// restore normal boot on charge
|
||||
const { keepStartup = true, restore = 20, exceptions = ["widdst.0"] }: DrainedSettings
|
||||
const { keepStartup = true, restore = 20, exceptions = ["widdst.0"], interval = 10 }: DrainedSettings
|
||||
= require("Storage").readJSON(`${app}.setting.json`, true) || {};
|
||||
|
||||
// re-enable normal boot code when we're above a threshold:
|
||||
|
@ -142,7 +142,10 @@ if (Bangle.isCharging())
|
|||
checkCharge();
|
||||
|
||||
Bangle.on("charging", charging => {
|
||||
if(charging) checkCharge();
|
||||
if(drainedInterval)
|
||||
drainedInterval = clearInterval(drainedInterval) as undefined;
|
||||
if(charging)
|
||||
drainedInterval = setInterval(checkCharge, interval * 60 * 1000);
|
||||
});
|
||||
|
||||
if(!keepStartup){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "drained",
|
||||
"name": "Drained",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -87,4 +87,4 @@
|
|||
});
|
||||
};
|
||||
updateAndRedraw();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -55,4 +55,4 @@
|
|||
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -30,3 +30,4 @@ when moving pages. Add caching for faster startups.
|
|||
0.23: Bangle 1: Fix issue with missing icons, added touch screen interactions
|
||||
0.24: Add buzz-on-interaction setting
|
||||
0.25: Minor code improvements
|
||||
0.26: Bangle 2: Postpone loading icons that are not needed initially.
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
s.writeJSON("launch.cache.json", launchCache);
|
||||
}
|
||||
let apps = launchCache.apps;
|
||||
apps.forEach(app=>{
|
||||
if (app.icon)
|
||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
for (let i = 0; i < 4; i++) { // Initially only load icons for the current page.
|
||||
if (apps[i].icon)
|
||||
apps[i].icon = s.read(apps[i].icon); // should just be a link to a memory area
|
||||
}
|
||||
|
||||
let Napps = apps.length;
|
||||
let Npages = Math.ceil(Napps/4);
|
||||
|
@ -101,6 +101,11 @@
|
|||
Bangle.loadWidgets();
|
||||
drawPage(0);
|
||||
|
||||
for (let i = 4; i < apps.length; i++) { // Load the rest of the app icons that were not initially.
|
||||
if (apps[i].icon)
|
||||
apps[i].icon = s.read(apps[i].icon); // should just be a link to a memory area
|
||||
}
|
||||
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
updateTimeoutToClock();
|
||||
selected = -1;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.25",
|
||||
"version": "0.26",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -65,4 +65,4 @@
|
|||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -28,4 +28,4 @@
|
|||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -248,4 +248,4 @@
|
|||
});
|
||||
};
|
||||
showMainMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -29,4 +29,4 @@
|
|||
}
|
||||
// Initially show the menu
|
||||
showMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -38,4 +38,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -45,4 +45,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -99,4 +99,4 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(function(back) {
|
||||
// just go right to our app - we need all the memory
|
||||
// just go right to our app - we need all the memory */
|
||||
load("gpsrec.app.js");
|
||||
})();
|
||||
})()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(function(back) {
|
||||
// just go right to our app
|
||||
/* just go right to our app*/
|
||||
load("gpssetup.app.js");
|
||||
})();
|
||||
})()
|
||||
|
|
|
@ -21,3 +21,4 @@
|
|||
0.34: Fix 'fast load' so clock doesn't always redraw when screen unlocked/locked
|
||||
0.35: Minor code improvements
|
||||
0.36: Minor code improvements
|
||||
0.37: Fix settings (show default as '90' not 'off')
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.36",
|
||||
"version": "0.37",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
var FILE = "hworldclock.json";
|
||||
var settings = Object.assign({
|
||||
secondsOnUnlock: false,
|
||||
rotationTarget: "90",
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
|
@ -55,4 +56,4 @@
|
|||
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
|
@ -50,4 +50,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
Broke out config loading into separate file to avoid duplicating a whole bunch of code
|
||||
Added support for fast loading
|
||||
0.10: Minor code improvements
|
||||
0.11: Make sure variables are properly defined in settings.js
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "infoclk",
|
||||
"name": "Informational clock",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
|
@ -41,4 +41,4 @@
|
|||
"dependencies": {
|
||||
"weather": "app"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
} else return '' + hour;
|
||||
}
|
||||
|
||||
let minute; // Is used in onchange functions. Defined here to appease the linter.
|
||||
let hour; // Is used in onchange functions. Defined here to appease the linter.
|
||||
|
||||
// The menu for configuring when the seconds are shown
|
||||
function showSecondsMenu() {
|
||||
E.showMenu({
|
||||
|
@ -744,4 +747,4 @@
|
|||
}
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Accents and extended mode characters
|
||||
0.03: Bugfix - draw initial text after the back button has been removed
|
||||
|
|
|
@ -12,7 +12,14 @@ To display the in app character chart, long press the screen; you can scroll thr
|
|||
|
||||
For a full character chart see [EwChart.pdf](EwChart.pdf)
|
||||
|
||||
**Supported:** Letters (including capitals), numbers, backspace, word backspace, space, punctuation, new line, and some cursor controls (left, right, word left/right, home, end).
|
||||
**Supported:** Letters (including capitals), numbers, backspace, word backspace, space, punctuation, new line, accents, extended mode (if characters are supported by the vector font), and some cursor controls (left, right, word left/right, home, end).
|
||||
|
||||
**Unsupported:** Extended mode, accents, and word-level stroking.
|
||||
**Unsupported:** Word-level stroking.
|
||||
|
||||
## Settings
|
||||
|
||||
Font size can be selected in Settings app > "Apps" > "EdgeWrite Keyboard"
|
||||
|
||||
## Author
|
||||
|
||||
Woogal [github](https://github.com/retcurve)
|
||||
|
|
|
@ -193,17 +193,93 @@
|
|||
"2425": "`",
|
||||
"31": " \n",
|
||||
"24": " ",
|
||||
"46": "\xb7",
|
||||
"432146": "\xb0",
|
||||
"412346": "\xb0",
|
||||
"123246": "\xae",
|
||||
"2123246": "\xae",
|
||||
"123146": "\xae",
|
||||
"2123146": "\xae",
|
||||
"2346": "\xac",
|
||||
"32146": "\xa9",
|
||||
"41236": "\xa2",
|
||||
"24316": "\xd7",
|
||||
"31246": "\xd7",
|
||||
"316": "\xf7",
|
||||
"136": "\xf7",
|
||||
"232146": "\x80",
|
||||
"23246": "\x80",
|
||||
"132146": "\x80",
|
||||
"412316": "\x80",
|
||||
"323146": "\x80",
|
||||
"324146": "\x80",
|
||||
"24346": "\xa5",
|
||||
"243416": "\xa5",
|
||||
"2143416": "\xa5",
|
||||
"34146": "\xf0",
|
||||
"342146": "\xf0",
|
||||
"343146": "\xf0",
|
||||
"4214346": "\xf0",
|
||||
"414346": "\xf0",
|
||||
"434146": "\xf0",
|
||||
"123416": "\xf0",
|
||||
"2123416": "\xf0",
|
||||
"1346": "\xe6",
|
||||
"1246": "\xe6",
|
||||
"13416": "\xe6",
|
||||
"12416": "\xe6",
|
||||
"3214346": "\xe6",
|
||||
"21416": "\xdf",
|
||||
"213416": "\xdf",
|
||||
"212416": "\xdf",
|
||||
"141216": "\xdf",
|
||||
"1341216": "\xdf",
|
||||
"121416": "\xdf",
|
||||
"1232416": "\xdf",
|
||||
"1231416": "\xdf",
|
||||
"21232416": "\xdf",
|
||||
"21231416": "\xdf",
|
||||
"2321416": "\xdf",
|
||||
"2146": "\xa3",
|
||||
"21436": "\xb5",
|
||||
"214346": "\xb5",
|
||||
"121436": "\xb5",
|
||||
"1214346": "\xb5",
|
||||
"3214316": "\xf8",
|
||||
"3412316": "\xf8",
|
||||
"4126": "\xbf",
|
||||
"216": "\xa1",
|
||||
"346": "\xa6",
|
||||
"21236": "\xb1",
|
||||
"212326": "\xb1",
|
||||
"31426": "\xa4",
|
||||
"24136": "\xa4",
|
||||
"3146": "\xab",
|
||||
"2416": "\xbb",
|
||||
"32": "#bs",
|
||||
"41": "#wbs",
|
||||
"12": "#pu-on",
|
||||
"43": "#pu-on",
|
||||
"325": "#pu-off",
|
||||
"415": "#pu-off",
|
||||
"42": "#ex-on",
|
||||
"326": "#ex-off",
|
||||
"416": "#ex-off",
|
||||
"323": "#cur-left",
|
||||
"232": "#cur-right",
|
||||
"414": "#cur-word-left",
|
||||
"141": "#cur-word-right",
|
||||
"4141": "#cur-home",
|
||||
"1414": "#cur-end"
|
||||
"1414": "#cur-end",
|
||||
"242": "#grave",
|
||||
"313": "#acute",
|
||||
"431": "#circumflex",
|
||||
"421": "#circumflex",
|
||||
"3421": "#tilde",
|
||||
"43412": "#umlaut",
|
||||
"43214": "#ring",
|
||||
"41234": "#ring",
|
||||
"142": "#cedilla",
|
||||
"143": "#cedilla"
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,20 @@ exports.input = function(options) {
|
|||
let chartX = 0;
|
||||
let chartY = 0;
|
||||
|
||||
let settings = Object.assign({
|
||||
fontSize: 32,
|
||||
}, require('Storage').readJSON("kbedgewrite.json", true));
|
||||
|
||||
let shouldShowWidgetBar = Bangle.appRect.y > 0;
|
||||
|
||||
options = options||{};
|
||||
let text = options.text;
|
||||
// Substring doesn't play well with UTF8
|
||||
if (E.isUTF8(text)) {
|
||||
text = E.decodeUTF8(text);
|
||||
}
|
||||
let wrappedText = '';
|
||||
|
||||
if ('string' != typeof text) text='';
|
||||
|
||||
// Colours for number of corner occurrences
|
||||
|
@ -18,40 +28,140 @@ exports.input = function(options) {
|
|||
|
||||
const cornerSize = g.getWidth() / 3;
|
||||
let punctuationMode = false;
|
||||
let extendedMode = false;
|
||||
let path = '';
|
||||
let cursorPos = text.length;
|
||||
let chartShown = false;
|
||||
|
||||
let characterSet = Object.assign({}, require('Storage').readJSON('kbedgewrite.charset.json', true) || {});
|
||||
|
||||
function draw() {
|
||||
g.clearRect(Bangle.appRect).setClipRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2);
|
||||
const accentedCharacters = {
|
||||
'#grave': {
|
||||
'a': String.fromCharCode(0xE0),
|
||||
'A': String.fromCharCode(0xC0),
|
||||
'e': String.fromCharCode(0xE8),
|
||||
'E': String.fromCharCode(0xC8),
|
||||
'i': String.fromCharCode(0xEC),
|
||||
'I': String.fromCharCode(0xCC),
|
||||
'o': String.fromCharCode(0xF2),
|
||||
'O': String.fromCharCode(0xD2),
|
||||
'u': String.fromCharCode(0xF9),
|
||||
'U': String.fromCharCode(0xD9)
|
||||
},
|
||||
'#acute': {
|
||||
'a': String.fromCharCode(0xE1),
|
||||
'A': String.fromCharCode(0xC1),
|
||||
'e': String.fromCharCode(0xE9),
|
||||
'E': String.fromCharCode(0xC9),
|
||||
'i': String.fromCharCode(0xED),
|
||||
'I': String.fromCharCode(0xCD),
|
||||
'o': String.fromCharCode(0xF3),
|
||||
'O': String.fromCharCode(0xD3),
|
||||
'u': String.fromCharCode(0xFA),
|
||||
'U': String.fromCharCode(0xDA),
|
||||
'y': String.fromCharCode(0xFD),
|
||||
'Y': String.fromCharCode(0xDD)
|
||||
},
|
||||
'#circumflex': {
|
||||
'a': String.fromCharCode(0xE2),
|
||||
'A': String.fromCharCode(0xC2),
|
||||
'e': String.fromCharCode(0xEA),
|
||||
'E': String.fromCharCode(0xCA),
|
||||
'i': String.fromCharCode(0xEE),
|
||||
'I': String.fromCharCode(0xCE),
|
||||
'o': String.fromCharCode(0xF4),
|
||||
'O': String.fromCharCode(0xD4),
|
||||
'u': String.fromCharCode(0xFB),
|
||||
'U': String.fromCharCode(0xDB)
|
||||
},
|
||||
'#umlaut': {
|
||||
'a': String.fromCharCode(0xE4),
|
||||
'A': String.fromCharCode(0xC4),
|
||||
'e': String.fromCharCode(0xEB),
|
||||
'E': String.fromCharCode(0xCB),
|
||||
'i': String.fromCharCode(0xEF),
|
||||
'I': String.fromCharCode(0xCF),
|
||||
'o': String.fromCharCode(0xF6),
|
||||
'O': String.fromCharCode(0xD6),
|
||||
'u': String.fromCharCode(0xFC),
|
||||
'U': String.fromCharCode(0xDC),
|
||||
'y': String.fromCharCode(0xFF)
|
||||
},
|
||||
'#tilde': {
|
||||
'a': String.fromCharCode(0xE3),
|
||||
'A': String.fromCharCode(0xC3),
|
||||
'n': String.fromCharCode(0xF1),
|
||||
'N': String.fromCharCode(0xD1),
|
||||
'o': String.fromCharCode(0xF5),
|
||||
'O': String.fromCharCode(0xD5)
|
||||
},
|
||||
'#ring': {
|
||||
'a': String.fromCharCode(0xE5),
|
||||
'A': String.fromCharCode(0xC5)
|
||||
},
|
||||
'#cedilla': {
|
||||
'c': String.fromCharCode(0xE7),
|
||||
'C': String.fromCharCode(0xC7)
|
||||
},
|
||||
|
||||
// Draw the text string
|
||||
let l = g.setFont('6x8:4').wrapString(text.substring(0, cursorPos) + '_' + text.substring(cursorPos), g.getWidth());
|
||||
if (!l) l = [];
|
||||
if (l.length>5) {
|
||||
};
|
||||
|
||||
function wrapText() {
|
||||
let stringToWrap = text.substring(0, cursorPos) + '_' + text.substring(cursorPos);
|
||||
let l = [];
|
||||
let startPos = 0;
|
||||
|
||||
g.setFont("Vector", settings.fontSize); // set the font so we can calculate a string width
|
||||
|
||||
// Wrap the string into array of lines that will fit the screen width
|
||||
for (let i = 0; i < stringToWrap.length; i++) {
|
||||
// wrap if string is too long or we hit a line break
|
||||
if (stringToWrap.charCodeAt(i) == 10 || g.stringWidth(stringToWrap.substring(startPos, i+1)) > 176) {
|
||||
l.push(stringToWrap.substring(startPos, i));
|
||||
// skip the line break
|
||||
if (stringToWrap.charCodeAt(i) == 10) {
|
||||
i++;
|
||||
}
|
||||
startPos = i;
|
||||
}
|
||||
}
|
||||
// Add the final line
|
||||
l.push(stringToWrap.substring(startPos));
|
||||
|
||||
// Number of lines that can fit on the screen
|
||||
let numLines = Math.floor(g.getHeight() / g.getFontHeight());
|
||||
|
||||
// If too many lines, reposition so the cursor can be seen
|
||||
if (l.length > numLines) {
|
||||
let textPos = 0;
|
||||
let lineNum;
|
||||
for (lineNum = 0; lineNum < l.length; lineNum++) {
|
||||
textPos = textPos + l[lineNum].length - 1;
|
||||
textPos = textPos + l[lineNum].length;
|
||||
if (textPos >= cursorPos) break;
|
||||
}
|
||||
l=l.slice(lineNum - l.length - 5);
|
||||
l=l.slice(lineNum - l.length - numLines + 1);
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(l.join('\n'), Bangle.appRect.x, Bangle.appRect.y);
|
||||
|
||||
// Draw punctuation flag
|
||||
if (punctuationMode > 0) {
|
||||
wrappedText = l.join('\n');
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clearRect(Bangle.appRect).setClipRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector", settings.fontSize);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(wrappedText, Bangle.appRect.x, Bangle.appRect.y);
|
||||
|
||||
// Draw punctuation or extended flags
|
||||
if (punctuationMode || extendedMode) {
|
||||
let x = (g.getWidth() / 2) - 12;
|
||||
let y = g.getHeight() - 32;
|
||||
g.setColor('#F00');
|
||||
g.setColor(punctuationMode ? '#F00' : '#0F0');
|
||||
g.fillRect(x,y,x+24,y+32);
|
||||
g.setColor('#FFF');
|
||||
g.setFont('6x8:4');
|
||||
g.drawString('P', x+4, y+4, false);
|
||||
g.drawString(punctuationMode ? 'P' : 'E', x+4, y+4, false);
|
||||
}
|
||||
|
||||
// Draw corners
|
||||
|
@ -69,30 +179,27 @@ exports.input = function(options) {
|
|||
}
|
||||
|
||||
function processPath() {
|
||||
let capital = false;
|
||||
|
||||
// Punctuation paths end in 5
|
||||
if (punctuationMode) {
|
||||
path = path + '5';
|
||||
}
|
||||
|
||||
// Capital letters end in 2, remove that and set a capital flag
|
||||
// but only if the path isn't 232 (cursor right)
|
||||
if (path != '232' && path.length > 2 && path.slice(-1) == '2') {
|
||||
path = path.slice(0,-1);
|
||||
capital = true;
|
||||
// Extended paths end in 6
|
||||
if (extendedMode) {
|
||||
path = path + '6';
|
||||
}
|
||||
|
||||
// Find character from path
|
||||
let char = characterSet[path];
|
||||
|
||||
// Handle capitals
|
||||
if (capital && char != 'undefined') {
|
||||
if (char.charCodeAt(0)>96 && char.charCodeAt(0)<123) {
|
||||
char = char.toUpperCase();
|
||||
} else {
|
||||
// Anything that can't be capitalised is an invalid path
|
||||
char = undefined;
|
||||
// Unknown character, but ends in a 2 so may be a capital letter
|
||||
if (char == 'undefined' && path.slice(-1) == '2') {
|
||||
// Remove the 2 and look for a letter
|
||||
char = characterSet[path.slice(0,-1)];
|
||||
// Handle capitals
|
||||
if (char != 'undefined') {
|
||||
if (char.charCodeAt(0)>96 && char.charCodeAt(0)<123) {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +236,17 @@ exports.input = function(options) {
|
|||
punctuationMode = false;
|
||||
break;
|
||||
}
|
||||
// Enable extended mode
|
||||
case '#ex-on': {
|
||||
extendedMode = true;
|
||||
break;
|
||||
}
|
||||
// Disable extended mode
|
||||
case '#ex-off': {
|
||||
extendedMode = false;
|
||||
break;
|
||||
}
|
||||
// Cursor controls
|
||||
case '#cur-left': {
|
||||
if (cursorPos > 0) {
|
||||
cursorPos--;
|
||||
|
@ -168,6 +286,22 @@ exports.input = function(options) {
|
|||
cursorPos = text.length;
|
||||
break;
|
||||
}
|
||||
// Accents
|
||||
case '#grave':
|
||||
case '#acute':
|
||||
case '#circumflex':
|
||||
case '#umlaut':
|
||||
case '#tilde':
|
||||
case '#ring':
|
||||
case '#cedilla':
|
||||
// If the previous character can be accented, replace it with the accented version
|
||||
if (cursorPos > 0) {
|
||||
char = accentedCharacters[char][text.substring(cursorPos-1, cursorPos)];
|
||||
if (char != 'undefined') {
|
||||
text = text.substring(0, cursorPos-1) + char + text.substring(cursorPos);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Append character
|
||||
default: {
|
||||
text = text.substring(0, cursorPos) + char + text.substring(cursorPos);
|
||||
|
@ -184,6 +318,7 @@ exports.input = function(options) {
|
|||
if (!chartShown) {
|
||||
if (e.b == 0) { // Finger lifted, process completed path
|
||||
processPath();
|
||||
wrapText();
|
||||
draw();
|
||||
} else {
|
||||
let corner = 0;
|
||||
|
@ -217,10 +352,6 @@ exports.input = function(options) {
|
|||
}
|
||||
};
|
||||
|
||||
// Draw initial string
|
||||
require("widget_utils").hide();
|
||||
g.setBgColor(g.theme.bg);
|
||||
draw();
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
Bangle.setUI({
|
||||
|
@ -249,6 +380,13 @@ exports.input = function(options) {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Draw initial string
|
||||
require("widget_utils").hide();
|
||||
g.setBgColor(g.theme.bg);
|
||||
wrapText();
|
||||
draw();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
{ "id": "kbedgewrite",
|
||||
"name": "EdgeWrite keyboard",
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "A library for text input via EdgeWrite swipe gestures",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"},
|
||||
{"name":"kbedgewrite.charset.json","url":"characterset.json"}
|
||||
{"name":"kbedgewrite.charset.json","url":"characterset.json"},
|
||||
{"name":"kbedgewrite.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"kbedgewrite.json"}
|
||||
]
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,40 @@
|
|||
(function(back) {
|
||||
var FILE = 'kbedgewrite.json';
|
||||
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
fontSize: 32
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function setSetting(key,value) {
|
||||
settings[key] = value;
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values and their labels
|
||||
function stringItems(key, startvalue, values, labels) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => labels[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
setSetting(key,values[v]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values, labels) {
|
||||
return stringItems(name,settings[name], values, labels);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
'' : { 'title' : 'EdgeWrite' },
|
||||
'< Back' : () => back(),
|
||||
'Font Size': stringInSettings('fontSize', [24, 32, 48], ['Small', 'Medium', 'Large'])
|
||||
});
|
||||
})
|
|
@ -82,4 +82,4 @@
|
|||
};
|
||||
|
||||
E.showMenu(mainMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -34,4 +34,4 @@
|
|||
}
|
||||
};
|
||||
E.showMenu(appMenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -515,12 +515,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/widbt_notify/widget.js": {
|
||||
"hash": "16372ffcbc6bd1419ca326c7da40c2195f82a4bfceb6f123c15872624c4f0adf",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/widbgjs/widget.js": {
|
||||
"hash": "9852ce9aafb0a1ca3029d497282c8cdf07438ea36a3323313bad5b7569b1081b",
|
||||
"rules": [
|
||||
|
@ -534,7 +528,7 @@ module.exports = {
|
|||
]
|
||||
},
|
||||
"apps/usgs/settings.js": {
|
||||
"hash": "af1b7bc7e041c1e6988b407b6c8ee66dbd6a0e181a20caf102d2abdb6dbd5ac0",
|
||||
"hash": "00ee672a6920f5667bfbd2988fd2853cfd579895a843ae036a00028dcb13878d",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
|
@ -1157,12 +1151,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/banglebridge/widget.js": {
|
||||
"hash": "4ee8d6749e1d0e28c58ad871fd9f6ccbca2d716bb4fbd3511ba4c34a6a5897e1",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/bad/bad.app.js": {
|
||||
"hash": "d1354613102818190dd4e6e28fd715db7dc4d51b8e618cae61a3135529cc97eb",
|
||||
"rules": [
|
||||
|
|
|
@ -177,7 +177,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mär 2020 // 01.03.20
|
||||
abmonth: "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
|
||||
|
@ -194,7 +194,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%Y/%m/%d", 1: "%y/%m/%d" },
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
|
@ -210,7 +210,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d %b %Y", 1: "%d-%m-%Y" }, // 28 feb 2020 // 28-02-2020
|
||||
abday: "zo,ma,di,wo,do,vr,za",
|
||||
|
@ -258,7 +258,7 @@ var locales = {
|
|||
speed: "km/h",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d %B %Y", "1": "%d/%m/%Y" }, // 1 mars 2020 // 01/03/2020
|
||||
abmonth: "janv,févr,mars,avril,mai,juin,juil,août,sept,oct,nov,déc",
|
||||
|
@ -290,7 +290,7 @@ var locales = {
|
|||
speed: 'km/h',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%B %d %Y", "1": "%Y-%m-%d" }, // March 1 2020 // 2020-03-01
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
|
@ -306,7 +306,7 @@ var locales = {
|
|||
speed: "km/t",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b. %Y", 1: "%d/%m %Y" }, // 1. feb. 2020 // 01/02 2020 // a better short ver. is 1/2 2020 but its not supported
|
||||
abmonth: "jan,feb,mar,apr,maj,jun,jul,aug,sep,okt,nov,dec",
|
||||
|
@ -322,7 +322,7 @@ var locales = {
|
|||
speed: "km/h",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b. %Y", 1: "%d/%m %Y" }, // 1. feb. 2020 // 01/02 2020 // a better short ver. is 1/2 2020 but its not supported
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
|
@ -370,7 +370,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%y" }, // Sonntag, 1. März 2020 // 01.03.20
|
||||
abmonth: "Jän,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
|
||||
|
@ -403,7 +403,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%A, %d de %B de %Y", "1": "%d/%m/%y" }, // domingo, 1 de marzo de 2020 // 01/03/20
|
||||
abmonth: "ene,feb,mar,abr,may,jun,jul,ago,sept,oct,nov,dic",
|
||||
|
@ -420,7 +420,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20
|
||||
abmonth: "janv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.",
|
||||
|
@ -484,7 +484,7 @@ var locales = {
|
|||
speed: 'kmh',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM.%SS", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
|
@ -500,7 +500,7 @@ var locales = {
|
|||
speed: 'kmh',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM.%SS", 1: "%HH:%MM" }, // 17:00.00 // 17:00
|
||||
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
|
||||
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
|
||||
|
@ -516,7 +516,7 @@ var locales = {
|
|||
speed: 'kmh',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH.%MM.%SS", 1: "%HH.%MM" }, // 17.00.00 // 17.00
|
||||
datePattern: { 0: "%A, %d. %B %Y", "1": "%Y-%m-%d" }, // Sunntag, 1. Märze 2020 // 2020-03-01
|
||||
abmonth: "Jen,Hor,Mär,Abr,Mei,Brá,Hei,Öig,Her,Wím,Win,Chr",
|
||||
|
@ -564,7 +564,7 @@ var locales = {
|
|||
speed: "km/h",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%A %d %B de %Y", "1": "%d/%m/%Y" }, // dimenge 1 de març de 2020 // 01/03/2020
|
||||
abmonth: "gen.,febr.,març,abril,mai,junh,julh,ago.,set.,oct.,nov.,dec.",
|
||||
|
@ -660,7 +660,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2021 // 01.03.2021
|
||||
abmonth: "Sty,Lut,Mar,Kwi,Maj,Cze,Lip,Sie,Wrz,Paź,Lis,Gru",
|
||||
|
@ -676,7 +676,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
|
||||
abmonth: "Jan,Feb,Mar,Apr,Mai,Jūn,Jūl,Aug,Sep,Okt,Nov,Dec",
|
||||
|
@ -692,7 +692,7 @@ var locales = {
|
|||
speed: "kmt",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
|
||||
abmonth: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des",
|
||||
|
@ -708,7 +708,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
|
||||
abmonth: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des",
|
||||
|
@ -725,7 +725,7 @@ var locales = {
|
|||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d %B %Y", "1": "%d/%m/%y" },
|
||||
abmonth: "gen.,febr.,març,abr.,maig,juny,jul.,ag.,set.,oct.,nov.,des.",
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*/
|
||||
const datetime_length_map = {
|
||||
// %A, %a, %B, %b vary depending on the locale, so they are calculated later
|
||||
"%Y": [4, 4],
|
||||
"%y": [2, 2],
|
||||
"%m": [2, 2],
|
||||
"%-m": [1, 2],
|
||||
"%d": [2, 2],
|
||||
"%-d": [1, 2],
|
||||
"%HH": [2, 2],
|
||||
"%MM": [2, 2],
|
||||
"%SS": [2, 2],
|
||||
"%Y": [4, 4, "2024", "2024"],
|
||||
"%y": [2, 2, "24", "24"],
|
||||
"%m": [2, 2, "10", "10"],
|
||||
"%-m": [1, 2, "1", "10"],
|
||||
"%d": [2, 2, "10", "10"],
|
||||
"%-d": [1, 2, "1", "10"],
|
||||
"%HH": [2, 2, "10", "10"],
|
||||
"%MM": [2, 2, "10", "10"],
|
||||
"%SS": [2, 2, "10", "10"],
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -30,20 +30,21 @@ function getLengthOfDatetimeFormat(name, datetimeEspruino, locale, errors) {
|
|||
["%a", locale.abday],
|
||||
["%B", locale.month],
|
||||
["%b", locale.abmonth],
|
||||
]){
|
||||
]) {
|
||||
const length = [Infinity, 0];
|
||||
for(const value of values.split(",")){
|
||||
if(length[0] > value.length) length[0] = value.length;
|
||||
if(length[1] < value.length) length[1] = value.length;
|
||||
if(length[0] > value.length) { length[0] = value.length; length[2] = value; }
|
||||
if(length[1] < value.length) { length[1] = value.length; length[3] = value; }
|
||||
}
|
||||
length_map[symbol] = length;
|
||||
}
|
||||
|
||||
// Find the length of the output
|
||||
let formatLength = [0, 0];
|
||||
let formatLength = [0, 0, "", ""];
|
||||
let i = 0;
|
||||
while (i < datetimeEspruino.length) {
|
||||
if (datetimeEspruino[i] === "%") {
|
||||
let ch = datetimeEspruino[i];
|
||||
if (ch === "%") {
|
||||
let match;
|
||||
for(const symbolLength of [2, 3]){
|
||||
const length = length_map[datetimeEspruino.substring(i, i+symbolLength)];
|
||||
|
@ -57,16 +58,22 @@ function getLengthOfDatetimeFormat(name, datetimeEspruino, locale, errors) {
|
|||
if(match){
|
||||
formatLength[0] += match.length[0];
|
||||
formatLength[1] += match.length[1];
|
||||
formatLength[2] += match.length[2];
|
||||
formatLength[3] += match.length[3];
|
||||
i += match.symbolLength;
|
||||
}else{
|
||||
errors.push({name, value: datetimeEspruino, lang: locale.lang, error: `uses an unsupported format symbol: ${datetimeEspruino.substring(i, i+3)}`});
|
||||
formatLength[0]++;
|
||||
formatLength[1]++;
|
||||
formatLength[2]+=" ";
|
||||
formatLength[3]+=" ";
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
formatLength[0]++;
|
||||
formatLength[1]++;
|
||||
formatLength[2]+=ch;
|
||||
formatLength[3]+=ch;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +161,10 @@ function checkLocale(locale, {speedUnits, distanceUnits, codePages, CODEPAGE_CON
|
|||
function checkFormatLength(name, value, min, max) {
|
||||
const length = getLengthOfDatetimeFormat(name, value, locale, errors);
|
||||
if (min && length[0] < min) {
|
||||
errors.push({name, value, lang: locale.lang, error: `output must be longer than ${min-1} characters`});
|
||||
errors.push({name, value, lang: locale.lang, error: `output must be longer than ${min-1} characters (${length[2]} -> ${length[0]})`});
|
||||
}
|
||||
if (max && length[1] > max) {
|
||||
errors.push({name, value, lang: locale.lang, error: `output must be shorter than ${max+1} characters`});
|
||||
errors.push({name, value, lang: locale.lang, error: `output must be shorter than ${max+1} characters (${length[3]} -> ${length[1]})`});
|
||||
}
|
||||
}
|
||||
function checkIsIn(name, value, listName, list) {
|
||||
|
|
|
@ -110,4 +110,5 @@
|
|||
0.81: Fix issue stopping Music message for being marked as read
|
||||
Make sure play button image is transparent
|
||||
Add top-right menu to music playback to allow message to be deleted
|
||||
0.82: Add option to not open the first unread message
|
||||
0.82: Stop buzzing when a message is removed (e.g. call answered)
|
||||
0.83: Add option to not open the first unread message
|
||||
|
|
|
@ -18,6 +18,10 @@ exports.listener = function(type, msg) {
|
|||
clearTimeout(exports.messageTimeout);
|
||||
delete exports.messageTimeout;
|
||||
}
|
||||
if (type==="clearAll") {
|
||||
require("messages").stopBuzz();
|
||||
return;
|
||||
}
|
||||
if (msg.t==="remove") {
|
||||
// we won't open the UI for removed messages, so make sure to delete it from flash
|
||||
if (Bangle.MESSAGES) {
|
||||
|
@ -25,6 +29,8 @@ exports.listener = function(type, msg) {
|
|||
require("messages").apply(msg, Bangle.MESSAGES);
|
||||
if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES;
|
||||
}
|
||||
if(type!=="music")
|
||||
require("messages").stopBuzz();
|
||||
return require("messages").save(msg); // always write removal to flash
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.82",
|
||||
"version": "0.83",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -136,4 +136,4 @@
|
|||
}
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
})
|
||||
|
|
|
@ -102,4 +102,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -23,5 +23,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
});
|
||||
|
||||
})
|
||||
|
|
|
@ -63,4 +63,4 @@
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
})
|
||||
|
|
|
@ -45,4 +45,4 @@
|
|||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
});
|
||||
})
|
||||
|
|
|
@ -29,4 +29,4 @@
|
|||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -39,4 +39,4 @@
|
|||
|
||||
E.showMenu(mainmenu);
|
||||
|
||||
});
|
||||
})
|
||||
|
|
|
@ -22,4 +22,4 @@
|
|||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
|
@ -81,4 +81,4 @@
|
|||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
})
|
||||
|
|
|
@ -54,4 +54,4 @@
|
|||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue