Merge branch 'master' into clock_backgrounds
|
@ -0,0 +1,9 @@
|
|||
Contributing to BangleApps
|
||||
==========================
|
||||
|
||||
https://github.com/espruino/BangleApps?tab=readme-ov-file#getting-started
|
||||
has some links to tutorials on developing for Bangle.js.
|
||||
|
||||
Please check out the Wiki to get an idea what sort of things
|
||||
we'd like to see for contributed apps: https://github.com/espruino/BangleApps/wiki/App-Contribution
|
||||
|
|
@ -9,3 +9,4 @@
|
|||
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
||||
0.10: Add software back button via setUI
|
||||
0.11: Add setting to unlock screen
|
||||
0.12: Fix handling that dates can be given as ms since epoch.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Activity Reminder",
|
||||
"shortName":"Activity Reminder",
|
||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||
"version":"0.11",
|
||||
"version":"0.12",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,activity",
|
||||
|
|
|
@ -8,3 +8,5 @@
|
|||
0.08: Changed month to day and text color to black on date
|
||||
0.09: Changed day color back to white
|
||||
0.10: Add blinking when charging
|
||||
0.11: Changed battery to buzz instead of blink and fixed battery counter
|
||||
0.12: Got rid of battery counter
|
|
@ -38,14 +38,8 @@ function drawbatrect() {
|
|||
g.drawRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + sq, mgn + gap + sq);
|
||||
}
|
||||
|
||||
function clearbat() {
|
||||
g.clearRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + sq, mgn + gap + sq);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
||||
let i = 0;
|
||||
var cnt = 0;
|
||||
var dt = new Date();
|
||||
var h = dt.getHours();
|
||||
var m = dt.getMinutes();
|
||||
|
@ -100,15 +94,9 @@ function draw() {
|
|||
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
|
||||
}
|
||||
|
||||
if (cnt == 0) {
|
||||
if (settings.showbat) {
|
||||
drawbat();
|
||||
drawbatrect();
|
||||
}
|
||||
cnt++;
|
||||
if (cnt > 29) {
|
||||
cnt = 0;
|
||||
}
|
||||
if (settings.showbat) {
|
||||
drawbat();
|
||||
drawbatrect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,21 +109,6 @@ if (!settings.fullscreen) {
|
|||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
var blink = true;
|
||||
|
||||
function blinkbat() {
|
||||
if (blink) {
|
||||
clearbat();
|
||||
} else {
|
||||
drawbat();
|
||||
}
|
||||
drawbatrect();
|
||||
blink = !blink;
|
||||
}
|
||||
|
||||
function getcharging() {
|
||||
if (Bangle.isCharging()) {
|
||||
blinkbat();
|
||||
}
|
||||
}
|
||||
setInterval(getcharging, 1000);
|
||||
Bangle.on('charging', function(charging) {
|
||||
if(charging) Bangle.buzz();
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "binaryclk",
|
||||
"name": "Bin Clock",
|
||||
"version": "0.10",
|
||||
"version": "0.12",
|
||||
"description": "Binary clock with date and battery",
|
||||
"icon": "app-icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Fix double-button press if you press the next button within 30s (#3243)
|
||||
0.03: Cope with identical duplicate buttons (fix #3260)
|
||||
Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
|
||||
Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
|
||||
0.04: Fix duplicate button on edit->save
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "bthome",
|
||||
"name": "BTHome",
|
||||
"shortName":"BTHome",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
|
||||
"icon": "icon.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
(function(back) {
|
||||
var settings = require("Storage").readJSON("bthome.json",1)||{};
|
||||
if (!(settings.buttons instanceof Array))
|
||||
settings.buttons = [];
|
||||
var settings;
|
||||
|
||||
function loadSettings() {
|
||||
settings = require("Storage").readJSON("bthome.json",1)||{};
|
||||
if (!(settings.buttons instanceof Array))
|
||||
settings.buttons = [];
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
require("Storage").writeJSON("bthome.json",settings)
|
||||
|
@ -15,7 +19,10 @@
|
|||
}
|
||||
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
||||
var menu = {
|
||||
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back:showMenu},
|
||||
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back: () => {
|
||||
loadSettings(); // revert changes
|
||||
showMenu();
|
||||
}},
|
||||
/*LANG*/"Icon" : {
|
||||
value : "\0"+require("icons").getIcon(button.icon),
|
||||
onchange : () => {
|
||||
|
@ -49,7 +56,7 @@
|
|||
onchange : v => button.n=v
|
||||
},
|
||||
/*LANG*/"Save" : () => {
|
||||
settings.buttons.push(button);
|
||||
if (isNew) settings.buttons.push(button);
|
||||
saveSettings();
|
||||
showMenu();
|
||||
}
|
||||
|
@ -94,5 +101,7 @@
|
|||
});
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
showMenu();
|
||||
})
|
|
@ -42,3 +42,8 @@
|
|||
Add debug option for disabling active scanning
|
||||
0.17: New GUI based on layout library
|
||||
0.18: Minor code improvements
|
||||
0.19: Move caching of characteristics into settings app
|
||||
Changed default of active scanning to false
|
||||
Fix setHRMPower method not returning new state
|
||||
Only buzz for disconnect after switching on if there already was an actual connection
|
||||
Fix recorder not switching BTHRM on and off
|
||||
|
|
|
@ -21,6 +21,10 @@ Once installed you will have to go into this app's settings while your heart rat
|
|||
|
||||
**To disable this and return to normal HRM, uninstall the app or change the settings**
|
||||
|
||||
The characteristics of your selected sensor are cached in the settings. That means if your sensor changes, e.g. by firmware updates or similar, you will need to re-scan in the settings to update the cache of characteristics. This is done to take some complexity (and time) out of the boot process.
|
||||
|
||||
Scanning in the settings will do 10 retries and then give up on adding the sensor. Usually that works fine, if it does not for you just try multiple times. Currently saved sensor information is only replaced on a successful pairing. There are additional options in the Debug entry of the menu that can help with specific sensor oddities. Bonding and active scanning can help with connecting, but can also prevent some sensors from working. The "Grace Periods" just add some additional time at certain steps in the connection process which can help with stability or reconnect speed of some finicky sensors. Defaults should be fine for most.
|
||||
|
||||
### Modes
|
||||
|
||||
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
|
||||
|
@ -57,3 +61,7 @@ This replaces `Bangle.setHRMPower` with its own implementation.
|
|||
## Creator
|
||||
|
||||
Gordon Williams
|
||||
|
||||
## Contributer
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
||||
|
|
|
@ -57,7 +57,7 @@ var layout = new Layout( {
|
|||
{ type:undefined, height:8 } //dummy to protect debug output
|
||||
]
|
||||
}, {
|
||||
lazy:true
|
||||
lazy:false
|
||||
});
|
||||
|
||||
var int,agg,bt;
|
||||
|
@ -106,8 +106,7 @@ function draw(){
|
|||
layout.btContact.label = "--";
|
||||
layout.btEnergy.label = "--";
|
||||
}
|
||||
|
||||
layout.update();
|
||||
layout.clear();
|
||||
layout.render();
|
||||
let first = true;
|
||||
for (let c of layout.l.c){
|
||||
|
@ -122,26 +121,29 @@ function draw(){
|
|||
|
||||
|
||||
// This can get called for the boot code to show what's happening
|
||||
function showStatusInfo(txt) {
|
||||
global.showStatusInfo = function(txt) {
|
||||
var R = Bangle.appRect;
|
||||
g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
|
||||
txt = g.wrapString(txt, R.w)[0];
|
||||
g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
|
||||
}
|
||||
};
|
||||
|
||||
function onBtHrm(e) {
|
||||
bt = e;
|
||||
bt.time = Date.now();
|
||||
draw();
|
||||
}
|
||||
|
||||
function onInt(e) {
|
||||
int = e;
|
||||
int.time = Date.now();
|
||||
draw();
|
||||
}
|
||||
|
||||
function onAgg(e) {
|
||||
agg = e;
|
||||
agg.time = Date.now();
|
||||
draw();
|
||||
}
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
@ -162,7 +164,6 @@ Bangle.drawWidgets();
|
|||
if (Bangle.setBTHRMPower){
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
|
||||
setInterval(draw, 1000);
|
||||
} else {
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
"custom_fallbackTimeout": 10,
|
||||
"gracePeriodNotification": 0,
|
||||
"gracePeriodConnect": 0,
|
||||
"gracePeriodService": 0,
|
||||
"gracePeriodRequest": 0,
|
||||
"bonding": false,
|
||||
"active": true
|
||||
"active": false
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
exports.enable = () => {
|
||||
var settings = Object.assign(
|
||||
let settings = Object.assign(
|
||||
require('Storage').readJSON("bthrm.default.json", true) || {},
|
||||
require('Storage').readJSON("bthrm.json", true) || {}
|
||||
);
|
||||
|
||||
var log = function(text, param){
|
||||
let log = function(text, param){
|
||||
if (global.showStatusInfo)
|
||||
global.showStatusInfo(text);
|
||||
if (settings.debuglog){
|
||||
var logline = new Date().toISOString() + " - " + text;
|
||||
let logline = new Date().toISOString() + " - " + text;
|
||||
if (param) logline += ": " + JSON.stringify(param);
|
||||
print(logline);
|
||||
}
|
||||
|
@ -16,60 +16,33 @@ exports.enable = () => {
|
|||
|
||||
log("Settings: ", settings);
|
||||
|
||||
if (settings.enabled){
|
||||
//this is for compatibility with 0.18 and older
|
||||
let oldCache = require('Storage').readJSON("bthrm.cache.json", true);
|
||||
if(oldCache){
|
||||
settings.cache = oldCache;
|
||||
require('Storage').writeJSON("bthrm.json", settings);
|
||||
require('Storage').erase("bthrm.cache.json");
|
||||
}
|
||||
|
||||
var clearCache = function() {
|
||||
return require('Storage').erase("bthrm.cache.json");
|
||||
};
|
||||
if (settings.enabled && settings.cache){
|
||||
|
||||
var getCache = function() {
|
||||
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
|
||||
if (settings.btid && settings.btid === cache.id) return cache;
|
||||
clearCache();
|
||||
return {};
|
||||
};
|
||||
log("Start");
|
||||
|
||||
var addNotificationHandler = function(characteristic) {
|
||||
let addNotificationHandler = function(characteristic) {
|
||||
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||
};
|
||||
|
||||
var writeCache = function(cache) {
|
||||
var oldCache = getCache();
|
||||
if (oldCache !== cache) {
|
||||
log("Writing cache");
|
||||
require('Storage').writeJSON("bthrm.cache.json", cache);
|
||||
} else {
|
||||
log("No changes, don't write cache");
|
||||
}
|
||||
};
|
||||
|
||||
var characteristicsToCache = function(characteristics) {
|
||||
log("Cache characteristics");
|
||||
var cache = getCache();
|
||||
if (!cache.characteristics) cache.characteristics = {};
|
||||
for (var c of characteristics){
|
||||
//"handle_value":16,"handle_decl":15
|
||||
log("Saving handle " + c.handle_value + " for characteristic: ", c);
|
||||
cache.characteristics[c.uuid] = {
|
||||
"handle": c.handle_value,
|
||||
"uuid": c.uuid,
|
||||
"notify": c.properties.notify,
|
||||
"read": c.properties.read
|
||||
};
|
||||
}
|
||||
writeCache(cache);
|
||||
};
|
||||
|
||||
var characteristicsFromCache = function(device) {
|
||||
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||
let characteristicsFromCache = function(device) {
|
||||
let service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||
log("Read cached characteristics");
|
||||
var cache = getCache();
|
||||
let cache = settings.cache;
|
||||
if (!cache.characteristics) return [];
|
||||
var restored = [];
|
||||
for (var c in cache.characteristics){
|
||||
var cached = cache.characteristics[c];
|
||||
var r = new BluetoothRemoteGATTCharacteristic();
|
||||
let restored = [];
|
||||
for (let c in cache.characteristics){
|
||||
let cached = cache.characteristics[c];
|
||||
let r = new BluetoothRemoteGATTCharacteristic();
|
||||
log("Restoring characteristic ", cached);
|
||||
r.handle_value = cached.handle;
|
||||
r.uuid = cached.uuid;
|
||||
|
@ -84,26 +57,14 @@ exports.enable = () => {
|
|||
return restored;
|
||||
};
|
||||
|
||||
log("Start");
|
||||
|
||||
var lastReceivedData={
|
||||
};
|
||||
|
||||
var supportedServices = [
|
||||
"0x180d", // Heart Rate
|
||||
"0x180f", // Battery
|
||||
];
|
||||
|
||||
var bpmTimeout;
|
||||
|
||||
var supportedCharacteristics = {
|
||||
let supportedCharacteristics = {
|
||||
"0x2a37": {
|
||||
//Heart rate measurement
|
||||
active: false,
|
||||
handler: function (dv){
|
||||
var flags = dv.getUint8(0);
|
||||
let flags = dv.getUint8(0);
|
||||
|
||||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
let bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||
switchFallback();
|
||||
|
@ -114,42 +75,42 @@ exports.enable = () => {
|
|||
startFallback();
|
||||
}, 3000);
|
||||
|
||||
var sensorContact;
|
||||
let sensorContact;
|
||||
|
||||
if (flags & 2){
|
||||
sensorContact = !!(flags & 4);
|
||||
}
|
||||
|
||||
var idx = 2 + (flags&1);
|
||||
let idx = 2 + (flags&1);
|
||||
|
||||
var energyExpended;
|
||||
let energyExpended;
|
||||
if (flags & 8){
|
||||
energyExpended = dv.getUint16(idx,1);
|
||||
idx += 2;
|
||||
}
|
||||
var interval;
|
||||
let interval;
|
||||
if (flags & 16) {
|
||||
interval = [];
|
||||
var maxIntervalBytes = (dv.byteLength - idx);
|
||||
let maxIntervalBytes = (dv.byteLength - idx);
|
||||
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
|
||||
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||
for(let i = 0 ; i < maxIntervalBytes / 2; i++){
|
||||
interval[i] = dv.getUint16(idx,1); // in milliseconds
|
||||
idx += 2;
|
||||
}
|
||||
}
|
||||
|
||||
var location;
|
||||
let location;
|
||||
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
|
||||
location = lastReceivedData["0x180d"]["0x2a38"];
|
||||
}
|
||||
|
||||
var battery;
|
||||
let battery;
|
||||
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
|
||||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||
}
|
||||
|
||||
if (settings.replace && bpm > 0){
|
||||
var repEvent = {
|
||||
let repEvent = {
|
||||
bpm: bpm,
|
||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||
src: "bthrm"
|
||||
|
@ -159,7 +120,7 @@ exports.enable = () => {
|
|||
Bangle.emit("HRM_R", repEvent);
|
||||
}
|
||||
|
||||
var newEvent = {
|
||||
let newEvent = {
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
|
@ -177,6 +138,7 @@ exports.enable = () => {
|
|||
//Body sensor location
|
||||
handler: function(dv){
|
||||
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
|
||||
log("Got location", dv);
|
||||
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
|
||||
}
|
||||
},
|
||||
|
@ -184,26 +146,27 @@ exports.enable = () => {
|
|||
//Battery
|
||||
handler: function (dv){
|
||||
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||
log("Got battery", dv);
|
||||
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var device;
|
||||
var gatt;
|
||||
var characteristics = [];
|
||||
var blockInit = false;
|
||||
var currentRetryTimeout;
|
||||
var initialRetryTime = 40;
|
||||
var maxRetryTime = 60000;
|
||||
var retryTime = initialRetryTime;
|
||||
|
||||
var connectSettings = {
|
||||
minInterval: 7.5,
|
||||
maxInterval: 1500
|
||||
let lastReceivedData={
|
||||
};
|
||||
|
||||
var waitingPromise = function(timeout) {
|
||||
let bpmTimeout;
|
||||
|
||||
let device;
|
||||
let gatt;
|
||||
let characteristics = [];
|
||||
let blockInit = false;
|
||||
let currentRetryTimeout;
|
||||
let initialRetryTime = 40;
|
||||
let maxRetryTime = 60000;
|
||||
let retryTime = initialRetryTime;
|
||||
|
||||
let waitingPromise = function(timeout) {
|
||||
return new Promise(function(resolve){
|
||||
log("Start waiting for " + timeout);
|
||||
setTimeout(()=>{
|
||||
|
@ -240,7 +203,7 @@ exports.enable = () => {
|
|||
};
|
||||
}
|
||||
|
||||
var clearRetryTimeout = function(resetTime) {
|
||||
let clearRetryTimeout = function(resetTime) {
|
||||
if (currentRetryTimeout){
|
||||
log("Clearing timeout " + currentRetryTimeout);
|
||||
clearTimeout(currentRetryTimeout);
|
||||
|
@ -252,12 +215,12 @@ exports.enable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
var retry = function() {
|
||||
let retry = function() {
|
||||
log("Retry");
|
||||
|
||||
if (!currentRetryTimeout && !powerdownRequested){
|
||||
|
||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||
let clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||
|
||||
log("Set timeout for retry as " + clampedTime);
|
||||
clearRetryTimeout();
|
||||
|
@ -276,20 +239,21 @@ exports.enable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
var buzzing = false;
|
||||
var onDisconnect = function(reason) {
|
||||
let initialDisconnects = true;
|
||||
let buzzing = false;
|
||||
let onDisconnect = function(reason) {
|
||||
log("Disconnect: " + reason);
|
||||
log("GATT", gatt);
|
||||
log("Characteristics", characteristics);
|
||||
|
||||
var retryTimeResetNeeded = true;
|
||||
let retryTimeResetNeeded = true;
|
||||
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||
clearRetryTimeout(retryTimeResetNeeded);
|
||||
supportedCharacteristics["0x2a37"].active = false;
|
||||
if (!powerdownRequested) startFallback();
|
||||
blockInit = false;
|
||||
if (settings.warnDisconnect && !buzzing){
|
||||
if (settings.warnDisconnect && !buzzing && !initialDisconnects){
|
||||
buzzing = true;
|
||||
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
|
||||
}
|
||||
|
@ -298,9 +262,9 @@ exports.enable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
var createCharacteristicPromise = function(newCharacteristic) {
|
||||
let createCharacteristicPromise = function(newCharacteristic) {
|
||||
log("Create characteristic promise", newCharacteristic);
|
||||
var result = Promise.resolve();
|
||||
let result = Promise.resolve();
|
||||
// For values that can be read, go ahead and read them, even if we might be notified in the future
|
||||
// Allows for getting initial state of infrequently updating characteristics, like battery
|
||||
if (newCharacteristic.readValue){
|
||||
|
@ -316,58 +280,29 @@ exports.enable = () => {
|
|||
if (newCharacteristic.properties.notify){
|
||||
result = result.then(()=>{
|
||||
log("Starting notifications", newCharacteristic);
|
||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||
|
||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||
startPromise = startPromise.then(()=>{
|
||||
log("Wait after connect");
|
||||
return waitingPromise(settings.gracePeriodNotification);
|
||||
});
|
||||
|
||||
let startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||
|
||||
if (settings.gracePeriodNotification){
|
||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||
startPromise = startPromise.then(()=>{
|
||||
log("Wait after connect");
|
||||
return waitingPromise(settings.gracePeriodNotification);
|
||||
});
|
||||
}
|
||||
return startPromise;
|
||||
});
|
||||
}
|
||||
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
||||
};
|
||||
|
||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||
let attachCharacteristicPromise = function(promise, characteristic) {
|
||||
return promise.then(()=>{
|
||||
log("Handling characteristic:", characteristic);
|
||||
return createCharacteristicPromise(characteristic);
|
||||
});
|
||||
};
|
||||
|
||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||
log("Create characteristics promis ", newCharacteristics);
|
||||
var result = Promise.resolve();
|
||||
for (var c of newCharacteristics){
|
||||
if (!supportedCharacteristics[c.uuid]) continue;
|
||||
log("Supporting characteristic", c);
|
||||
characteristics.push(c);
|
||||
if (c.properties.notify){
|
||||
addNotificationHandler(c);
|
||||
}
|
||||
|
||||
result = attachCharacteristicPromise(result, c);
|
||||
}
|
||||
return result.then(()=>log("Handled characteristics"));
|
||||
};
|
||||
|
||||
var createServicePromise = function(service) {
|
||||
log("Create service promise", service);
|
||||
var result = Promise.resolve();
|
||||
result = result.then(()=>{
|
||||
log("Handling service" + service.uuid);
|
||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||
});
|
||||
return result.then(()=>log("Handled service" + service.uuid));
|
||||
};
|
||||
|
||||
var attachServicePromise = function(promise, service) {
|
||||
return promise.then(()=>createServicePromise(service));
|
||||
};
|
||||
|
||||
var initBt = function () {
|
||||
let initBt = function () {
|
||||
log("initBt with blockInit: " + blockInit);
|
||||
if (blockInit && !powerdownRequested){
|
||||
retry();
|
||||
|
@ -376,8 +311,8 @@ exports.enable = () => {
|
|||
|
||||
blockInit = true;
|
||||
|
||||
var promise;
|
||||
var filters;
|
||||
let promise;
|
||||
let filters;
|
||||
|
||||
if (!device){
|
||||
if (settings.btid){
|
||||
|
@ -394,9 +329,13 @@ exports.enable = () => {
|
|||
onDisconnect(e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (settings.gracePeriodRequest){
|
||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||
promise = promise.then((d)=>{
|
||||
log("Wait after request");
|
||||
return waitingPromise(settings.gracePeriodRequest).then(()=>Promise.resolve(d));
|
||||
});
|
||||
}
|
||||
|
||||
promise = promise.then((d)=>{
|
||||
|
@ -404,100 +343,42 @@ exports.enable = () => {
|
|||
d.on('gattserverdisconnected', onDisconnect);
|
||||
device = d;
|
||||
});
|
||||
|
||||
promise = promise.then(()=>{
|
||||
log("Wait after request");
|
||||
return waitingPromise(settings.gracePeriodRequest);
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
log("Reuse device", device);
|
||||
}
|
||||
|
||||
promise = promise.then(()=>{
|
||||
if (gatt){
|
||||
log("Reuse GATT", gatt);
|
||||
} else {
|
||||
log("GATT is new", gatt);
|
||||
characteristics = [];
|
||||
var cachedId = getCache().id;
|
||||
if (device.id !== cachedId){
|
||||
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
|
||||
clearCache();
|
||||
}
|
||||
var newCache = getCache();
|
||||
newCache.id = device.id;
|
||||
writeCache(newCache);
|
||||
gatt = device.gatt;
|
||||
}
|
||||
|
||||
gatt = device.gatt;
|
||||
return Promise.resolve(gatt);
|
||||
});
|
||||
|
||||
promise = promise.then((gatt)=>{
|
||||
if (!gatt.connected){
|
||||
log("Connecting...");
|
||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
||||
let connectPromise = gatt.connect().then(function() {
|
||||
log("Connected.");
|
||||
});
|
||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||
connectPromise = connectPromise.then(()=>{
|
||||
log("Wait after connect");
|
||||
return waitingPromise(settings.gracePeriodConnect);
|
||||
});
|
||||
if (settings.gracePeriodConnect){
|
||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||
connectPromise = connectPromise.then(()=>{
|
||||
log("Wait after connect");
|
||||
return waitingPromise(settings.gracePeriodConnect);
|
||||
});
|
||||
}
|
||||
return connectPromise;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
if (settings.bonding){
|
||||
promise = promise.then(() => {
|
||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||
if (gatt.getSecurityStatus()['bonded']) {
|
||||
log("Already bonded");
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
log("Start bonding");
|
||||
return gatt.startBonding()
|
||||
.then(() => log("Security status" + gatt.getSecurityStatus()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
promise = promise.then(()=>{
|
||||
if (!characteristics || characteristics.length === 0){
|
||||
if (!characteristics || characteristics.length == 0){
|
||||
characteristics = characteristicsFromCache(device);
|
||||
}
|
||||
});
|
||||
|
||||
promise = promise.then(()=>{
|
||||
var characteristicsPromise = Promise.resolve();
|
||||
if (characteristics.length === 0){
|
||||
characteristicsPromise = characteristicsPromise.then(()=>{
|
||||
log("Getting services");
|
||||
return gatt.getPrimaryServices();
|
||||
});
|
||||
|
||||
characteristicsPromise = characteristicsPromise.then((services)=>{
|
||||
log("Got services", services);
|
||||
var result = Promise.resolve();
|
||||
for (var service of services){
|
||||
if (!(supportedServices.includes(service.uuid))) continue;
|
||||
log("Supporting service", service.uuid);
|
||||
result = attachServicePromise(result, service);
|
||||
}
|
||||
log("Add " + settings.gracePeriodService + "ms grace period after services");
|
||||
result = result.then(()=>{
|
||||
log("Wait after services");
|
||||
return waitingPromise(settings.gracePeriodService);
|
||||
});
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
for (var characteristic of characteristics){
|
||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||
}
|
||||
let characteristicsPromise = Promise.resolve();
|
||||
for (let characteristic of characteristics){
|
||||
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||
}
|
||||
|
||||
return characteristicsPromise;
|
||||
|
@ -505,7 +386,7 @@ exports.enable = () => {
|
|||
|
||||
return promise.then(()=>{
|
||||
log("Connection established, waiting for notifications");
|
||||
characteristicsToCache(characteristics);
|
||||
initialDisconnects = false;
|
||||
clearRetryTimeout(true);
|
||||
}).catch((e) => {
|
||||
characteristics = [];
|
||||
|
@ -514,7 +395,7 @@ exports.enable = () => {
|
|||
});
|
||||
};
|
||||
|
||||
var powerdownRequested = false;
|
||||
let powerdownRequested = false;
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
|
@ -526,6 +407,7 @@ exports.enable = () => {
|
|||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
initialDisconnects = true;
|
||||
powerdownRequested = false;
|
||||
switchFallback();
|
||||
if (!Bangle.isBTHRMConnected()) initBt();
|
||||
|
@ -598,17 +480,18 @@ exports.enable = () => {
|
|||
Bangle.setBTHRMPower(0);
|
||||
if (!isOn) stopFallback();
|
||||
}
|
||||
return Bangle.isBTHRMOn() || Bangle.isHRMOn();
|
||||
}
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||
Bangle.origSetHRMPower(isOn, app);
|
||||
return Bangle.origSetHRMPower(isOn, app);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var fallbackActive = false;
|
||||
var inSwitch = false;
|
||||
let fallbackActive = false;
|
||||
let inSwitch = false;
|
||||
|
||||
var stopFallback = function(){
|
||||
let stopFallback = function(){
|
||||
if (fallbackActive){
|
||||
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
||||
fallbackActive = false;
|
||||
|
@ -616,7 +499,7 @@ exports.enable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
var startFallback = function(){
|
||||
let startFallback = function(){
|
||||
if (!fallbackActive && settings.allowFallback) {
|
||||
fallbackActive = true;
|
||||
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
||||
|
@ -624,7 +507,7 @@ exports.enable = () => {
|
|||
}
|
||||
};
|
||||
|
||||
var switchFallback = function() {
|
||||
let switchFallback = function() {
|
||||
log("Check falling back to HRM");
|
||||
if (!inSwitch){
|
||||
inSwitch = true;
|
||||
|
@ -640,8 +523,8 @@ exports.enable = () => {
|
|||
if (settings.replace){
|
||||
log("Replace HRM event");
|
||||
if (Bangle._PWR && Bangle._PWR.HRM){
|
||||
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||
var app = Bangle._PWR.HRM[i];
|
||||
for (let i = 0; i < Bangle._PWR.HRM.length; i++){
|
||||
let app = Bangle._PWR.HRM[i];
|
||||
log("Moving app " + app);
|
||||
Bangle.origSetHRMPower(0, app);
|
||||
Bangle.setBTHRMPower(1, app);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.18",
|
||||
"version": "0.19",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screen.png"}],
|
||||
|
|
|
@ -26,11 +26,11 @@
|
|||
},
|
||||
start : () => {
|
||||
Bangle.on('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(1,"recorder");
|
||||
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(function(back) {
|
||||
function writeSettings(key, value) {
|
||||
var s = require('Storage').readJSON(FILE, true) || {};
|
||||
let s = require('Storage').readJSON(FILE, true) || {};
|
||||
s[key] = value;
|
||||
require('Storage').writeJSON(FILE, s);
|
||||
readSettings();
|
||||
|
@ -13,10 +13,14 @@
|
|||
);
|
||||
}
|
||||
|
||||
var FILE="bthrm.json";
|
||||
var settings;
|
||||
let FILE="bthrm.json";
|
||||
let settings;
|
||||
readSettings();
|
||||
|
||||
let log = ()=>{};
|
||||
if (settings.debuglog)
|
||||
log = print;
|
||||
|
||||
function applyCustomSettings(){
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",settings.custom_replace);
|
||||
|
@ -26,7 +30,7 @@
|
|||
}
|
||||
|
||||
function buildMainMenu(){
|
||||
var mainmenu = {
|
||||
let mainmenu = {
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
'< Back': back,
|
||||
'Mode': {
|
||||
|
@ -63,12 +67,13 @@
|
|||
};
|
||||
|
||||
if (settings.btname || settings.btid){
|
||||
var name = "Clear " + (settings.btname || settings.btid);
|
||||
let name = "Clear " + (settings.btname || settings.btid);
|
||||
mainmenu[name] = function() {
|
||||
E.showPrompt("Clear current device?").then((r)=>{
|
||||
if (r) {
|
||||
writeSettings("btname",undefined);
|
||||
writeSettings("btid",undefined);
|
||||
writeSettings("cache", undefined);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
|
@ -81,7 +86,7 @@
|
|||
return mainmenu;
|
||||
}
|
||||
|
||||
var submenu_debug = {
|
||||
let submenu_debug = {
|
||||
'' : { title: "Debug"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Alert on disconnect': {
|
||||
|
@ -111,11 +116,135 @@
|
|||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||
};
|
||||
|
||||
let supportedServices = [
|
||||
"0x180d", // Heart Rate
|
||||
"0x180f", // Battery
|
||||
];
|
||||
|
||||
let supportedCharacteristics = [
|
||||
"0x2a37", // Heart Rate
|
||||
"0x2a38", // Body sensor location
|
||||
"0x2a19", // Battery
|
||||
];
|
||||
|
||||
var characteristicsToCache = function(characteristics) {
|
||||
log("Cache characteristics");
|
||||
let cache = {};
|
||||
if (!cache.characteristics) cache.characteristics = {};
|
||||
for (var c of characteristics){
|
||||
//"handle_value":16,"handle_decl":15
|
||||
log("Saving handle " + c.handle_value + " for characteristic: ", c.uuid);
|
||||
cache.characteristics[c.uuid] = {
|
||||
"handle": c.handle_value,
|
||||
"uuid": c.uuid,
|
||||
"notify": c.properties.notify,
|
||||
"read": c.properties.read
|
||||
};
|
||||
}
|
||||
writeSettings("cache", cache);
|
||||
};
|
||||
|
||||
let createCharacteristicPromise = function(newCharacteristic) {
|
||||
log("Create characteristic promise", newCharacteristic.uuid);
|
||||
return Promise.resolve().then(()=>log("Handled characteristic", newCharacteristic.uuid));
|
||||
};
|
||||
|
||||
let attachCharacteristicPromise = function(promise, characteristic) {
|
||||
return promise.then(()=>{
|
||||
log("Handling characteristic:", characteristic.uuid);
|
||||
return createCharacteristicPromise(characteristic);
|
||||
});
|
||||
};
|
||||
|
||||
let characteristics;
|
||||
|
||||
let createCharacteristicsPromise = function(newCharacteristics) {
|
||||
log("Create characteristics promise ", newCharacteristics.length);
|
||||
let result = Promise.resolve();
|
||||
for (let c of newCharacteristics){
|
||||
if (!supportedCharacteristics.includes(c.uuid)) continue;
|
||||
log("Supporting characteristic", c.uuid);
|
||||
characteristics.push(c);
|
||||
|
||||
result = attachCharacteristicPromise(result, c);
|
||||
}
|
||||
return result.then(()=>log("Handled characteristics"));
|
||||
};
|
||||
|
||||
let createServicePromise = function(service) {
|
||||
log("Create service promise", service.uuid);
|
||||
let result = Promise.resolve();
|
||||
result = result.then(()=>{
|
||||
log("Handling service", service.uuid);
|
||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||
});
|
||||
return result.then(()=>log("Handled service", service.uuid));
|
||||
};
|
||||
|
||||
let attachServicePromise = function(promise, service) {
|
||||
return promise.then(()=>createServicePromise(service));
|
||||
};
|
||||
|
||||
function cacheDevice(deviceId){
|
||||
let promise;
|
||||
let filters;
|
||||
let gatt;
|
||||
characteristics = [];
|
||||
filters = [{ id: deviceId }];
|
||||
|
||||
log("Requesting device with filters", filters);
|
||||
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
||||
|
||||
promise = promise.then((d)=>{
|
||||
log("Got device", d);
|
||||
gatt = d.gatt;
|
||||
log("Connecting...");
|
||||
return gatt.connect().then(function() {
|
||||
log("Connected.");
|
||||
});
|
||||
});
|
||||
|
||||
if (settings.bonding){
|
||||
promise = promise.then(() => {
|
||||
log(JSON.stringify(gatt.getSecurityStatus()));
|
||||
if (gatt.getSecurityStatus().bonded) {
|
||||
log("Already bonded");
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
log("Start bonding");
|
||||
return gatt.startBonding()
|
||||
.then(() => log("Security status after bonding" + gatt.getSecurityStatus()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
promise = promise.then(()=>{
|
||||
log("Getting services");
|
||||
return gatt.getPrimaryServices();
|
||||
});
|
||||
|
||||
promise = promise.then((services)=>{
|
||||
log("Got services", services.length);
|
||||
let result = Promise.resolve();
|
||||
for (let service of services){
|
||||
if (!(supportedServices.includes(service.uuid))) continue;
|
||||
log("Supporting service", service.uuid);
|
||||
result = attachServicePromise(result, service);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
return promise.then(()=>{
|
||||
log("Connection established, saving cache");
|
||||
characteristicsToCache(characteristics);
|
||||
});
|
||||
}
|
||||
|
||||
function createMenuFromScan(){
|
||||
E.showMenu();
|
||||
E.showMessage("Scanning for 4 seconds");
|
||||
|
||||
var submenu_scan = {
|
||||
let submenu_scan = {
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); }
|
||||
};
|
||||
NRF.findDevices(function(devices) {
|
||||
|
@ -126,18 +255,41 @@
|
|||
return;
|
||||
} else {
|
||||
devices.forEach((d) => {
|
||||
print("Found device", d);
|
||||
var shown = (d.name || d.id.substr(0, 17));
|
||||
log("Found device", d);
|
||||
let shown = (d.name || d.id.substr(0, 17));
|
||||
submenu_scan[shown] = function () {
|
||||
E.showPrompt("Set " + shown + "?").then((r) => {
|
||||
E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
|
||||
if (r) {
|
||||
writeSettings("btid", d.id);
|
||||
// Store the name for displaying later. Will connect by ID
|
||||
if (d.name) {
|
||||
writeSettings("btname", d.name);
|
||||
}
|
||||
E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
|
||||
let count = 0;
|
||||
const successHandler = ()=>{
|
||||
E.showPrompt("Success!", {
|
||||
img:require("Storage").read("bthrm.img"),
|
||||
buttons: { "OK":true }
|
||||
}).then(()=>{
|
||||
writeSettings("btid", d.id);
|
||||
// Store the name for displaying later. Will connect by ID
|
||||
if (d.name) {
|
||||
writeSettings("btname", d.name);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
};
|
||||
const errorHandler = (e)=>{
|
||||
count++;
|
||||
log("ERROR", e);
|
||||
if (count <= 10){
|
||||
E.showMessage("Error during caching\nRetry " + count + "/10", e);
|
||||
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
||||
} else {
|
||||
E.showAlert("Error during caching", e).then(()=>{
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
};
|
||||
});
|
||||
|
@ -146,7 +298,7 @@
|
|||
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
|
||||
}
|
||||
|
||||
var submenu_custom = {
|
||||
let submenu_custom = {
|
||||
'' : { title: "Custom mode"},
|
||||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Replace HRM': {
|
||||
|
@ -183,7 +335,7 @@
|
|||
},
|
||||
};
|
||||
|
||||
var submenu_grace = {
|
||||
let submenu_grace = {
|
||||
'' : { title: "Grace periods"},
|
||||
'< Back': function() { E.showMenu(submenu_debug); },
|
||||
'Request': {
|
||||
|
@ -215,16 +367,6 @@
|
|||
onchange: v => {
|
||||
writeSettings("gracePeriodNotification",v);
|
||||
}
|
||||
},
|
||||
'Service': {
|
||||
value: settings.gracePeriodService,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
format: v=>v+"ms",
|
||||
onchange: v => {
|
||||
writeSettings("gracePeriodService",v);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,53 @@
|
|||
# Chrono Logger
|
||||
|
||||
Record times active on a task, course, work or anything really.
|
||||
|
||||
**Disclaimer:** No one is responsible for any loss of data you recorded with this app. If you run into problems please report as advised under **Requests** below.
|
||||
|
||||
With time on your side and a little help from your friends - you'll surely triumph over Lavos in the end!
|
||||
|
||||
      
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Click the large green button to log the start of your activity. Click the now red button again to log that you stopped.
|
||||
|
||||
## Features
|
||||
|
||||
- Saves to file on every toggling of the active state.
|
||||
- csv file contents looks like:
|
||||
```
|
||||
1,Start,2024-03-02T15:18:09 GMT+0200
|
||||
2,Note,Critical hit!
|
||||
3,Stop,2024-03-02T15:19:17 GMT+0200
|
||||
```
|
||||
- Add annotations to the log.
|
||||
- Create and switch between multiple logs.
|
||||
- Sync log files to an Android device through Gadgetbridge (Needs pending code changes to Gadgetbridge).
|
||||
- App state is restored when you start the app again.
|
||||
|
||||
## Controls
|
||||
|
||||
- Large button to toggle active state.
|
||||
- Menu icon to access additional functionality.
|
||||
- Hardware button exits menus, closes the app on the main screen.
|
||||
|
||||
## TODO and notes
|
||||
|
||||
- Delete individual tasks/logs through the app?
|
||||
- Reset everything through the app?
|
||||
- Scan for chronlog storage files that somehow no longer have tasks associated with it?
|
||||
- Complete the Gadgetbridge side of things for sync.
|
||||
- Sync to iOS?
|
||||
- Inspect log files through the app, similarly to Recorder app?
|
||||
- Changes to Android file system permissions makes it not always trivial to access the synced files.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions.
|
||||
|
||||
## Creator
|
||||
|
||||
[thyttan](https://github.com/thyttan)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///gElq3X0ELJf4AiitAAYMBqgKEgNVrgEBmtVCAQABgtVr/Agf1qtQEQlpq6QB6tpEgkVywLDywLEq2uyoLB6wEBBZAECBYda32lBYIECBZ9W3wjDAgILPquWqoACAgILEtILDAgKOEAAyQCRwIAGSAUVBY6ECBZYGD7WnAoYLF9WrBYupAoWq1QECtQLBtWdBYt21QLC1LfBBYVfA4ILBlWq1f9rWVv/q1WoBYMKCgOvTYP6AoOgBYMCAoIAFwCQCBY6nDGAIAEFwQkIEQZVCBQZRCAAcGBYeQBYoYDCwwYECw5KC0gKIAH4APA="))
|
|
@ -0,0 +1,376 @@
|
|||
// TODO:
|
||||
// - Add more /*LANG*/ tags for translations.
|
||||
// - Check if there are chronlog storage files that should be added to tasks.
|
||||
|
||||
{
|
||||
const storage = require("Storage");
|
||||
let appData = storage.readJSON("chronlog.json", true) || {
|
||||
currentTask : "default",
|
||||
tasks : {
|
||||
default: {
|
||||
file : "chronlog_default.csv", // Existing default task log file
|
||||
state : "stopped",
|
||||
lineNumber : 0,
|
||||
lastLine : "",
|
||||
lastSyncedLine : "",
|
||||
},
|
||||
// Add more tasks as needed
|
||||
},
|
||||
};
|
||||
let currentTask = appData.currentTask;
|
||||
let tasks = appData.tasks;
|
||||
delete appData;
|
||||
|
||||
let themeColors = g.theme;
|
||||
|
||||
let logEntry; // Avoid previous lint warning
|
||||
|
||||
// Function to draw the Start/Stop button with play and pause icons
|
||||
let drawButton = ()=>{
|
||||
var btnWidth = g.getWidth() - 40;
|
||||
var btnHeight = 50;
|
||||
var btnX = 20;
|
||||
var btnY = (g.getHeight() - btnHeight) / 2;
|
||||
var cornerRadius = 25;
|
||||
|
||||
var isStopped = tasks[currentTask].state === "stopped";
|
||||
g.setColor(isStopped ? "#0F0" : "#F00"); // Set color to green when stopped and red when started
|
||||
|
||||
// Draw rounded corners of the button
|
||||
g.fillCircle(btnX + cornerRadius, btnY + cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + btnWidth - cornerRadius, btnY + cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + btnWidth - cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius);
|
||||
|
||||
// Draw rectangles to fill in the button
|
||||
g.fillRect(btnX + cornerRadius, btnY, btnX + btnWidth - cornerRadius, btnY + btnHeight);
|
||||
g.fillRect(btnX, btnY + cornerRadius, btnX + btnWidth, btnY + btnHeight - cornerRadius);
|
||||
|
||||
g.setColor(themeColors.bg); // Set icon color to contrast against the button's color
|
||||
|
||||
// Center the icon within the button
|
||||
var iconX = btnX + btnWidth / 2;
|
||||
var iconY = btnY + btnHeight / 2;
|
||||
|
||||
if (isStopped) {
|
||||
// Draw play icon
|
||||
var playSize = 10; // Side length of the play triangle
|
||||
var offset = playSize / Math.sqrt(3) - 3;
|
||||
g.fillPoly([
|
||||
iconX - playSize, iconY - playSize + offset,
|
||||
iconX - playSize, iconY + playSize + offset,
|
||||
iconX + playSize * 2 / Math.sqrt(3), iconY + offset
|
||||
]);
|
||||
} else {
|
||||
// Draw pause icon
|
||||
var barWidth = 5; // Width of pause bars
|
||||
var barHeight = btnHeight / 2; // Height of pause bars
|
||||
var barSpacing = 5; // Spacing between pause bars
|
||||
g.fillRect(iconX - barSpacing / 2 - barWidth, iconY - barHeight / 2, iconX - barSpacing / 2, iconY + barHeight / 2);
|
||||
g.fillRect(iconX + barSpacing / 2, iconY - barHeight / 2, iconX + barSpacing / 2 + barWidth, iconY + barHeight / 2);
|
||||
}
|
||||
};
|
||||
|
||||
let drawHamburgerMenu = ()=>{
|
||||
var x = g.getWidth() / 2; // Center the hamburger menu horizontally
|
||||
var y = (7/8)*g.getHeight(); // Position it near the bottom
|
||||
var lineLength = 18; // Length of the hamburger lines
|
||||
var spacing = 6; // Space between the lines
|
||||
|
||||
g.setColor(themeColors.fg); // Set color to foreground color for the icon
|
||||
// Draw three horizontal lines
|
||||
for (var i = -1; i <= 1; i++) {
|
||||
g.fillRect(x - lineLength/2, y + i * spacing - 1, x + lineLength/2, y + i * spacing + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to draw the task name centered between the widget field and the start/stop button
|
||||
let drawTaskName = ()=>{
|
||||
g.setFont("Vector", 20); // Set a smaller font for the task name display
|
||||
|
||||
// Calculate position to center the task name horizontally
|
||||
var x = (g.getWidth()) / 2;
|
||||
|
||||
// Calculate position to center the task name vertically between the widget field and the start/stop button
|
||||
var y = g.getHeight()/4; // Center vertically
|
||||
|
||||
g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color
|
||||
g.drawString(currentTask, x, y); // Draw the task name centered on the screen
|
||||
};
|
||||
|
||||
// Function to draw the last log entry of the current task
|
||||
let drawLastLogEntry = ()=>{
|
||||
g.setFont("Vector", 10); // Set a smaller font for the task name display
|
||||
|
||||
// Calculate position to center the log entry horizontally
|
||||
var x = (g.getWidth()) / 2;
|
||||
|
||||
// Calculate position to place the log entry properly between the start/stop button and hamburger menu
|
||||
var btnBottomY = (g.getHeight() + 50) / 2; // Y-coordinate of the bottom of the start/stop button
|
||||
var menuBtnYTop = g.getHeight() * (5 / 6); // Y-coordinate of the top of the hamburger menu button
|
||||
var y = btnBottomY + (menuBtnYTop - btnBottomY) / 2 + 2; // Center vertically between button and menu
|
||||
|
||||
g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color
|
||||
g.drawString(g.wrapString(tasks[currentTask].lastLine, 150).join("\n"), x, y);
|
||||
};
|
||||
|
||||
/*
|
||||
// Helper function to read the last log entry from the current task's log file
|
||||
let updateLastLogEntry = ()=>{
|
||||
var filename = tasks[currentTask].file;
|
||||
var file = require("Storage").open(filename, "r");
|
||||
var lastLine = "";
|
||||
var line;
|
||||
while ((line = file.readLine()) !== undefined) {
|
||||
lastLine = line; // Keep reading until the last line
|
||||
}
|
||||
tasks[currentTask].lastLine = lastLine;
|
||||
};
|
||||
*/
|
||||
|
||||
// Main UI drawing function
|
||||
let drawMainMenu = ()=>{
|
||||
g.clear();
|
||||
Bangle.drawWidgets(); // Draw any active widgets
|
||||
g.setColor(themeColors.bg); // Set color to theme's background color
|
||||
g.fillRect(Bangle.appRect); // Fill the app area with the background color
|
||||
|
||||
drawTaskName(); // Draw the centered task name
|
||||
drawLastLogEntry(); // Draw the last log entry of the current task
|
||||
drawButton(); // Draw the Start/Stop toggle button
|
||||
drawHamburgerMenu(); // Draw the hamburger menu button icon
|
||||
|
||||
//g.flip(); // Send graphics to the display
|
||||
};
|
||||
|
||||
// Function to toggle the active state
|
||||
let toggleChronlog = ()=>{
|
||||
var dateObj = new Date();
|
||||
var dateObjStrSplit = dateObj.toString().split(" ");
|
||||
var currentTime = dateObj.getFullYear().toString() + "-" + (dateObj.getMonth()<10?"0":"") + dateObj.getMonth().toString() + "-" + (dateObj.getDate()<10?"0":"") + dateObj.getDate().toString() + "T" + (dateObj.getHours()<10?"0":"") + dateObj.getHours().toString() + ":" + (dateObj.getMinutes()<10?"0":"") + dateObj.getMinutes().toString() + ":" + (dateObj.getSeconds()<10?"0":"") + dateObj.getSeconds().toString() + " " + dateObjStrSplit[dateObjStrSplit.length-1];
|
||||
|
||||
tasks[currentTask].lineNumber = Number(tasks[currentTask].lineNumber) + 1;
|
||||
logEntry = tasks[currentTask].lineNumber + (tasks[currentTask].state === "stopped" ? ",Start," : ",Stop,") + currentTime + "\n";
|
||||
var filename = tasks[currentTask].file;
|
||||
|
||||
// Open the appropriate file and append the log entry
|
||||
var file = require("Storage").open(filename, "a");
|
||||
file.write(logEntry);
|
||||
tasks[currentTask].lastLine = logEntry;
|
||||
|
||||
// Toggle the state and update the button text
|
||||
tasks[currentTask].state = tasks[currentTask].state === "stopped" ? "started" : "stopped";
|
||||
drawMainMenu(); // Redraw the main UI
|
||||
};
|
||||
|
||||
// Define the touch handler function for the main menu
|
||||
let handleMainMenuTouch = (button, xy)=>{
|
||||
var btnTopY = (g.getHeight() - 50) / 2;
|
||||
var btnBottomY = btnTopY + 50;
|
||||
var menuBtnYTop = (7/8)*g.getHeight() - 15;
|
||||
var menuBtnYBottom = (7/8)*g.getHeight() + 15;
|
||||
var menuBtnXLeft = (g.getWidth() / 2) - 15;
|
||||
var menuBtnXRight = (g.getWidth() / 2) + 15;
|
||||
|
||||
// Detect if the touch is within the toggle button area
|
||||
if (xy.x >= 20 && xy.x <= (g.getWidth() - 20) && xy.y > btnTopY && xy.y < btnBottomY) {
|
||||
toggleChronlog();
|
||||
}
|
||||
// Detect if the touch is within the hamburger menu button area
|
||||
else if (xy.x >= menuBtnXLeft && xy.x <= menuBtnXRight && xy.y >= menuBtnYTop && xy.y <= menuBtnYBottom) {
|
||||
showMenu();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to attach the touch event listener
|
||||
let setMainUI = ()=>{
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
back: load,
|
||||
touch: handleMainMenuTouch
|
||||
});
|
||||
};
|
||||
|
||||
let saveAppState = ()=>{
|
||||
let appData = {
|
||||
currentTask : currentTask,
|
||||
tasks : tasks,
|
||||
};
|
||||
require("Storage").writeJSON("chronlog.json", appData);
|
||||
};
|
||||
// Set up a listener for the 'kill' event
|
||||
E.on('kill', saveAppState);
|
||||
|
||||
// Function to switch to a selected task
|
||||
let switchTask = (taskName)=>{
|
||||
currentTask = taskName; // Update the current task
|
||||
|
||||
// Reinitialize the UI elements
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI to reflect the task change and the button state
|
||||
};
|
||||
|
||||
// Function to create a new task
|
||||
let createNewTask = ()=>{
|
||||
// Prompt the user to input the task's name
|
||||
require("textinput").input({
|
||||
text: "" // Default empty text for new task
|
||||
}).then(result => {
|
||||
var taskName = result; // Store the result from text input
|
||||
if (taskName) {
|
||||
if (tasks.hasOwnProperty(taskName)) {
|
||||
// Task already exists, handle this case as needed
|
||||
E.showAlert(/*LANG*/"Task already exists", "Error").then(drawMainMenu);
|
||||
} else {
|
||||
// Create a new task log file for the new task
|
||||
var filename = "chronlog_" + taskName.replace(/\W+/g, "_") + ".csv";
|
||||
tasks[taskName] = {
|
||||
file : filename,
|
||||
state : "stopped",
|
||||
lineNumber : 0,
|
||||
lastLine : "",
|
||||
lastSyncedLine : "",
|
||||
};
|
||||
|
||||
currentTask = taskName;
|
||||
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI with the new task
|
||||
}
|
||||
} else {
|
||||
setMainUI();
|
||||
drawMainMenu(); // User cancelled, redraw main menu
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log("Text input error", e);
|
||||
setMainUI();
|
||||
drawMainMenu(); // In case of error also redraw main menu
|
||||
});
|
||||
};
|
||||
|
||||
// Function to display the list of tasks for selection
|
||||
let chooseTask = ()=>{
|
||||
// Construct the tasks menu from the tasks object
|
||||
var taskMenu = {
|
||||
"": { "title": /*LANG*/"Choose Task",
|
||||
"back" : function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Cancel task selection
|
||||
}
|
||||
}
|
||||
};
|
||||
for (var taskName in tasks) {
|
||||
if (!tasks.hasOwnProperty(taskName)) continue;
|
||||
taskMenu[taskName] = (function(name) {
|
||||
return function() {
|
||||
switchTask(name);
|
||||
};
|
||||
})(taskName);
|
||||
}
|
||||
|
||||
// Add a menu option for creating a new task
|
||||
taskMenu[/*LANG*/"Create New Task"] = createNewTask;
|
||||
|
||||
E.showMenu(taskMenu); // Display the task selection
|
||||
};
|
||||
|
||||
// Function to annotate the current or last work session
|
||||
let annotateTask = ()=>{
|
||||
|
||||
// Prompt the user to input the annotation text
|
||||
require("textinput").input({
|
||||
text: "" // Default empty text for annotation
|
||||
}).then(result => {
|
||||
var annotationText = result.trim();
|
||||
if (annotationText) {
|
||||
// Append annotation to the last or current log entry
|
||||
tasks[currentTask].lineNumber ++;
|
||||
var annotatedEntry = tasks[currentTask].lineNumber + /*LANG*/",Note," + annotationText + "\n";
|
||||
var filename = tasks[currentTask].file;
|
||||
var file = require("Storage").open(filename, "a");
|
||||
file.write(annotatedEntry);
|
||||
tasks[currentTask].lastLine = annotatedEntry;
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI after adding the annotation
|
||||
} else {
|
||||
// User cancelled, so we do nothing and just redraw the main menu
|
||||
setMainUI();
|
||||
drawMainMenu();
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log("Annotation input error", e);
|
||||
setMainUI();
|
||||
drawMainMenu(); // In case of error also redraw main menu
|
||||
});
|
||||
};
|
||||
|
||||
let syncToAndroid = (taskName, isFullSync)=>{
|
||||
let mode = "a";
|
||||
if (isFullSync) mode = "w";
|
||||
let lastSyncedLine = tasks[taskName].lastSyncedLine || 0;
|
||||
let taskNameValidFileName = taskName.replace(" ","_"); // FIXME: Should use something similar to replaceAll using a regular expression to catch all illegal characters.
|
||||
|
||||
let storageFile = require("Storage").open("chronlog_"+taskNameValidFileName+".csv", "r");
|
||||
let contents = storageFile.readLine();
|
||||
let lineNumber = contents ? contents.slice(0, contents.indexOf(",")) : 0;
|
||||
let shouldSyncLine = ()=>{return (contents && (isFullSync || (Number(lineNumber)>Number(lastSyncedLine))));};
|
||||
let doSyncLine = (mde)=>{Bluetooth.println(JSON.stringify({t:"file", n:"chronlog_"+taskNameValidFileName+".csv", c:contents, m:mde}));};
|
||||
|
||||
if (shouldSyncLine()) doSyncLine(mode);
|
||||
contents = storageFile.readLine();
|
||||
while (contents) {
|
||||
lineNumber = contents.slice(0, contents.indexOf(",")); // Could theoretically do with `lineNumber++`, but this is more robust in case numbering in file ended up irregular.
|
||||
if (shouldSyncLine()) doSyncLine("a");
|
||||
contents = storageFile.readLine();
|
||||
}
|
||||
tasks[taskName].lastSyncedLine = lineNumber;
|
||||
};
|
||||
|
||||
// Function to display the list of tasks for selection
|
||||
let syncTasks = ()=>{
|
||||
let isToDoFullSync = false;
|
||||
// Construct the tasks menu from the tasks object
|
||||
var syncMenu = {
|
||||
"": { "title": /*LANG*/"Sync Tasks",
|
||||
"back" : function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Cancel task selection
|
||||
}
|
||||
}
|
||||
};
|
||||
syncMenu[/*LANG*/"Full Resyncs"] = {
|
||||
value: !!isToDoFullSync, // !! converts undefined to false
|
||||
onchange: ()=>{
|
||||
isToDoFullSync = !isToDoFullSync
|
||||
},
|
||||
}
|
||||
for (var taskName in tasks) {
|
||||
if (!tasks.hasOwnProperty(taskName)) continue;
|
||||
syncMenu[taskName] = (function(name) {
|
||||
return function() {syncToAndroid(name,isToDoFullSync);};
|
||||
})(taskName);
|
||||
}
|
||||
|
||||
E.showMenu(syncMenu); // Display the task selection
|
||||
};
|
||||
|
||||
let showMenu = ()=>{
|
||||
var menu = {
|
||||
"": { "title": /*LANG*/"Menu",
|
||||
"back": function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Redraw the main UI when closing the menu
|
||||
},
|
||||
},
|
||||
/*LANG*/"Annotate": annotateTask, // Now calls the real annotation function
|
||||
/*LANG*/"Change Task": chooseTask, // Opens the task selection screen
|
||||
/*LANG*/"Sync to Android": syncTasks,
|
||||
};
|
||||
E.showMenu(menu);
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
drawMainMenu(); // Draw the main UI when the app starts
|
||||
// When the application starts, attach the touch event listener
|
||||
setMainUI();
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "chronlog",
|
||||
"name": "Chrono Logger",
|
||||
"version":"0.01",
|
||||
"description": "Record time active on a task, course, work or anything really.",
|
||||
"icon": "app.png",
|
||||
"tags": "logging, record, work, tasks",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots" : [ { "url":"dump.png"}, { "url":"dump1.png" }, { "url":"dump2.png" }, { "url":"dump3.png" }, { "url":"dump4.png" }, { "url":"dump5.png" }, { "url":"dump6.png" } ],
|
||||
"storage": [
|
||||
{"name":"chronlog.app.js","url":"app.js"},
|
||||
{"name":"chronlog.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -5,4 +5,6 @@
|
|||
0.05: Improved colors (connected vs disconnected)
|
||||
0.06: Tell clock widgets to hide.
|
||||
0.07: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
|
||||
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
|
||||
0.09: You can now back out of the calendar using the button
|
||||
0.10: Fix linter warnings
|
||||
|
|
|
@ -7,23 +7,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
|
|||
|:--:|:-|
|
||||
||locked: triggers only one minimal update/min|
|
||||
||unlocked: smaller clock, but with seconds|
|
||||
||swipe up for big calendar, (up down to scroll, left/right to exit)|
|
||||
||swipe up for big calendar<br>⬆️/⬇️ to scroll<br> ⬅️/➡️ to exit|
|
||||
|
||||
## Configurable Features
|
||||
- Number of calendar rows (weeks)
|
||||
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
|
||||
- Buzz on connect/disconnect (feel free to disable and use a widget)
|
||||
- Clock Mode (24h/12h). (No am/pm indicator)
|
||||
- First day of the week
|
||||
- Red Saturday/Sunday
|
||||
- Swipe/Drag gestures to launch features or apps.
|
||||
|
||||
## Auto detects your message/music apps:
|
||||
- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable)
|
||||
- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable)
|
||||
## Integrated swipe launcher: (Configure in Settings)
|
||||
- ⬇️ (down) will search your files for an app with the string "**message**"
|
||||
- ➡️ (right) will search your files for an app with the string "**music**"
|
||||
- ⬅️ (left) will search your files for an app with the string "**agenda**"
|
||||
- ⬆️ (up) will show the **internal full calendar**
|
||||
|
||||
## Feedback
|
||||
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
|
||||
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues
|
||||
If something isn't working, please tell me: https://github.com/Stuff-etc/BangleApps/issues (I moved my github repo)
|
||||
|
||||
## Planned features:
|
||||
- Internal lightweight music control, because switching apps has a loading time.
|
||||
|
|
|
@ -24,15 +24,25 @@ const DEBUG = false;
|
|||
var state = "watch";
|
||||
var monthOffset = 0;
|
||||
|
||||
// FIXME: These variables should maybe be defined inside relevant functions below. The linter complained they were not defined (i.e. they were added to global scope if I understand correctly).
|
||||
let dayInterval;
|
||||
let secondInterval;
|
||||
let minuteInterval;
|
||||
let newmonth;
|
||||
let bottomrightY;
|
||||
let bottomrightX;
|
||||
let rMonth;
|
||||
let dimSeconds;
|
||||
|
||||
/*
|
||||
* Calendar features
|
||||
*/
|
||||
function drawFullCalendar(monthOffset) {
|
||||
addMonths = function (_d, _am) {
|
||||
var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
|
||||
const addMonths = function (_d, _am) {
|
||||
let ay = 0, m = _d.getMonth(), y = _d.getFullYear();
|
||||
while ((m + _am) > 11) { ay++; _am -= 12; }
|
||||
while ((m + _am) < 0) { ay--; _am += 12; }
|
||||
n = new Date(_d.getTime());
|
||||
let n = new Date(_d.getTime());
|
||||
n.setMonth(m + _am);
|
||||
n.setFullYear(y + ay);
|
||||
return n;
|
||||
|
@ -45,10 +55,10 @@ function drawFullCalendar(monthOffset) {
|
|||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
|
||||
d = addMonths(Date(), monthOffset);
|
||||
tdy = Date().getDate() + "." + Date().getMonth();
|
||||
var d = addMonths(Date(), monthOffset);
|
||||
let tdy = Date().getDate() + "." + Date().getMonth();
|
||||
newmonth = false;
|
||||
c_y = 0;
|
||||
let c_y = 0;
|
||||
g.reset();
|
||||
g.setBgColor(0);
|
||||
g.clear();
|
||||
|
@ -60,8 +70,8 @@ function drawFullCalendar(monthOffset) {
|
|||
rD.setDate(rD.getDate() - dow);
|
||||
var rDate = rD.getDate();
|
||||
bottomrightY = c_y - 3;
|
||||
clrsun = s.REDSUN ? '#f00' : '#fff';
|
||||
clrsat = s.REDSUN ? '#f00' : '#fff';
|
||||
let clrsun = s.REDSUN ? '#f00' : '#fff';
|
||||
let clrsat = s.REDSUN ? '#f00' : '#fff';
|
||||
var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat];
|
||||
for (var y = 1; y <= 11; y++) {
|
||||
bottomrightY += CELL_H;
|
||||
|
@ -90,7 +100,7 @@ function caldrawMonth(rDate, c, m, rD) {
|
|||
g.setColor(c);
|
||||
g.setFont("Vector", 18);
|
||||
g.setFontAlign(-1, 1, 1);
|
||||
drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
|
||||
let drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
|
||||
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
|
||||
newmonth = false;
|
||||
}
|
||||
|
@ -124,7 +134,7 @@ function drawMinutes() {
|
|||
var d = new Date();
|
||||
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
|
||||
var minutes = d.getMinutes().toString().padStart(2, '0');
|
||||
var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff';
|
||||
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
|
||||
var size = 50;
|
||||
var clock_x = (w - 20) / 2;
|
||||
if (dimSeconds) {
|
||||
|
@ -156,7 +166,7 @@ function drawSeconds() {
|
|||
}
|
||||
|
||||
function drawWatch() {
|
||||
if (DEBUG) console.log("CALENDAR");
|
||||
if (DEBUG) console.log("DRAWWATCH");
|
||||
monthOffset = 0;
|
||||
state = "watch";
|
||||
var d = new Date();
|
||||
|
@ -197,6 +207,7 @@ function drawWatch() {
|
|||
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
|
||||
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
|
||||
dayInterval = setTimeout(drawWatch, nextday * 1000);
|
||||
if (DEBUG) console.log("ended DRAWWATCH. next refresh in " + nextday + "s");
|
||||
}
|
||||
|
||||
function BTevent() {
|
||||
|
@ -211,8 +222,12 @@ function action(a) {
|
|||
g.reset();
|
||||
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
|
||||
if (DEBUG) console.log("action:" + a);
|
||||
state = "unknown";
|
||||
console.log("state -> unknown");
|
||||
let l;
|
||||
switch (a) {
|
||||
case "[ignore]":
|
||||
drawWatch();
|
||||
break;
|
||||
case "[calend.]":
|
||||
drawFullCalendar();
|
||||
|
@ -229,6 +244,12 @@ function action(a) {
|
|||
load(l[0]);
|
||||
} else E.showAlert("Message app not found", "Not found").then(drawWatch);
|
||||
break;
|
||||
case "[AI:agenda]":
|
||||
l = require("Storage").list(RegExp("agenda.*app.js"));
|
||||
if (l.length > 0) {
|
||||
load(l[0]);
|
||||
} else E.showAlert("Agenda app not found", "Not found").then(drawWatch);
|
||||
break;
|
||||
default:
|
||||
l = require("Storage").list(RegExp(a + ".app.js"));
|
||||
if (l.length > 0) {
|
||||
|
@ -276,7 +297,6 @@ function input(dir) {
|
|||
drawWatch();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,3 +329,10 @@ NRF.on('disconnect', BTevent);
|
|||
dimSeconds = Bangle.isLocked();
|
||||
drawWatch();
|
||||
|
||||
setWatch(function() {
|
||||
if (state == "watch") {
|
||||
Bangle.showLauncher()
|
||||
} else if (state == "calendar") {
|
||||
drawWatch();
|
||||
}
|
||||
}, BTN1, {repeat:true, edge:"falling"});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "clockcal",
|
||||
"name": "Clock & Calendar",
|
||||
"version": "0.08",
|
||||
"version": "0.10",
|
||||
"description": "Clock with Calendar",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(function (back) {
|
||||
var FILE = "clockcal.json";
|
||||
defaults={
|
||||
const defaults={
|
||||
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
|
||||
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
|
||||
MODE24: true, //24h mode vs 12h mode
|
||||
|
@ -9,19 +9,19 @@
|
|||
REDSAT: true, // Use red color for saturday?
|
||||
DRAGDOWN: "[AI:messg]",
|
||||
DRAGRIGHT: "[AI:music]",
|
||||
DRAGLEFT: "[ignore]",
|
||||
DRAGLEFT: "[AI:agenda]",
|
||||
DRAGUP: "[calend.]"
|
||||
};
|
||||
settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
|
||||
let settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"];
|
||||
let actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]","[AI:agenda]"];
|
||||
require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js","")));
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
menu = {
|
||||
const menu = {
|
||||
"": { "title": "Clock & Calendar" },
|
||||
"< Back": () => back(),
|
||||
'Buzz(dis)conn.?': {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Added Settings & readme
|
||||
0.03: Fix lint warnings
|
||||
0.04: Fix lint warnings
|
|
@ -0,0 +1,24 @@
|
|||
# Counter2 by Michael
|
||||
|
||||
I needed an HP/XP-Tracker for a game, so i made one.
|
||||
The counter state gets saved. Best to use this with pattern launcher or ClockCal
|
||||
|
||||
- Colored Background Mode
|
||||
- 
|
||||
- Colored Text Mode
|
||||
- 
|
||||
|
||||
## Howto
|
||||
- Tap top side or swipe up to increase counter
|
||||
- Tap bottom side or swipe down to decrease counter
|
||||
- Hold (600ms) to reset to default value (configurable)
|
||||
- Press button to exit
|
||||
|
||||
## Configurable Features
|
||||
- Default value Counter 1
|
||||
- Default value Counter 2
|
||||
- Buzz on interact
|
||||
- Colored Text/Background
|
||||
|
||||
## Feedback
|
||||
If something isn't working, please tell me: https://github.com/Stuff-etc/BangleApps/issues
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcAyVJkgCFAwwCBAgd5CI+eCI2T/IRH/wR7n//AAPyCIdPBAX8CKpr/CLTpSCOipB8gRFXoPJCIknCJAIBOoYRCagLNCa4f8Q4gREI4tP8mT/41HCKJHFGoQRG+QKBLI4RHLIx9CCJ7zBGpxZCPoyhQYpIIBYor7kCP4R8YoX/WY69DAIM/BAT+BdIYICeYQRTGqKP/CNIA=="))
|
|
@ -0,0 +1,95 @@
|
|||
Bangle.loadWidgets();
|
||||
|
||||
var s = Object.assign({
|
||||
counter0:10,
|
||||
counter1:20,
|
||||
max0:15,
|
||||
max1:25,
|
||||
buzz: true,
|
||||
colortext: true,
|
||||
}, require('Storage').readJSON("counter2.json", true) || {});
|
||||
|
||||
const f1 = (s.colortext) ? "#f00" : "#fff";
|
||||
const f2 = (s.colortext) ? "#00f" : "#fff";
|
||||
const b1 = (s.colortext) ? g.theme.bg : "#f00";
|
||||
const b2 = (s.colortext) ? g.theme.bg : "#00f";
|
||||
|
||||
var drag;
|
||||
|
||||
const screenwidth = g.getWidth();
|
||||
const screenheight = g.getHeight();
|
||||
const halfwidth = screenwidth / 2;
|
||||
const halfheight = screenheight / 2;
|
||||
|
||||
const counter = [];
|
||||
counter[0] = s.counter0;
|
||||
counter[1] = s.counter1;
|
||||
const defaults = [];
|
||||
defaults[0] = s.max0;
|
||||
defaults[1] = s.max1;
|
||||
|
||||
function saveSettings() {
|
||||
s.counter0 = counter[0];
|
||||
s.counter1 = counter[1];
|
||||
s.max0 = defaults[0];
|
||||
s.max1 = defaults[1];
|
||||
require('Storage').writeJSON("counter2.json", s);
|
||||
}
|
||||
|
||||
let ignoreonce = false;
|
||||
var dragtimeout;
|
||||
|
||||
function updateScreen() {
|
||||
g.setBgColor(b1);
|
||||
g.clearRect(0, 0, halfwidth, screenheight);
|
||||
g.setBgColor(b2);
|
||||
g.clearRect(halfwidth, 0, screenwidth, screenheight);
|
||||
g.setFont("Vector", 60).setFontAlign(0, 0);
|
||||
g.setColor(f1);
|
||||
g.drawString(Math.floor(counter[0]), halfwidth * 0.5, halfheight);
|
||||
g.setColor(f2);
|
||||
g.drawString(Math.floor(counter[1]), halfwidth * 1.5, halfheight);
|
||||
saveSettings();
|
||||
if (s.buzz) Bangle.buzz(50,.5);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
Bangle.on("drag", e => {
|
||||
const c = (e.x < halfwidth) ? 0 : 1;
|
||||
if (!drag) {
|
||||
if (ignoreonce) {
|
||||
ignoreonce = false;
|
||||
return;
|
||||
}
|
||||
drag = { x: e.x, y: e.y };
|
||||
dragtimeout = setTimeout(function () { resetcounter(c); }, 600); //if dragging for 500ms, reset counter
|
||||
}
|
||||
else if (drag && !e.b) { // released
|
||||
let adjust = 0;
|
||||
const dx = e.x - drag.x, dy = e.y - drag.y;
|
||||
if (Math.abs(dy) > Math.abs(dx) + 30) {
|
||||
adjust = (dy > 0) ? -1 : 1;
|
||||
} else {
|
||||
adjust = (e.y > halfwidth) ? -1 : 1;
|
||||
}
|
||||
counter[c] += adjust;
|
||||
updateScreen();
|
||||
drag = undefined;
|
||||
clearTimeout(dragtimeout);
|
||||
}
|
||||
});
|
||||
|
||||
function resetcounter(which) {
|
||||
counter[which] = defaults[which];
|
||||
console.log("resetting counter ", which);
|
||||
updateScreen();
|
||||
drag = undefined;
|
||||
ignoreonce = true;
|
||||
}
|
||||
|
||||
|
||||
updateScreen();
|
||||
|
||||
setWatch(function() {
|
||||
load();
|
||||
}, BTN1, {repeat:true, edge:"falling"});
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "counter2",
|
||||
"name": "Counter2",
|
||||
"version": "0.04",
|
||||
"description": "Dual Counter",
|
||||
"readme":"README.md",
|
||||
"icon": "counter2-icon.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"counter2-screenshot.png"},{"url":"counter2dark-screenshot.png"}],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"counter2.app.js","url":"app.js"},
|
||||
{"name":"counter2.settings.js","url":"settings.js"},
|
||||
{"name":"counter2.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"counter2.json"}]
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
(function (back) {
|
||||
var FILE = "counter2.json";
|
||||
const defaults={
|
||||
counter0:12,
|
||||
counter1:0,
|
||||
max0:12,
|
||||
max1:0,
|
||||
buzz: true,
|
||||
colortext: true,
|
||||
};
|
||||
const settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
const menu = {
|
||||
"": { "title": "Counter2" },
|
||||
"< Back": () => back(),
|
||||
'Default C1': {
|
||||
value: settings[0],
|
||||
min: -99, max: 99,
|
||||
onchange: v => {
|
||||
settings.max0 = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Default C2': {
|
||||
value: settings[2],
|
||||
min: -99, max: 99,
|
||||
onchange: v => {
|
||||
settings.max1 = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Color': {
|
||||
value: settings.colortext,
|
||||
format: v => v?"Text":"Backg",
|
||||
onchange: v => {
|
||||
settings.colortext = v;
|
||||
console.log("Color",v);
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Vibrate': {
|
||||
value: settings.buzz,
|
||||
onchange: v => {
|
||||
settings.buzz = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
// Show the menu
|
||||
E.showMenu(menu);
|
||||
});
|
|
@ -1,11 +1,17 @@
|
|||
#### ⚠️EXPERIMENTAL⚠️
|
||||
|
||||
# Fastload Utils
|
||||
|
||||
*EXPERIMENTAL* Use this with caution. When you find something misbehaving please check if the problem actually persists when removing this app.
|
||||
Use this with caution. When you find something misbehaving please check if the problem actually persists when removing this app.
|
||||
|
||||
This allows fast loading of all apps with two conditions:
|
||||
* Loaded app contains `Bangle.loadWidgets`. This is needed to prevent problems with apps not expecting widgets to be already loaded.
|
||||
* Current app can be removed completely from RAM.
|
||||
|
||||
#### ⚠️ KNOWN ISSUES ⚠️
|
||||
|
||||
* Fastload currently does not play nice with the automatic reload option of the apploader. App installs and upgrades are unreliable since the fastload causes code to run after reset and interfere with the upload process.
|
||||
|
||||
## Settings
|
||||
|
||||
* Activate app history and navigate back through recent apps instead of immediately loading the clock face
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: First release
|
|
@ -0,0 +1,17 @@
|
|||
# Fonts (Korean)
|
||||
|
||||
This library provides an Korean font that can be used to display messages.
|
||||
|
||||
The font is the 16px high [GNU Unifont](https://unifoundry.com/unifont/index.html).
|
||||
Korean characters from Unicode codepoint 32-255, 0x1100-0x11FF, 0x3130-0x318F, 0xA960-0xA97F
|
||||
|
||||
## Usage
|
||||
|
||||
See [the BangleApps README file](https://github.com/espruino/BangleApps/blob/master/README.md#api-reference)
|
||||
for more information on fonts.
|
||||
|
||||
|
||||
## Recreating fontkorean.pbf
|
||||
|
||||
* Go to `bin` directory
|
||||
* Run `./font_creator.js "Korean" ../apps/fontkorean/font.pbf`
|
After Width: | Height: | Size: 494 B |
|
@ -0,0 +1 @@
|
|||
Graphics.prototype.setFontIntl = function() { return this.setFontPBF(require("Storage").read("fontkorean.pbf")); };
|
|
@ -0,0 +1,3 @@
|
|||
exports.getFont = (options) => {
|
||||
return "Intl"; // placeholder for now - see https://github.com/espruino/BangleApps/issues/3109
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
{ "id": "fontkorean",
|
||||
"name": "Korean font",
|
||||
"version":"0.01",
|
||||
"description": "Installs a font data, Unifont characters for Korean **Requires 420 KB storage**",
|
||||
"icon": "app.png",
|
||||
"tags": "font",
|
||||
"type": "module",
|
||||
"provides_modules" : ["font"],
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"font","url":"lib.js"},
|
||||
{"name":"fontkorean.boot.js","url":"boot.js"},
|
||||
{"name":"fontkorean.pbf","url":"font.pbf"}
|
||||
]
|
||||
}
|
|
@ -9,3 +9,4 @@
|
|||
0.10: Show satellites "in view" separated by GNS-system
|
||||
0.11: Show number of packets received
|
||||
0.12: Fix number of packets received
|
||||
0.13: Minor code improvements
|
||||
|
|
|
@ -42,21 +42,21 @@ function getMaidenHead(param1,param2){
|
|||
|
||||
lon = lon + 180;
|
||||
var t = lon/20;
|
||||
fLon = Math.floor(t);
|
||||
const fLon = Math.floor(t);
|
||||
t = (t % fLon)*10;
|
||||
sqLon = Math.floor(t);
|
||||
const sqLon = Math.floor(t);
|
||||
t = (t-sqLon)*24;
|
||||
subLon = Math.floor(t);
|
||||
extLon = Math.floor((t-subLon)*10);
|
||||
const subLon = Math.floor(t);
|
||||
const extLon = Math.floor((t-subLon)*10);
|
||||
|
||||
lat = lat + 90;
|
||||
t = lat/10;
|
||||
fLat = Math.floor(t);
|
||||
const fLat = Math.floor(t);
|
||||
t = (t % fLat)*10;
|
||||
sqLat = Math.floor(t);
|
||||
const sqLat = Math.floor(t);
|
||||
t=(t-sqLat)*24;
|
||||
subLat = Math.floor(t);
|
||||
extLat = Math.floor((t-subLat)*10);
|
||||
const subLat = Math.floor(t);
|
||||
const extLat = Math.floor((t-subLat)*10);
|
||||
|
||||
return U[fLon]+U[fLat]+sqLon+sqLat+L[subLon]+L[subLat]+extLon+extLat;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpsinfo",
|
||||
"name": "GPS Info",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"description": "An application that displays information about latitude, longitude, altitude, speed, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.01: New App!
|
||||
0.02: Get health data for the day
|
||||
|
|
|
@ -91,7 +91,7 @@ const runHassio = () => {
|
|||
});
|
||||
|
||||
const updateSensor = () => {
|
||||
hassioAttributes.health = Bangle.getHealthStatus();
|
||||
hassioAttributes.health = Bangle.getHealthStatus("day");
|
||||
hassioAttributes.accel = Bangle.getAccel();
|
||||
hassioAttributes.battery = getBattery();
|
||||
hassioAttributes.compass = Bangle.getCompass();
|
||||
|
@ -125,4 +125,4 @@ const runHassio = () => {
|
|||
setInterval(runHassio, HASSIO.interval);
|
||||
}, 5000);
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Home Assistant API Interface",
|
||||
"shortName":"Hassio",
|
||||
"icon": "hassio.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "This app gives access to viewing Home Assistant data and sends health, compass, accelerometer, and battery information to Home Assistant as a sensor through the Home Assistant REST API.",
|
||||
"tags": "tool,sensors",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
@ -15,4 +15,4 @@
|
|||
{"name":"hassio.img","url":"hassio.img"}
|
||||
],
|
||||
"data": [{"name":"hassio.json"}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,4 @@
|
|||
Fix daily summaries for 31st of the month
|
||||
0.28: Calculate distance from steps if myprofile is installed and stride length is set
|
||||
0.29: Minor code improvements
|
||||
0.30: Minor code improvements
|
||||
|
|
|
@ -176,7 +176,6 @@ function barChart(label, dt) {
|
|||
chart_label = label;
|
||||
chart_data = dt;
|
||||
drawBarChart();
|
||||
swipe_enabled = true;
|
||||
}
|
||||
|
||||
function drawBarChart() {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"shortName": "Health",
|
||||
"version": "0.29",
|
||||
"version": "0.30",
|
||||
"description": "Logs health data and provides an app to view it",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,health",
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
0.10: Autoscale raw graph to maximum value seen
|
||||
0.11: Automatic translation of strings.
|
||||
0.12: Minor code improvements
|
||||
0.13: Minor code improvements
|
||||
|
|
|
@ -61,7 +61,6 @@ var scale = 2000;
|
|||
/* On newer (2v10) firmwares we can subscribe to get
|
||||
HRM events as they happen */
|
||||
Bangle.on('HRM-raw', function(v) {
|
||||
h=v;
|
||||
hrmOffset++;
|
||||
if (hrmOffset>g.getWidth()) {
|
||||
let thousands = Math.round(rawMax / 1000) * 1000;
|
||||
|
@ -76,7 +75,7 @@ Bangle.on('HRM-raw', function(v) {
|
|||
if (rawMax < v.raw) {
|
||||
rawMax = v.raw;
|
||||
}
|
||||
y = E.clip(btm-(8+v.filt/3000),btm-24,btm);
|
||||
let y = E.clip(btm-(8+v.filt/3000),btm-24,btm);
|
||||
g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y);
|
||||
y = E.clip(btm - (v.raw/scale*84),84,btm);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
|
@ -120,7 +119,7 @@ function readHRM() {
|
|||
for (var i=0;i<2;i++) {
|
||||
var a = hrmInfo.raw[hrmOffset];
|
||||
hrmOffset++;
|
||||
y = E.clip(170 - (a*2),100,230);
|
||||
let y = E.clip(170 - (a*2),100,230);
|
||||
g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
|
||||
lastHrmPt = [hrmOffset, y];
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "hrm",
|
||||
"name": "Heart Rate Monitor",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"icon": "heartrate.png",
|
||||
"tags": "health",
|
||||
|
|
|
@ -9,4 +9,5 @@
|
|||
0.08: Use Bangle.setUI for button/launcher handling
|
||||
0.09: Bangle.js 2 compatibility
|
||||
0.10: Tell clock widgets to hide.
|
||||
0.11: Allow fullscreen clock faces with hidden widgets
|
||||
0.11: Allow fullscreen clock faces with hidden widgets
|
||||
0.12: Fixed a bug where other app's graphics would not get cleared.
|
||||
|
|
|
@ -3,6 +3,7 @@ Draws a fullscreen image from flash memory
|
|||
Saves a small image to flash which is just the area where the clock is
|
||||
Keeps an offscreen buffer and draws the time to that
|
||||
*/
|
||||
g.clear(); //clears other apps's graphics
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
var inf = require("Storage").readJSON("imgclock.face.json");
|
||||
var img = require("Storage").read("imgclock.face.img");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "imgclock",
|
||||
"name": "Image background clock",
|
||||
"shortName": "Image Clock",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"description": "A clock with an image as a background. **Note:** this clock shows seconds, so has higher than average power consumption.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Created
|
||||
0.02: Changed side bar color to blue for better clarity when it's locked
|
|
@ -0,0 +1,26 @@
|
|||
# jclock
|
||||
|
||||
I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own clock with much simpler features and to switch the time window and the feature window because I'm wearing my watch on my left wrist and about a half (left side) of the screen is covered by the sleeve of my jacket or shirts. Of course it won't happen during summer, but I decided to make my first Bagle app with these changes. See Features below for the items displayed on the screen.
|
||||
- The layout is inspired by the Rebble clock.
|
||||
- The big font KdamThmor is copied from the Rebble clock.
|
||||
|
||||
## Features
|
||||
- Single screen
|
||||
- No settings
|
||||
- Time on the right side with big font
|
||||
- On the sidebar on the left
|
||||
- Day of week
|
||||
- Day
|
||||
- Month
|
||||
- Steps
|
||||
- Bluetooth connection status
|
||||
- Battery %
|
||||
- Update time and status every 1 minute
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
||||
## Creator
|
||||
|
||||
Written by [JeonLab](https://jeonlab.wordpress.com)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4f/AAIHB7ue4cYrPO0cQtUy2WUHU0kyVJARAQEhIRLkgQCgQOKAQWACIYbHIImQAYMSpQRLgmSCIVSCJcACIWSKJARPzO9gETm+4BwNACI8Etu28GN23fIgIRIg14hnw/UI7wRGltvCIMjuEO7BCBCI97twRCsEICIMO7gRKnYRCju2/A1Hr4RHEY5ZDGokJzeACJRZB+EAgPbCIxrDgd4g347kBEY8rLIUHUIPA9qhICIcA/LFBj830ARLAAwR/CMkd3wOBozXHCIcE5oRCswRLg/RCIMD3gRLcoIRBgOwCJ8Z+ARIfYQRDx8ACI97CI1uCI9K7YRFglt23b4ARGuQROpPXAQI1EAAJHHkgCCg4gB+ARVyQRIdI+SdgVSCKFKCJcEyADBgVJlIRJhMkCIUAEQgCIwAXECJUgHYoRJPQIAlA="))
|
|
@ -0,0 +1,68 @@
|
|||
// single screen, clock on the right, sidebar without image (date/steps/bluetooth connection status/batt%)
|
||||
|
||||
// Large font KdamThmor taken from Rebble clock
|
||||
Graphics.prototype.setFontKdamThmor = function(scale) {
|
||||
// Actual height 70 (69 - 0)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('AH4AMgfABZM/BZMB/4WJg/+BZMf/ALJ//gIpP/wAugLpUAvyBKsDC/ACKYJQIKYJgaYKv6YJh7HJeoP8VxLSJg//+D0JIhMf/7RIf4JPJv//LX5a6CwLvJn5aJLYIKJgY4IADn/KpKvBAAKvIAARiGBQanGOwILJBQgLFFogvGIgZHGWAIAEdwg5FNYreBAAjvDeoIAFYQcfBYy3DEQRKEKQQiCAoRiCIogoDCIJGDEQLlEIwZoBCwYLCHQQoBQwgGEj7aFGoKuDKwYSFE4LZFv41Ch6dEIITICn5FEDwQuDeAwuEBQgeEB4b8EFwbADNIZdaHQoSBFwUfNIoGEv5GFXYpGEIoJBCZgjZGHQILDCwIpDj//GgQoBMggcBAApkDBQwiDDoQAEEQY0BERJGBERBGCERC8BBYrYFBQj8FLwrBGBQbkFEYoKFBYgtFL4jLFZ4gKJAH4AciALKRA73DbIgAFj/ABZLOGEQjDEj40En6tEv4oDgLPEAoLRFCIcHDgouJDgP4FxAiFFwt//xXEFwcDEQouEj4iEFwv/EQguEEQJ6EFwgiBS4guE/5uEFwiiBAAyiDBQwdDCw4uCIoIAGFwSLBF34unAAy7EAAy7EAAzqEAArqEF34ukAH4AGgfgNJWAAod8Cwn+SQn4RggFEv4oE/4FDg//FAYFFn4oEAoidBFAYFFh//YIYFBFwd//7BDAoIuCgf/YIYFBFwcfFAgFFDgIoDDgIFCEQpcBFwZFFn4uEAoJcEFwYFBLgouDQoo/BAwcf/hcEFwgiELgPfFwQRBEQYVBFwcPDYYzB+YSDn55DKwOPFwgbCKwP8CQYuBXIouEKIZcBIIgbF/BBEDYZcB4ASFDYI5BCgIuEHQSzCFwo6CeYQuEv4nBOYIPBFwa7Ddoa7FJoLtCFwhNBAAQfBFwiTBAAXAT4oKDCYSfFAAQ9BFwg6BAAQHBFwhDCLgQuFIwY5BFwhGDDwT9FOQI5CFwpSDDoYuDBYQWCFwoLCAgQuFCIsHFwgAFh4uEAH4AWjgLKvwGFj6LDP4sBcgjhCCwaGDn4LEgKjDAgKXEh61Dg7LEdQIuDj7AEZgIpDfYPACIgdCFwLjDdIQRCFwIoDEQJdEFAgiBJgYoEEQoLCAoRFFBYRjCFAIWDQII0Dv6SFv40CRYg1DHQRXBBQg1BFISpDBwQSEEQTQDj4SCDYJKBh42Cv4uCh4TCn4aBIIIuDCYIHBDQIeBFwYPBg4aCe4YPDfAYuHv4uNLo6bBLpJ4EFwYTBEQIHBCQYbBHQIqBEwIGCXYl/IQTwDD4P+CwIfBFILCCBAQACwACBEQQQBAArlDn4LGcoY3BGAIlEHQYAB+YiGMQIAB54DCOgRGD/0fEQpGD+A+CEQZ6BLYhFEKQX8HwYKDBYXgHwQ5DBYQpBBYQ5DHYRWDUQQAGgK5DADsBBZUfb4IAIOYoAETgJcFAAbLBBRBoBUQg5FRYxQDRYJGIZQQ5KFxDtCFxDpCFw7dIfAouICwQuHHIP+FxBQB8YuHf4UPFw6KCn4uGKAWAFw6KB/glBHJHAFw5QCQQIuGRQLzBFww5CKgRQH/A9BFwxQCFw45BCYQuGKAI5BFwwGBKAIuHRQRVCFwhQDFw6KBKAIuHfwQAEGAYKGGgbQCAAowCFwIAGF34ugAAjqHTojqFfQrqFcYoWJF0f+CxMH8ALJAEkCBZU8BRMB/CCKOw0DA4V/OwqhBA4IDBwAKFVoTlBBQytCn6xDBQX/IQQDDAgIACSwIRBTQQWDGwUHHQYzBAAK5CHQk/Fwo6EFwppBNoQuGgIPDFwYeCOoguC34eCh74DEASMCCQI+CDYQCBCQYuDDYMPFwQ6BFwYbBn4uCg4uE8ASBFwUfFwqIBCQV/FwsfLpAbBPgZdFFwpdGFwhdHDwQPELoYeCHwYbD/46CAYaMEBwLqFFwRGCv5RDFYUfBYIWBGQQuDv7iDMIQuCNIIADCwQuCfIgiDFwT5DEQYuDHQIiFVAc/EQyJDIwYiDc4RGDNAYuBCAJGDRYQHBCAQLDCwcPCAR+BHIgAEBYQKHEYQtDAH4Ak/gKJZALMBRhLGDAAjSGWYgLCEY7qDBYwtCXhBEBewzpF/5fGj4LDdYwKD//gKBBeHKAZGGHIX+gJGGKAQfBHQoSBCYQEB+A5GA4InBHQiJEQgKKGOIUPHQg5CFQU/HQaKDVgR1ERQQeCIwK8DBQPvDwUHFwZQB/0/DwUfFwaKB+IeDv4PCHIWHFw45B/geDFwjBCDwYPDEQKsCLoxFB+CIDCQIPCP4OAj6MCj4uEBAN/FQV/SAS0CFwIqBXYioCA4ZYBVwYbBHoIaCQAY+CHoPACwKADGwa+CEQcPFQIfBAARVCgE+dgiGCBYRVCHQLiFganEEQsIZQgiFAAZFGAAZGDNAYADcQSLDAAhSCVwYLHHI4LCCxC5FAH4AIJhRYBXgQAGh5vJgE/VI4uDSRAuJoAuJg4uKvguJg/wFxN/OAQuGaoIuJv/8FxAWBFxN/T4YuFCwIuJCwIuICwQuICwIuICwQGDFwgWCEQQuECwQpDFwk/BQIdDFwYPBCwguECwwuDCw4uDCw4uCCw4uDCw4uCCxAuCCxAuBCwYKEFwQWCRIYuD8YWIEAO/CxEPCoQWGLQYWHFwIWJJ4YWHFwYKGFwYWHFwYKHFwQWIFwQKHFwQWIFwQKIFwIWJdQQuJ8ALJAH8f/BuK/gIFv6RDBYqlBwEBSIIjFA4OAWgSSEA4WAv4LGA4TXC//Ab4v+j4LCwBYDAwP8DQTNEAwXzAYTCDFQfvAYRSDFQYADIwYqDAAZGCEQYAB8A6ENARHCDoI6DAgKKCD4N/HQQIB8ACBCYQGBAYMHE4IxBIQIPBHQU/DYIOBA4ISCDYQHBh4iCh7ICD4IaEAYJpCB4d/GwQuEGwasBDwYPBA4MHFw4HCj4uHA4QuULqyUDRgxCCRhC0Cn46CEwYbB+DhCYQa7DAAQyBcoIaBdQoLBawYrCAApRCHQILGKIT/C//7Eoh1DAAPvAYRRCIwkfEQpGD/AyDBQSBBCQQiGKQX+HwYiDKQXwGQRFDBYYyDNAYLCAwILCBQg+FHIgAEC4IKIQwKtCAH4AWnwKJPoKrEOAi3GaY4WJ/6KHW4ShIfwTbFAAMDCwX8A4UYHIrQE8AiFeYcHHwQiDKQZ6DEQZSCgYmDEQZGCj4uCEQQZBCYRtDNAPAg46Cg5hDv5aBBYI6Bn4aCRYInBDQIpCFwQTBGwQaBGQIuCn59Cn4uBSAgbDHoYuCE4JlCEwJjBCQUPEQUH/hjCFwaUCj/wHIKzDSgd/4AWBQAhhDcYTpDFwg5BUYYuE8Y5ELoufHIhdFaoguBYYbJESgjWDGgQHCH4IiDBQZZBCIIiCKAa7CIwIWCKAbPC8AWCKAZpCCgRQFIQhQGHQQADKAhOEKApGDAARQEIwZQHIwpQFBYpQFKQgWHPwYWHBYQWIEYREGL4YKJAH4AegIEDsCxGPIfgCwr/Dn6nFh6jCgKcGn/wEQQbDXgYqCn/4BQkDDwYPDFzV/JoUfB4RdOgI1DnjG/ACoA='))),
|
||||
46,
|
||||
atob("GBo2NjY2NjY2NjY2Gg=="),
|
||||
94+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
const zeroPad = (num, places) => String(num).padStart(places, '0');
|
||||
|
||||
function draw() {
|
||||
let barWidth = 64;
|
||||
let date = new Date();
|
||||
|
||||
// queue next draw in one minute
|
||||
queueDraw();
|
||||
|
||||
// clean screen
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
|
||||
// draw side bar in blue
|
||||
g.setColor('#00f');
|
||||
g.fillRect(0, 0, barWidth, g.getHeight());
|
||||
|
||||
// show time on the right
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getHours(),2), 120, 10);
|
||||
g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getMinutes(),2), 120, g.getHeight()/2+10);
|
||||
|
||||
// show date
|
||||
g.setFont('Vector', 20).setFontAlign(0, -1).setColor('#fff');
|
||||
g.drawString(require("date_utils").dow(date.getDay(),1).toUpperCase(), barWidth/2, 3);
|
||||
g.drawString(date.getDate(), barWidth/2, 28);
|
||||
g.drawString(require("date_utils").month(date.getMonth()+1,1).toUpperCase(), barWidth/2, 53);
|
||||
|
||||
// divider, place holder for any other info
|
||||
g.drawString('=====', barWidth/2, 78);
|
||||
|
||||
// show daily steps
|
||||
g.drawString(Bangle.getHealthStatus("day").steps, barWidth/2, 103);
|
||||
|
||||
// show battery remaining percentage
|
||||
g.drawString(E.getBattery() + '%', barWidth/2, 153);
|
||||
|
||||
// Bluetooth connection status
|
||||
if (NRF.getSecurityStatus().connected) g.drawString('>BT<', barWidth/2, 128);
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
Bangle.setUI("clock");
|
After Width: | Height: | Size: 824 B |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,16 @@
|
|||
{ "id":"jclock",
|
||||
"name":"jclock",
|
||||
"shortName":"jclock",
|
||||
"icon":"app.png",
|
||||
"version":"0.02",
|
||||
"description":"Similar layout to Rebble clock, but much simpler features with switched time and the feature window. The time is on the right side. This is my first Bangle app.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"jclock_screenshot_no_BT.png"},{"url":"jclock_screenshot_BT.png"}],
|
||||
"storage": [
|
||||
{"name":"jclock.app.js","url":"app.js"},
|
||||
{"name":"jclock.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"readme":"README.md"
|
||||
}
|
|
@ -59,13 +59,6 @@ module.exports = {
|
|||
"no-unused-vars"
|
||||
]
|
||||
},
|
||||
"sleeplog/settings.js": {
|
||||
"hash": "bd5e3e1382321df6682ef1cb718b0e15ab355422bef77278eb086f213f643021",
|
||||
"rules": [
|
||||
"no-unused-vars",
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"showimg/app.js": {
|
||||
"hash": "71cbbaa488e2d08c5bf28f7d56178d5e7694eb9761cd4752bbc9733e825d4bcf",
|
||||
"rules": [
|
||||
|
@ -276,13 +269,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"sleeplog/app.js": {
|
||||
"hash": "336da552e4b04677447cf76a253b40bc259a597ea11d455121933f93afe99794",
|
||||
"rules": [
|
||||
"no-unused-vars",
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"qmsched/app.js": {
|
||||
"hash": "4b7dbabed6c252021531d6b0449c16a3adc2e405f2ddda33ca0a65f5fa42c663",
|
||||
"rules": [
|
||||
|
@ -562,13 +548,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"sleeplog/lib.js": {
|
||||
"hash": "755e0d4c02b92181281fd6990df39c9446c73ff896b50b64d7e14cb1c0188556",
|
||||
"rules": [
|
||||
"no-unused-vars",
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"doztime/app-bangle1.js": {
|
||||
"hash": "1e9598c201175180ae77d1c3bc47e8138b339b72eb58782b5057fb7aefdc88a1",
|
||||
"rules": [
|
||||
|
@ -666,12 +645,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"taglaunch/app.js": {
|
||||
"hash": "944689f0600e59bbe4d9e5e2684baeefabe4457a6edd938aae451dc4cd659ad3",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"tabanchi/app.js": {
|
||||
"hash": "6ad6dc1d6b0f539f9f659d5773b5a26d19eb6dacafe7b4682469e6f3c412647e",
|
||||
"rules": [
|
||||
|
@ -762,12 +735,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"sleeplog/boot.js": {
|
||||
"hash": "b4c9d8e3c3e7cdf44ea10e29a9e3b53f958b86c21ca91d88e4efb85901c3bde9",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"scicalc/app.js": {
|
||||
"hash": "416c7b2eb12a5d10bcc3a99d89d8f6f54ecd2b47cce2d1f4d55c3e3bc602b31a",
|
||||
"rules": [
|
||||
|
@ -798,12 +765,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"ratchet_launch/app.js": {
|
||||
"hash": "592d432301d7836aa54e288d465ae8952ecb891d628f824ea9f62479a2a01631",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"rclock/rclock.app.js": {
|
||||
"hash": "8e698787730601a1bba71aff03204c2adfaf7eeb77b35dc706534755f63f613b",
|
||||
"rules": [
|
||||
|
@ -1068,12 +1029,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"hrm/heartrate.js": {
|
||||
"hash": "beb8e433f10d3639b343b060f0d5583ea665445f92b2171daff7612eaf135596",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"hebrew_calendar/app.js": {
|
||||
"hash": "3077d581b9fcf73816e265e61105a0692356b89e8ed41a82be51960ae26fc8de",
|
||||
"rules": [
|
||||
|
@ -1086,12 +1041,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"health/app.js": {
|
||||
"hash": "6d612eed04ee5a844be6ad47c326624cd3e204fecf1c28c99a57ca963b3d7a7b",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"hassio/hassio.app.js": {
|
||||
"hash": "b8fbb03cf4a7595299e65a46c4f850394bf57cd4cba879d5524eafbf40ccc32e",
|
||||
"rules": [
|
||||
|
@ -1110,12 +1059,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"gpsinfo/gps-info.js": {
|
||||
"hash": "1eb77f45d4182613879b8214dc174f84c7333b4a541c2b43cba6014a16f470ee",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"glbasic/glbasic.app.js": {
|
||||
"hash": "7d12a030d6f0ef69a0e5a9783229fd49c0a6a06bf751e3ac562145d2ce8350e9",
|
||||
"rules": [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New Clock!
|
|
@ -0,0 +1,11 @@
|
|||
# Meridian Clock
|
||||
|
||||
An elegant clock with 2 clock info
|
||||
|
||||
## Usage
|
||||
|
||||
Tap on a widget and swipe left/right/up/down to change the displayed info
|
||||
|
||||
## Creator
|
||||
|
||||
Spioune
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgMAkEAwEEBIMQAo8IDpMYAq0wAoosCAoU8gNgAoV+gwRDvgXDsAFFEYgFR4AFQIgQFHgIFC8AFDg4HBhwWEngFE+AEDgYuEh4QEDgoASiGII4kMAYLWBgDKBggzEgb/YICSJBGwIFDghCDAoQ"))
|
|
@ -0,0 +1,153 @@
|
|||
function getArcXY(centerX,centerY,radius,angle){
|
||||
var s,r = [];
|
||||
s = 2 * Math.PI * angle / 360;
|
||||
r.push(centerX + Math.round(Math.cos(s) * radius));
|
||||
r.push(centerY + Math.round(Math.sin(s) * radius));
|
||||
return r;
|
||||
}
|
||||
|
||||
function getArc(centerX,centerY,radius,startAngle,endAngle){
|
||||
var r = [], actAngle = startAngle;
|
||||
var stepAngle = (radius + radius) * Math.PI / 60;
|
||||
stepAngle = 6;
|
||||
while(actAngle < endAngle){
|
||||
r = r.concat(getArcXY(centerX,centerY,radius,actAngle));
|
||||
actAngle += stepAngle;
|
||||
actAngle = Math.min(actAngle,endAngle);
|
||||
}
|
||||
return r.concat(getArcXY(centerX,centerY,radius,endAngle));
|
||||
}
|
||||
|
||||
function fillLine(x1,y1,x2,y2,thickness){
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
const offset_x = thickness * Math.sin(angle) / 2;
|
||||
const offset_y = thickness * Math.cos(angle) / 2;
|
||||
g.fillPoly([
|
||||
x1 + offset_x,
|
||||
y1 - offset_y,
|
||||
x1 - offset_x,
|
||||
y1 + offset_y,
|
||||
x2 - offset_x,
|
||||
y2 + offset_y,
|
||||
x2 + offset_x,
|
||||
y2 - offset_y
|
||||
],true);
|
||||
}
|
||||
|
||||
function drawInfoClock(itm,info,options){
|
||||
g.reset();
|
||||
|
||||
if (options.focus)
|
||||
g.drawCircle(options.x+options.w/2, options.y+options.h/2, options.w/2+3);
|
||||
|
||||
if (info.img)
|
||||
g.drawImage(info.img, info.img.width ? options.x+options.w/2-info.img.width/2 : options.x, options.y);
|
||||
|
||||
if(info.text)
|
||||
g.setFont("6x8").setFontAlign(0,1).drawString(info.text, options.x+options.w/2,options.y+options.h);
|
||||
}
|
||||
|
||||
var clockInfoItems = require("clock_info").load();
|
||||
|
||||
clockInfoItems[0].items.unshift({
|
||||
name : "BatteryRing",
|
||||
hasRange : true,
|
||||
get : () => {
|
||||
var s = 30;
|
||||
var mid=s/2;
|
||||
var v = E.getBattery();
|
||||
var g = Graphics.createArrayBuffer(s,s,4);
|
||||
|
||||
const outerarc = getArc(mid,mid,14,-90,Math.max(v*3.6, 10)-90);
|
||||
const innerarc = getArc(mid,mid,11,-92,Math.max(v*3.6, 10)-88);
|
||||
|
||||
g.reset();
|
||||
g.transparent=0;
|
||||
g.setColor('#00FF00').fillPoly([mid, mid].concat(outerarc));
|
||||
g.setColor('#000').fillPoly([mid, mid].concat(innerarc));
|
||||
g.setFont("6x8").setColor('#FFF').setFontAlign(0, 0).drawString(v, mid, mid);
|
||||
return { v : v, min:0, max:100, img : g.asImage("object") };
|
||||
},
|
||||
show : function() { },
|
||||
hide : function() { },
|
||||
});
|
||||
|
||||
var topleft = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x : g.getWidth()*(1/4)-15, y: g.getHeight()*(1/4)-15, w: 30, h:30,
|
||||
draw : (itm,info,options)=>{
|
||||
topleft.info = info;
|
||||
topleft.options = options;
|
||||
if(typeof draw === 'function') draw();
|
||||
}
|
||||
});
|
||||
|
||||
var topright = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x : g.getWidth()*(3/4)-15, y: g.getHeight()*(1/4)-15, w: 30, h:30,
|
||||
draw : (itm,info,options)=>{
|
||||
topright.info = info;
|
||||
topright.options = options;
|
||||
if(typeof draw === 'function') draw();
|
||||
}
|
||||
});
|
||||
|
||||
var timeout;
|
||||
|
||||
function draw(){
|
||||
if(timeout){
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
|
||||
g.setTheme({fg:0xFFFF, bg:0});
|
||||
g.reset().clear();
|
||||
|
||||
const mid=g.getWidth()/2;
|
||||
|
||||
for(let i = 0; i<12;i++){
|
||||
const angle = i*Math.PI/6;
|
||||
fillLine(mid, mid, mid+Math.cos(angle)*120, mid+Math.sin(angle)*120, 3);
|
||||
}
|
||||
|
||||
g.clearRect(10,10,g.getWidth()-10,g.getHeight()-10);
|
||||
|
||||
if(topleft && topleft.info && topleft.options)
|
||||
drawInfoClock(topleft.itm, topleft.info, topleft.options);
|
||||
if(topright && topright.info && topright.options)
|
||||
drawInfoClock(topright.itm, topright.info, topright.options);
|
||||
|
||||
const now = new Date();
|
||||
|
||||
g.setFont("Vector",14);
|
||||
g.setColor('#FFF');
|
||||
g.setFontAlign(0,0);
|
||||
// Date (ex. MON 8)
|
||||
g.drawString(require("locale").dow(now, 1).toUpperCase() + " " + now.getDate(), g.getWidth()/2, g.getHeight()*(3/4));
|
||||
|
||||
|
||||
let rhour = (now.getHours()*Math.PI/6)+(now.getMinutes()*Math.PI/30/12)-Math.PI/2;
|
||||
let rmin = now.getMinutes()*Math.PI/30-Math.PI/2;
|
||||
|
||||
// Middle circle
|
||||
g.fillCircle(mid,mid,4);
|
||||
|
||||
// Hour hand
|
||||
fillLine(mid, mid, mid+Math.cos(rhour)*10, mid+Math.sin(rhour)*10,3);
|
||||
fillLine(mid+Math.cos(rhour)*10, mid+Math.sin(rhour)*10, mid+Math.cos(rhour)*50, mid+Math.sin(rhour)*50,7);
|
||||
|
||||
// Minute hand
|
||||
fillLine(mid, mid, mid+Math.cos(rmin)*10, mid+Math.sin(rmin)*10,3);
|
||||
fillLine(mid+Math.cos(rmin)*10, mid+Math.sin(rmin)*10, mid+Math.cos(rmin)*76, mid+Math.sin(rmin)*76,7);
|
||||
|
||||
|
||||
if(new Date().getMinutes()==0){
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
timeout = setTimeout(()=>{
|
||||
timeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
draw();
|
||||
Bangle.setUI("clock");
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "meridian",
|
||||
"name": "Meridian Clock",
|
||||
"shortName": "Meridian",
|
||||
"version": "0.01",
|
||||
"description": "An elegant clock",
|
||||
"screenshots": [{ "url": "screenshot.png" }],
|
||||
"icon": "icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "meridian.app.js", "url": "app.js" },
|
||||
{ "name": "meridian.img", "url": "app-icon.js", "evaluate": true }
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -104,3 +104,4 @@
|
|||
0.75: Handle text with images in messages list by just displaying the first line
|
||||
0.76: Swipe up/down on a shown message to show the next newer/older message.
|
||||
0.77: Messages can now use international fonts if they are installed
|
||||
0.78: Fix: When user taps on a new message, clear the unread timeout
|
||||
|
|
|
@ -233,6 +233,7 @@ function showMusicMessage(msg) {
|
|||
}
|
||||
|
||||
function showMessageScroller(msg) {
|
||||
cancelReloadTimeout();
|
||||
active = "scroller";
|
||||
var bodyFont = fontBig;
|
||||
g.setFont(bodyFont);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.77",
|
||||
"version": "0.78",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -5,3 +5,9 @@
|
|||
0.05: Fix the overlay keeping the LCD on
|
||||
0.06: Better low memory handling
|
||||
Fix first message beeing displayed again on unlock
|
||||
0.07: Adds settings
|
||||
Automatic discard of oldest messages
|
||||
Indicator for multiple messages in queue
|
||||
Some optimization in the rendering code
|
||||
Track handler changes done by background code
|
||||
0.08: Fix linter warnings
|
||||
|
|
|
@ -2,23 +2,26 @@
|
|||
|
||||
This app handles the display of messages and message notifications as an overlay pop up.
|
||||
|
||||
It is a GUI replacement for the `messages` apps.
|
||||
It is a GUI replacement for the `messagesgui` app.
|
||||
|
||||
Messages are ephemeral and not stored on the Bangle.
|
||||
|
||||
## Usage
|
||||
|
||||
Close app by tapping the X and scroll by swiping. The border of the pop up changes color if the Bangle is locked. The color depends on your currently active theme.
|
||||
Close app by tapping the X and scroll by swiping. The title background of the pop up changes color if the Bangle is locked. The color depends on your currently active theme.
|
||||
|
||||
## Firmware hint
|
||||
Current stable firmware draws incorrect colors for emojis. Nightly firmware builds correct this.
|
||||
## Theme support
|
||||
|
||||
Using the system theme needs more RAM since it uses a 16 bit color buffer for normal message display. Selecting the "low RAM" theme reduces that to a 4 bit buffer.
|
||||
16 bit buffer with a small message takes ~4K RAM blocks while 4 bit buffer only needs about 1.5K.
|
||||
|
||||
## Low memory mode
|
||||
|
||||
If free memory is below 2000 blocks, the overlay automatically only uses 1 bit depth. Default uses roundabout 1300 blocks, while low memory mode uses about 600.
|
||||
If the overlay estimates that showing the next message would get under the configured minimum free memory limit it automatically only tries to use 1 bit depth. Low memory mode uses about 0.8K blocks plus memory needed for messages. If dropping to 1 bit depth is not enough the oldest messages are discarded to keep the overlay working.
|
||||
|
||||
## Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
||||
|
||||
Forked from messages_light by Rarder44
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"autoclear": 30,
|
||||
"border": 10,
|
||||
"minfreemem": 2,
|
||||
"systemTheme": true
|
||||
}
|
|
@ -1,49 +1,69 @@
|
|||
const MIN_FREE_MEM = 1000;
|
||||
const LOW_MEM = 2000;
|
||||
const ovrx = 10;
|
||||
const ovry = 10;
|
||||
let lockListener;
|
||||
let ovr;
|
||||
let clearingTimeout;
|
||||
|
||||
// Converts a espruino version to a semantiv versioning object
|
||||
const toSemantic = function (v){
|
||||
return {
|
||||
major: v.substring(0,v.indexOf("v")),
|
||||
minor: v.substring(v.indexOf("v") + 1, v.includes(".") ? v.indexOf(".") : v.length),
|
||||
patch: v.includes(".") ? v.substring(v.indexOf(".") + 1, v.length) : 0
|
||||
};
|
||||
};
|
||||
|
||||
const isNewer = function(espruinoVersion, baseVersion){
|
||||
const s = toSemantic(espruinoVersion);
|
||||
const b = toSemantic(baseVersion);
|
||||
|
||||
return s.major >= b.major &&
|
||||
s.minor >= b.major &&
|
||||
s.patch > b.patch;
|
||||
};
|
||||
|
||||
let needsWorkaround;
|
||||
|
||||
let settings = Object.assign(
|
||||
require('Storage').readJSON("messagesoverlay.default.json", true) || {},
|
||||
require('Storage').readJSON("messagesoverlay.json", true) || {}
|
||||
);
|
||||
|
||||
settings = Object.assign({
|
||||
fontSmall:"6x8",
|
||||
fontMedium:"6x15",
|
||||
fontBig: "12x20"
|
||||
}, settings);
|
||||
|
||||
const ovrx = settings.border;
|
||||
const ovry = ovrx;
|
||||
const ovrw = g.getWidth()-2*ovrx;
|
||||
const ovrh = g.getHeight()-2*ovry;
|
||||
let _g = g;
|
||||
|
||||
let lockListener;
|
||||
let quiet;
|
||||
let LOG=()=>{};
|
||||
//LOG = function() { print.apply(null, arguments);};
|
||||
|
||||
let LOG = function() {
|
||||
//print.apply(null, arguments);
|
||||
};
|
||||
|
||||
let isQuiet = function(){
|
||||
if (quiet == undefined) quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
|
||||
return quiet;
|
||||
};
|
||||
|
||||
let settings = {
|
||||
fontSmall:"6x8",
|
||||
fontMedium:"Vector:14",
|
||||
fontBig:"Vector:20",
|
||||
fontLarge:"Vector:30",
|
||||
const isQuiet = function(){
|
||||
return (require('Storage').readJSON('setting.json', 1) || {}).quiet;
|
||||
};
|
||||
|
||||
let eventQueue = [];
|
||||
let callInProgress = false;
|
||||
let buzzing = false;
|
||||
|
||||
let show = function(ovr){
|
||||
let img = ovr;
|
||||
LOG("show", img.getBPP());
|
||||
const show = function(){
|
||||
let img = ovr.asImage();
|
||||
LOG("show", img.bpp);
|
||||
if (ovr.getBPP() == 1) {
|
||||
img = ovr.asImage();
|
||||
img.palette = new Uint16Array([_g.theme.fg,_g.theme.bg]);
|
||||
img.palette = new Uint16Array([g.theme.fg,g.theme.bg]);
|
||||
}
|
||||
Bangle.setLCDOverlay(img, ovrx, ovry);
|
||||
};
|
||||
|
||||
let manageEvent = function(ovr, event) {
|
||||
const manageEvent = function(event) {
|
||||
event.new = true;
|
||||
|
||||
LOG("manageEvent");
|
||||
if (event.id == "call") {
|
||||
showCall(ovr, event);
|
||||
showCall(event);
|
||||
return;
|
||||
}
|
||||
switch (event.t) {
|
||||
|
@ -51,7 +71,7 @@ let manageEvent = function(ovr, event) {
|
|||
eventQueue.unshift(event);
|
||||
|
||||
if (!callInProgress)
|
||||
showMessage(ovr, event);
|
||||
showMessage(event);
|
||||
break;
|
||||
|
||||
case "modify": {
|
||||
|
@ -66,23 +86,23 @@ let manageEvent = function(ovr, event) {
|
|||
eventQueue.unshift(event);
|
||||
|
||||
if (!callInProgress)
|
||||
showMessage(ovr, event);
|
||||
showMessage(event);
|
||||
break;
|
||||
}
|
||||
case "remove":
|
||||
if (eventQueue.length == 0 && !callInProgress)
|
||||
next(ovr);
|
||||
next();
|
||||
|
||||
if (!callInProgress && eventQueue[0] !== undefined && eventQueue[0].id == event.id)
|
||||
next(ovr);
|
||||
else
|
||||
next();
|
||||
else
|
||||
eventQueue = [];
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let roundedRect = function(ovr, x,y,w,h,filled){
|
||||
const roundedRect = function(x,y,w,h,filled){
|
||||
var poly = [
|
||||
x,y+4,
|
||||
x+4,y,
|
||||
|
@ -94,115 +114,153 @@ let roundedRect = function(ovr, x,y,w,h,filled){
|
|||
x,y+h-5,
|
||||
x,y+4
|
||||
];
|
||||
if (filled){
|
||||
let c = ovr.getColor();
|
||||
ovr.setColor(ovr.getBgColor());
|
||||
ovr.fillPoly(poly,true);
|
||||
ovr.setColor(c);
|
||||
}
|
||||
ovr.drawPoly(poly,true);
|
||||
if (filled) ovr.fillPoly(poly,true);
|
||||
};
|
||||
|
||||
let drawScreen = function(ovr, title, titleFont, src, iconcolor, icon){
|
||||
ovr.setBgColor(ovr.theme.bg2);
|
||||
ovr.clearRect(2,2,ovr.getWidth()-3,37);
|
||||
const DIVIDER = 38;
|
||||
|
||||
const drawScreen = function(title, src, iconcolor, icon){
|
||||
setColors(false);
|
||||
|
||||
drawBorder();
|
||||
|
||||
setColors(true);
|
||||
ovr.clearRect(2,2,ovr.getWidth()-3, DIVIDER - 1);
|
||||
|
||||
ovr.setColor(ovr.theme.fg2);
|
||||
ovr.setFont(settings.fontSmall);
|
||||
ovr.setFontAlign(0,-1);
|
||||
|
||||
let textCenter = (ovr.getWidth()+35-26)/2;
|
||||
const textCenter = (ovr.getWidth()+34-24)/2-1;
|
||||
|
||||
if (src) {
|
||||
let shortened = src;
|
||||
while (ovr.stringWidth(shortened) > ovr.getWidth()-80) shortened = shortened.substring(0,shortened.length-2);
|
||||
if (shortened.length != src.length) shortened += "...";
|
||||
ovr.drawString(shortened, textCenter, 2);
|
||||
const w = ovr.getWidth() - 35 - 26;
|
||||
|
||||
if (title)
|
||||
drawTitle(title, textCenter, w, 8, DIVIDER - 8, 0);
|
||||
|
||||
if (src)
|
||||
drawSource(src, textCenter, w, 2, -1);
|
||||
|
||||
if (ovr.getBPP() > 1) {
|
||||
let old = ovr.getBgColor();
|
||||
ovr.setBgColor("#888");
|
||||
roundedRect(4, 5, 30, 30,true);
|
||||
ovr.setBgColor(old);
|
||||
old = ovr.getColor();
|
||||
ovr.setColor(iconcolor);
|
||||
ovr.drawImage(icon,7,8);
|
||||
ovr.setColor(old);
|
||||
} else {
|
||||
roundedRect(4, 5, 30, 30,true);
|
||||
ovr.drawImage(icon,7,8);
|
||||
}
|
||||
|
||||
roundedRect(ovr.getWidth()-26,5,22,30,true);
|
||||
ovr.setFontAlign(0,0);
|
||||
ovr.setFont(titleFont);
|
||||
if (title) ovr.drawString(title, textCenter, 38/2 + 5);
|
||||
|
||||
ovr.setColor(ovr.theme.fg2);
|
||||
|
||||
ovr.setFont(settings.fontMedium);
|
||||
roundedRect(ovr, ovr.getWidth()-26,5,22,30,false);
|
||||
ovr.setFont("Vector:16");
|
||||
ovr.drawString("X",ovr.getWidth()-14,21);
|
||||
|
||||
ovr.setColor("#888");
|
||||
roundedRect(ovr, 5,5,30,30,true);
|
||||
ovr.setColor(ovr.getBPP() != 1 ? iconcolor : ovr.theme.bg2);
|
||||
ovr.drawImage(icon,8,8);
|
||||
ovr.drawString("X",ovr.getWidth()-14,20);
|
||||
};
|
||||
|
||||
let showMessage = function(ovr, msg) {
|
||||
LOG("showMessage");
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
const drawSource = function(src, center, w, y, align) {
|
||||
ovr.setFont(settings.fontSmall);
|
||||
while (ovr.stringWidth(src) > w) src = src.substring(0,src.length-2);
|
||||
if (src.length != src.length) src += "...";
|
||||
ovr.setFontAlign(0,align);
|
||||
ovr.drawString(src, center, y);
|
||||
};
|
||||
|
||||
if (typeof msg.CanscrollDown === "undefined")
|
||||
msg.CanscrollDown = false;
|
||||
if (typeof msg.CanscrollUp === "undefined")
|
||||
msg.CanscrollUp = false;
|
||||
const drawTitle = function(title, center, w, y, h) {
|
||||
let size = 30;
|
||||
|
||||
// Normal text message display
|
||||
let title = msg.title,
|
||||
titleFont = settings.fontLarge,
|
||||
lines;
|
||||
if (title) {
|
||||
let w = ovr.getWidth() - 35 - 26;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w)
|
||||
titleFont = settings.fontMedium;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w) {
|
||||
lines = ovr.wrapString(title, w);
|
||||
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
|
||||
while (ovr.setFont("Vector:" + size).stringWidth(title) > w){
|
||||
size -= 2;
|
||||
if (size < 14){
|
||||
ovr.setFont(settings.fontMedium);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
let dh;
|
||||
let a;
|
||||
if (ovr.stringWidth(title) > w) {
|
||||
let ws = ovr.wrapString(title, w);
|
||||
if (ws.length >= 2 && ovr.stringWidth(ws[1]) > w - 8){
|
||||
ws[1] = ws[1].substring(0, ws[1].length - 2);
|
||||
ws[1] += "...";
|
||||
}
|
||||
title = ws.slice(0, 2).join("\n");
|
||||
|
||||
a = -1;
|
||||
dh = y + 2;
|
||||
} else {
|
||||
a = 0;
|
||||
dh = y + h/2;
|
||||
}
|
||||
ovr.setFontAlign(0, a);
|
||||
ovr.drawString(title, center, dh);
|
||||
};
|
||||
|
||||
const setColors = function(lockRelevant) {
|
||||
if (lockRelevant && !Bangle.isLocked()){
|
||||
ovr.setColor(ovr.theme.fg2);
|
||||
ovr.setBgColor(ovr.theme.bg2);
|
||||
} else {
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
}
|
||||
};
|
||||
|
||||
const showMessage = function(msg) {
|
||||
LOG("showMessage");
|
||||
|
||||
ovr.setClipRect(0,0,ovr.getWidth(),ovr.getHeight());
|
||||
|
||||
drawScreen(msg.title, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
|
||||
|
||||
if (!Bangle.isLocked()){
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
}
|
||||
|
||||
drawMessage(msg);
|
||||
|
||||
if (!isQuiet() && msg.new) {
|
||||
msg.new = false;
|
||||
Bangle.buzz();
|
||||
if (!buzzing){
|
||||
buzzing = true;
|
||||
Bangle.buzz().then(()=>{setTimeout(()=>{buzzing = false;},2000);});
|
||||
}
|
||||
Bangle.setLCDPower(1);
|
||||
}
|
||||
|
||||
drawMessage(ovr, msg);
|
||||
};
|
||||
|
||||
let drawBorder = function(img) {
|
||||
const drawBorder = function() {
|
||||
LOG("drawBorder", isQuiet());
|
||||
if (img) ovr=img;
|
||||
if (Bangle.isLocked())
|
||||
ovr.setColor(ovr.theme.fgH);
|
||||
else
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.drawRect(0,0,ovr.getWidth()-1,ovr.getHeight()-1);
|
||||
ovr.drawRect(1,1,ovr.getWidth()-2,ovr.getHeight()-2);
|
||||
show(ovr);
|
||||
ovr.drawRect(2,DIVIDER,ovr.getWidth()-2,DIVIDER+1);
|
||||
show();
|
||||
};
|
||||
|
||||
let showCall = function(ovr, msg) {
|
||||
const showCall = function(msg) {
|
||||
LOG("showCall");
|
||||
LOG(msg);
|
||||
|
||||
if (msg.t == "remove") {
|
||||
LOG("hide call screen");
|
||||
next(ovr); //dont shift
|
||||
next(); //dont shift
|
||||
return;
|
||||
}
|
||||
|
||||
callInProgress = true;
|
||||
|
||||
let title = msg.title,
|
||||
titleFont = settings.fontLarge,
|
||||
lines;
|
||||
if (title) {
|
||||
let w = ovr.getWidth() - 35 -26;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w)
|
||||
titleFont = settings.fontMedium;
|
||||
if (ovr.setFont(titleFont).stringWidth(title) > w) {
|
||||
lines = ovr.wrapString(title, w);
|
||||
title = (lines.length > 2) ? lines.slice(0, 2).join("\n") + "..." : lines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
drawScreen(ovr, title, titleFont, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
drawScreen(msg.title, msg.src || /*LANG*/ "Message", require("messageicons").getColor(msg), require("messageicons").getImage(msg));
|
||||
|
||||
stopCallBuzz();
|
||||
if (!isQuiet()) {
|
||||
|
@ -216,10 +274,10 @@ let showCall = function(ovr, msg) {
|
|||
Bangle.buzz(500);
|
||||
}
|
||||
}
|
||||
drawMessage(ovr, msg);
|
||||
drawMessage(msg);
|
||||
};
|
||||
|
||||
let next = function(ovr) {
|
||||
const next = function() {
|
||||
LOG("next");
|
||||
stopCallBuzz();
|
||||
|
||||
|
@ -230,203 +288,378 @@ let next = function(ovr) {
|
|||
if (eventQueue.length == 0) {
|
||||
LOG("no element in queue - closing");
|
||||
cleanup();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
showMessage(ovr, eventQueue[0]);
|
||||
showMessage(eventQueue[0]);
|
||||
return true;
|
||||
};
|
||||
|
||||
let callBuzzTimer = null;
|
||||
let stopCallBuzz = function() {
|
||||
const stopCallBuzz = function() {
|
||||
if (callBuzzTimer) {
|
||||
clearInterval(callBuzzTimer);
|
||||
callBuzzTimer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
let drawTriangleUp = function(ovr) {
|
||||
ovr.reset();
|
||||
ovr.fillPoly([ovr.getWidth()-9, 46,ovr.getWidth()-14, 56,ovr.getWidth()-4, 56]);
|
||||
const drawTriangleUp = function() {
|
||||
ovr.fillPoly([ovr.getWidth()-10, 46,ovr.getWidth()-15, 56,ovr.getWidth()-5, 56]);
|
||||
};
|
||||
|
||||
let drawTriangleDown = function(ovr) {
|
||||
ovr.reset();
|
||||
ovr.fillPoly([ovr.getWidth()-9, ovr.getHeight()-6, ovr.getWidth()-14, ovr.getHeight()-16, ovr.getWidth()-4, ovr.getHeight()-16]);
|
||||
const drawTriangleDown = function() {
|
||||
ovr.fillPoly([ovr.getWidth()-10, ovr.getHeight()-6, ovr.getWidth()-15, ovr.getHeight()-16, ovr.getWidth()-5, ovr.getHeight()-16]);
|
||||
};
|
||||
|
||||
let linesScroll = 6;
|
||||
|
||||
let scrollUp = function(ovr) {
|
||||
msg = eventQueue[0];
|
||||
const scrollUp = function() {
|
||||
const msg = eventQueue[0];
|
||||
LOG("up", msg);
|
||||
if (typeof msg.FirstLine === "undefined")
|
||||
msg.FirstLine = 0;
|
||||
if (typeof msg.CanscrollUp === "undefined")
|
||||
msg.CanscrollUp = false;
|
||||
|
||||
if (!msg.CanscrollUp) return;
|
||||
|
||||
msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - linesScroll : 0;
|
||||
|
||||
drawMessage(ovr, msg);
|
||||
msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - 1 : 0;
|
||||
drawMessage(msg);
|
||||
};
|
||||
|
||||
let scrollDown = function(ovr) {
|
||||
msg = eventQueue[0];
|
||||
const scrollDown = function() {
|
||||
const msg = eventQueue[0];
|
||||
LOG("down", msg);
|
||||
if (typeof msg.FirstLine === "undefined")
|
||||
msg.FirstLine = 0;
|
||||
if (typeof msg.CanscrollDown === "undefined")
|
||||
msg.CanscrollDown = false;
|
||||
|
||||
if (!msg.CanscrollDown) return;
|
||||
|
||||
msg.FirstLine = msg.FirstLine + linesScroll;
|
||||
drawMessage(ovr, msg);
|
||||
msg.FirstLine = msg.FirstLine + 1;
|
||||
drawMessage(msg);
|
||||
};
|
||||
|
||||
let drawMessage = function(ovr, msg) {
|
||||
let MyWrapString = function(str, maxWidth) {
|
||||
const drawMessage = function(msg) {
|
||||
setColors(false);
|
||||
const getStringHeight = function(str){
|
||||
"jit";
|
||||
const metrics = ovr.stringMetrics(str);
|
||||
if (needsWorkaround === undefined)
|
||||
needsWorkaround = isNewer("2v21.13", process.version);
|
||||
if (needsWorkaround && metrics.maxImageHeight > 16)
|
||||
metrics.maxImageHeight = metrics.height;
|
||||
return Math.max(metrics.height, metrics.maxImageHeight);
|
||||
};
|
||||
|
||||
const wrapString = function(str, maxWidth) {
|
||||
str = str.replace("\r\n", "\n").replace("\r", "\n");
|
||||
return ovr.wrapString(str, maxWidth);
|
||||
};
|
||||
const wrappedStringHeight = function(strArray){
|
||||
let r = 0;
|
||||
strArray.forEach((line, i) => {
|
||||
r += getStringHeight(line);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
if (typeof msg.FirstLine === "undefined") msg.FirstLine = 0;
|
||||
if (msg.FirstLine === undefined) msg.FirstLine = 0;
|
||||
|
||||
let bodyFont = typeof msg.bodyFont === "undefined" ? settings.fontMedium : msg.bodyFont;
|
||||
let Padding = 3;
|
||||
if (typeof msg.lines === "undefined") {
|
||||
const padding = eventQueue.length > 1 ? (eventQueue.length > 3 ? 7 : 5) : 3;
|
||||
|
||||
const yText = DIVIDER+2;
|
||||
let yLine = yText + 4;
|
||||
|
||||
ovr.setClipRect(2, yText, ovr.getWidth() - 3, ovr.getHeight() - 3);
|
||||
|
||||
const maxTextHeight = ovr.getHeight() - yLine - padding + 2;
|
||||
|
||||
if (!msg.lines) {
|
||||
let bodyFont = settings.fontBig;
|
||||
ovr.setFont(bodyFont);
|
||||
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
|
||||
if (msg.lines.length <= 2) {
|
||||
bodyFont = ovr.getFonts().includes("Vector") ? "Vector:20" : "6x8:3";
|
||||
msg.lines = wrapString(msg.body, ovr.getWidth() - 4 - padding);
|
||||
|
||||
if (wrappedStringHeight(msg.lines) > maxTextHeight) {
|
||||
bodyFont = settings.fontMedium;
|
||||
ovr.setFont(bodyFont);
|
||||
msg.lines = MyWrapString(msg.body, ovr.getWidth() - (Padding * 2));
|
||||
msg.bodyFont = bodyFont;
|
||||
msg.lines = wrapString(msg.body, ovr.getWidth() - 4 - padding);
|
||||
}
|
||||
msg.bodyFont = bodyFont;
|
||||
msg.lineHeights = [];
|
||||
msg.lines.forEach((line, i) => {
|
||||
msg.lineHeights[i] = getStringHeight(line);
|
||||
});
|
||||
}
|
||||
|
||||
let NumLines = 7;
|
||||
LOG("Prepared message", msg);
|
||||
|
||||
let linesToPrint = (msg.lines.length > NumLines) ? msg.lines.slice(msg.FirstLine, msg.FirstLine + NumLines) : msg.lines;
|
||||
ovr.setFont(msg.bodyFont);
|
||||
ovr.clearRect(2, yText, ovr.getWidth()-3, ovr.getHeight()-3);
|
||||
|
||||
let yText = 40;
|
||||
let xText = 4;
|
||||
|
||||
ovr.setBgColor(ovr.theme.bg);
|
||||
ovr.setColor(ovr.theme.fg);
|
||||
ovr.clearRect(2, yText, ovrw-3, ovrh-3);
|
||||
let xText = Padding;
|
||||
yText += Padding;
|
||||
ovr.setFont(bodyFont);
|
||||
let HText = ovr.getFontHeight();
|
||||
|
||||
yText = ((ovrh - yText) / 2) - (linesToPrint.length * HText / 2) + yText;
|
||||
|
||||
if (linesToPrint.length <= 3) {
|
||||
if (msg.bodyFont == settings.fontBig) {
|
||||
ovr.setFontAlign(0, -1);
|
||||
xText = ovr.getWidth() / 2;
|
||||
} else
|
||||
xText = Math.round(ovr.getWidth() / 2 - (padding - 3) / 2) + 1;
|
||||
yLine = (ovr.getHeight() + yLine) / 2 - (wrappedStringHeight(msg.lines) / 2);
|
||||
ovr.drawString(msg.lines.join("\n"), xText, yLine);
|
||||
} else {
|
||||
ovr.setFontAlign(-1, -1);
|
||||
}
|
||||
|
||||
let currentLine = msg.FirstLine;
|
||||
|
||||
linesToPrint.forEach((line, i) => {
|
||||
ovr.drawString(line, xText, yText + HText * i);
|
||||
});
|
||||
let drawnHeight = 0;
|
||||
|
||||
while(drawnHeight < maxTextHeight && msg.lines.length > currentLine) {
|
||||
const lineHeight = msg.lineHeights[currentLine];
|
||||
ovr.drawString(msg.lines[currentLine], xText, yLine + drawnHeight);
|
||||
drawnHeight += lineHeight;
|
||||
currentLine++;
|
||||
}
|
||||
|
||||
if (eventQueue.length > 1){
|
||||
ovr.drawLine(ovr.getWidth()-4,ovr.getHeight()/2,ovr.getWidth()-4,ovr.getHeight()-4);
|
||||
ovr.drawLine(ovr.getWidth()/2,ovr.getHeight()-4,ovr.getWidth()-4,ovr.getHeight()-4);
|
||||
}
|
||||
if (eventQueue.length > 3){
|
||||
ovr.drawLine(ovr.getWidth()-6,ovr.getHeight()*0.6,ovr.getWidth()-6,ovr.getHeight()-6);
|
||||
ovr.drawLine(ovr.getWidth()*0.6,ovr.getHeight()-6,ovr.getWidth()-6,ovr.getHeight()-6);
|
||||
}
|
||||
|
||||
if (msg.FirstLine != 0) {
|
||||
msg.CanscrollUp = true;
|
||||
drawTriangleUp(ovr);
|
||||
drawTriangleUp();
|
||||
} else
|
||||
msg.CanscrollUp = false;
|
||||
|
||||
if (msg.FirstLine + linesToPrint.length < msg.lines.length) {
|
||||
if (currentLine < msg.lines.length) {
|
||||
msg.CanscrollDown = true;
|
||||
drawTriangleDown(ovr);
|
||||
drawTriangleDown();
|
||||
} else
|
||||
msg.CanscrollDown = false;
|
||||
show(ovr);
|
||||
if (!isQuiet()) Bangle.setLCDPower(1);
|
||||
|
||||
show();
|
||||
};
|
||||
|
||||
let getSwipeHandler = function(ovr){
|
||||
return (lr, ud) => {
|
||||
if (ud == 1) {
|
||||
scrollUp(ovr);
|
||||
} else if (ud == -1){
|
||||
scrollDown(ovr);
|
||||
const getDragHandler = function(){
|
||||
return (e) => {
|
||||
if (e.dy > 0) {
|
||||
scrollUp();
|
||||
} else if (e.dy < 0){
|
||||
scrollDown();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let getTouchHandler = function(ovr){
|
||||
const getTouchHandler = function(){
|
||||
return (_, xy) => {
|
||||
if (xy.y < ovry + 40){
|
||||
next(ovr);
|
||||
if (xy.y < ovry + DIVIDER){
|
||||
next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let restoreHandler = function(event){
|
||||
LOG("Restore", event, backup[event]);
|
||||
Bangle.removeAllListeners(event);
|
||||
Bangle["#on" + event]=backup[event];
|
||||
backup[event] = undefined;
|
||||
const EVENTS=["touch", "drag", "swipe"];
|
||||
|
||||
let hasBackup = false;
|
||||
|
||||
const origOn = Bangle.on;
|
||||
const backupOn = function(event, handler){
|
||||
if (EVENTS.includes(event)){
|
||||
if (!backup[event])
|
||||
backup[event] = [];
|
||||
backup[event].push(handler);
|
||||
}
|
||||
else origOn.call(Bangle, event, handler);
|
||||
};
|
||||
|
||||
let backupHandler = function(event){
|
||||
if (backupDone) return; // do not backup, overlay is already up
|
||||
backup[event] = Bangle["#on" + event];
|
||||
LOG("Backed up", backup[event]);
|
||||
Bangle.removeAllListeners(event);
|
||||
const origClearWatch = clearWatch;
|
||||
const backupClearWatch = function(w) {
|
||||
if (w)
|
||||
backup.watches.filter((e)=>e.index != w);
|
||||
else
|
||||
backup.watches = [];
|
||||
};
|
||||
|
||||
let cleanup = function(){
|
||||
const origSetWatch = setWatch;
|
||||
const backupSetWatch = function(){
|
||||
if (!backup.watches)
|
||||
backup.watches = [];
|
||||
LOG("backup for watch", arguments);
|
||||
let i = backup.watches.map((e)=>e.index).sort().pop() + 1;
|
||||
backup.watches.push({index:i, args:arguments});
|
||||
return i;
|
||||
};
|
||||
|
||||
const origRemove = Bangle.removeListener;
|
||||
const backupRemove = function(event, handler){
|
||||
if (EVENTS.includes(event) && backup[event]){
|
||||
LOG("backup for " + event + ": " + backup[event]);
|
||||
backup[event] = backup[event].filter(e=>e!==handler);
|
||||
}
|
||||
else origRemove.call(Bangle, event, handler);
|
||||
};
|
||||
|
||||
const origRemoveAll = Bangle.removeAllListeners;
|
||||
const backupRemoveAll = function(event){
|
||||
if (backup[event])
|
||||
backup[event] = undefined;
|
||||
origRemoveAll.call(Bangle);
|
||||
};
|
||||
|
||||
const restoreHandlers = function(){
|
||||
if (!hasBackup){
|
||||
LOG("No backup available");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const event of EVENTS){
|
||||
LOG("Restore", backup[event]);
|
||||
origRemoveAll.call(Bangle, event);
|
||||
if (backup[event] && backup[event].length == 1)
|
||||
backup[event] = backup[event][0];
|
||||
Bangle["#on" + event]=backup[event];
|
||||
backup[event] = undefined;
|
||||
}
|
||||
|
||||
if (backup.watches){
|
||||
let toRemove = [];
|
||||
|
||||
origClearWatch.call(global);
|
||||
|
||||
for(let i = 0; i < backup.watches.length; i++){
|
||||
let w = backup.watches[i];
|
||||
LOG("Restoring watch", w);
|
||||
if (w) {
|
||||
origSetWatch.apply(global, w);
|
||||
} else {
|
||||
toRemove.push(i+1);
|
||||
origSetWatch.call(global, ()=>{}, BTN);
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Remove watches", toRemove, global["\xff"].watches);
|
||||
for(let c of toRemove){
|
||||
origClearWatch.call(global, c);
|
||||
}
|
||||
}
|
||||
|
||||
global.setWatch = origSetWatch;
|
||||
global.clearWatch = origClearWatch;
|
||||
Bangle.on = origOn;
|
||||
Bangle.removeListener = origRemove;
|
||||
Bangle.removeAllListeners = origRemoveAll;
|
||||
|
||||
hasBackup = false;
|
||||
};
|
||||
|
||||
const backupHandlers = function(){
|
||||
if (hasBackup){
|
||||
LOG("Backup already exists");
|
||||
return false; // do not backup, overlay is already up
|
||||
}
|
||||
|
||||
for (const event of EVENTS){
|
||||
backup[event] = Bangle["#on" + event];
|
||||
if (typeof backup[event] == "function")
|
||||
backup[event] = [ backup[event] ];
|
||||
LOG("Backed up", backup[event], event);
|
||||
Bangle.removeAllListeners(event);
|
||||
}
|
||||
|
||||
backup.watches = [];
|
||||
|
||||
for (let i = 1; i < global["\xff"].watches.length; i++){
|
||||
let w = global["\xff"].watches[i];
|
||||
LOG("Transform watch", w);
|
||||
if (w) {
|
||||
w = [
|
||||
w.callback,
|
||||
w.pin,
|
||||
w
|
||||
];
|
||||
delete w[2].callback;
|
||||
delete w[2].pin;
|
||||
w[2].debounce = Math.round(w[2].debounce / 1048.576);
|
||||
} else {
|
||||
w = null;
|
||||
}
|
||||
LOG("Transformed to", w);
|
||||
backup.watches.push(w);
|
||||
}
|
||||
|
||||
LOG("Backed up watches", backup.watches);
|
||||
clearWatch();
|
||||
|
||||
global.setWatch = backupSetWatch;
|
||||
global.clearWatch = backupClearWatch;
|
||||
Bangle.on = backupOn;
|
||||
Bangle.removeListener = backupRemove;
|
||||
Bangle.removeAllListeners = backupRemoveAll;
|
||||
|
||||
hasBackup = true;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const cleanup = function(){
|
||||
if (lockListener) {
|
||||
Bangle.removeListener("lock", lockListener);
|
||||
lockListener = undefined;
|
||||
}
|
||||
restoreHandler("touch");
|
||||
restoreHandler("swipe");
|
||||
restoreHandler("drag");
|
||||
restoreHandlers();
|
||||
|
||||
Bangle.setLCDOverlay();
|
||||
backupDone = false;
|
||||
ovr = undefined;
|
||||
quiet = undefined;
|
||||
};
|
||||
|
||||
let backup = {};
|
||||
const backup = {};
|
||||
|
||||
let backupDone = false;
|
||||
|
||||
let main = function(ovr, event) {
|
||||
LOG("Main", event, settings);
|
||||
const main = function(event) {
|
||||
LOG("Main", event.t);
|
||||
const didBackup = backupHandlers();
|
||||
|
||||
if (!lockListener) {
|
||||
lockListener = function (){
|
||||
drawBorder();
|
||||
lockListener = function (e){
|
||||
updateClearingTimeout();
|
||||
showMessage(eventQueue[0]);
|
||||
};
|
||||
Bangle.on('lock', lockListener);
|
||||
LOG("Add overlay lock handlers");
|
||||
origOn.call(Bangle, 'lock', lockListener);
|
||||
}
|
||||
backupHandler("touch");
|
||||
backupHandler("swipe");
|
||||
backupHandler("drag");
|
||||
if (!backupDone){
|
||||
Bangle.on('touch', getTouchHandler(ovr));
|
||||
Bangle.on('swipe', getSwipeHandler(ovr));
|
||||
|
||||
if (didBackup){
|
||||
LOG("Add overlay UI handlers");
|
||||
origOn.call(Bangle, 'touch', getTouchHandler(ovr));
|
||||
origOn.call(Bangle, 'drag', getDragHandler(ovr));
|
||||
}
|
||||
backupDone=true;
|
||||
|
||||
if (event !== undefined){
|
||||
drawBorder(ovr);
|
||||
manageEvent(ovr, event);
|
||||
manageEvent(event);
|
||||
} else {
|
||||
LOG("No event given");
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
let ovr;
|
||||
const updateClearingTimeout = ()=>{
|
||||
LOG("updateClearingTimeout");
|
||||
if (settings.autoclear <= 0)
|
||||
return;
|
||||
LOG("Remove clearing timeout", clearingTimeout);
|
||||
if (clearingTimeout) clearTimeout(clearingTimeout);
|
||||
if (Bangle.isLocked()){
|
||||
LOG("Set new clearing timeout");
|
||||
clearingTimeout = setTimeout(()=>{
|
||||
LOG("setNewTimeout");
|
||||
const event = eventQueue.pop();
|
||||
if (event)
|
||||
showMessage(event);
|
||||
if (eventQueue.length > 0){
|
||||
LOG("still got elements");
|
||||
updateClearingTimeout();
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
}, settings.autoclear * 1000);
|
||||
} else {
|
||||
clearingTimeout = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
exports.message = function(type, event) {
|
||||
LOG("Got message", type, event);
|
||||
|
@ -434,13 +667,40 @@ exports.message = function(type, event) {
|
|||
if(!(type=="text" || type == "call")) return;
|
||||
if(type=="text" && event.id == "nav") return;
|
||||
if(event.handled) return;
|
||||
if(event.messagesoverlayignore) return;
|
||||
|
||||
bpp = 4;
|
||||
if (process.memory().free < LOW_MEM)
|
||||
let free = process.memory().free;
|
||||
let bpp = settings.systemTheme ? 16 : 4;
|
||||
|
||||
let estimatedMemUse = bpp == 16 ? 4096 : (bpp == 4 ? 1536 : 768);
|
||||
// reduce estimation if ovr already exists and uses memory;
|
||||
if (ovr)
|
||||
estimatedMemUse -= ovr.getBPP() == 16 ? 4096 : (ovr.getBPP() == 4 ? 1536 : 768);
|
||||
|
||||
if (process.memory().free - estimatedMemUse < settings.minfreemem * 1024) {
|
||||
// we are going to be under our minfreemem setting if we proceed
|
||||
bpp = 1;
|
||||
if (ovr && ovr.getBPP() > 1){
|
||||
// can reduce memory by going 1 bit
|
||||
let saves = ovr.getBPP() == 16 ? 4096 - 768 : 768;
|
||||
estimatedMemUse -= saves;
|
||||
LOG("Go to 1 bit, saving", saves);
|
||||
} else {
|
||||
estimatedMemUse = 768;
|
||||
}
|
||||
}
|
||||
|
||||
while (process.memory().free < MIN_FREE_MEM && eventQueue.length > 0){
|
||||
let dropped = eventQueue.pop();
|
||||
|
||||
if (E.getSizeOf){
|
||||
let e = E.getSizeOf(eventQueue);
|
||||
estimatedMemUse += e;
|
||||
LOG("EventQueue has", e, "blocks");
|
||||
}
|
||||
|
||||
LOG("Free ", free, "estimated use", estimatedMemUse, "for", bpp, "BPP");
|
||||
|
||||
while (process.memory().free - estimatedMemUse < settings.minfreemem * 1024 && eventQueue.length > 0){
|
||||
const dropped = eventQueue.pop();
|
||||
print("Dropped message because of memory constraints", dropped);
|
||||
}
|
||||
|
||||
|
@ -448,19 +708,37 @@ exports.message = function(type, event) {
|
|||
ovr = Graphics.createArrayBuffer(ovrw, ovrh, bpp, {
|
||||
msb: true
|
||||
});
|
||||
} else {
|
||||
ovr.clear();
|
||||
if(E.getSizeOf)
|
||||
LOG("New overlay uses", E.getSizeOf(ovr), "blocks");
|
||||
}
|
||||
|
||||
g = ovr;
|
||||
ovr.reset();
|
||||
|
||||
if (bpp == 4)
|
||||
ovr.theme = g.theme;
|
||||
else
|
||||
ovr.theme = { fg:0, bg:1, fg2:1, bg2:0, fgH:1, bgH:0 };
|
||||
if (bpp > 1){
|
||||
if (settings.systemTheme){
|
||||
ovr.theme = g.theme;
|
||||
} else {
|
||||
ovr.theme = {
|
||||
fg: g.theme.dark ? 15: 0,
|
||||
bg: g.theme.dark ? 0: 15,
|
||||
fg2: g.theme.dark ? 15: 0,
|
||||
bg2: g.theme.dark ? 9 : 8,
|
||||
fgH: g.theme.dark ? 15 : 0,
|
||||
bgH: g.theme.dark ? 9: 8,
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (g.theme.dark)
|
||||
ovr.theme = { fg:1, bg:0, fg2:0, bg2:1, fgH:0, bgH:1 };
|
||||
else
|
||||
ovr.theme = { fg:0, bg:1, fg2:1, bg2:0, fgH:1, bgH:0 };
|
||||
}
|
||||
|
||||
main(event);
|
||||
|
||||
updateClearingTimeout();
|
||||
|
||||
main(ovr, event);
|
||||
if (!isQuiet()) Bangle.setLCDPower(1);
|
||||
event.handled = true;
|
||||
g = _g;
|
||||
g.flip();
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messagesoverlay",
|
||||
"name": "Messages Overlay",
|
||||
"version": "0.06",
|
||||
"version": "0.08",
|
||||
"description": "An overlay based implementation of a messages UI (display notifications from iOS and Gadgetbridge/Android)",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
@ -11,7 +11,10 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"messagesoverlay","url":"lib.js"},
|
||||
{"name":"messagesoverlay.0.boot.js","url":"boot.js"}
|
||||
{"name":"messagesoverlay.0.boot.js","url":"boot.js"},
|
||||
{"name":"messagesoverlay.settings.js","url":"settings.js"},
|
||||
{"name":"messagesoverlay.default.json","url":"default.json"}
|
||||
],
|
||||
"data": [{"name":"bthrm.json"}],
|
||||
"screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
(function(back) {
|
||||
function writeSettings(key, value) {
|
||||
var s = require('Storage').readJSON(FILE, true) || {};
|
||||
s[key] = value;
|
||||
require('Storage').writeJSON(FILE, s);
|
||||
readSettings();
|
||||
}
|
||||
|
||||
function readSettings(){
|
||||
settings = Object.assign(
|
||||
require('Storage').readJSON("messagesoverlay.default.json", true) || {},
|
||||
require('Storage').readJSON(FILE, true) || {}
|
||||
);
|
||||
}
|
||||
|
||||
var FILE="messagesoverlay.json";
|
||||
var settings;
|
||||
readSettings();
|
||||
|
||||
function buildMainMenu(){
|
||||
var mainmenu = {
|
||||
'' : { title: "Messages Overlay"},
|
||||
'< Back': back,
|
||||
'Border': {
|
||||
value: settings.border,
|
||||
min: 0,
|
||||
max: Math.floor(g.getWidth()/2-50),
|
||||
step: 1,
|
||||
format: v=>v + "px",
|
||||
onchange: v => {
|
||||
writeSettings("border",v);
|
||||
}
|
||||
},
|
||||
'Autoclear after': {
|
||||
value: settings.autoclear,
|
||||
min: 0,
|
||||
max: 3600,
|
||||
step: 10,
|
||||
format: v=>v>0?v+"s":"Off",
|
||||
onchange: v => {
|
||||
writeSettings("autoclear",v);
|
||||
}
|
||||
},
|
||||
'Theme': {
|
||||
value: settings.systemTheme,
|
||||
format: v=>v?"System":"low RAM",
|
||||
onchange: v => {
|
||||
writeSettings("systemTheme",v);
|
||||
}
|
||||
},
|
||||
'Min. free RAM': {
|
||||
value: settings.minfreemem,
|
||||
min: 0,
|
||||
max: process.memory().total/1000,
|
||||
step: 1,
|
||||
format: v=>v + "k free",
|
||||
onchange: v => {
|
||||
writeSettings("minfreemem",v);
|
||||
}
|
||||
}
|
||||
};
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial release
|
||||
0.02: Cache the app-launch info
|
||||
0.03: Fix bugs that would make the launcher unusable on most watches
|
||||
|
|
|
@ -5,13 +5,13 @@ var font = g.getFonts().includes("6x15") ? "6x15" : "6x8:2";
|
|||
var largeFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:3";
|
||||
var currentApp = 0;
|
||||
var overscroll = 0;
|
||||
var blankImage = Graphics.createImage(` `);
|
||||
var blankImage = Graphics.createImage(`\n \n`);
|
||||
var rowHeight = g.getHeight()/3;
|
||||
|
||||
// Load apps list
|
||||
var apps;
|
||||
|
||||
var launchCache = s.readJSON("launch.cache.json", true)||{};
|
||||
var launchCache = Storage.readJSON("launch.cache.json", true)||{};
|
||||
var launchHash = require("Storage").hash(/\.info/);
|
||||
if (launchCache.hash==launchHash) {
|
||||
apps = launchCache.apps;
|
||||
|
@ -39,7 +39,7 @@ if (launchCache.hash==launchHash) {
|
|||
});
|
||||
|
||||
launchCache = { apps, hash: launchHash };
|
||||
s.writeJSON("launch.cache.json", launchCache);
|
||||
Storage.writeJSON("launch.cache.json", launchCache);
|
||||
}
|
||||
|
||||
// Uncomment for testing in the emulator without apps:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "ratchet_launch",
|
||||
"name": "Ratchet Launcher",
|
||||
"shortName": "Ratchet",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Launcher with discrete scrolling for quicker app selection",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
|
|
|
@ -52,3 +52,4 @@
|
|||
0.41: Fix exit from plots and graphs would easily react twice, going back two
|
||||
levels instead of one.
|
||||
0.42: Minor code improvements
|
||||
0.43: Fix interaction on clocks without widgets
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{
|
||||
name: "Toggle",
|
||||
get: () => {
|
||||
const w = WIDGETS && WIDGETS["recorder"];
|
||||
const w = typeof WIDGETS !== "undefined" && WIDGETS["recorder"];
|
||||
|
||||
return w && w.isRecording() ? {
|
||||
text: "Recording",
|
||||
|
@ -24,7 +24,7 @@
|
|||
};
|
||||
},
|
||||
run: () => {
|
||||
const w = WIDGETS && WIDGETS["recorder"];
|
||||
const w = typeof WIDGETS !== "undefined" && WIDGETS["recorder"];
|
||||
if(w){
|
||||
Bangle.buzz();
|
||||
w.setRecording(!w.isRecording(), { force: "append" });
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.42",
|
||||
"version": "0.43",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget,clkinfo",
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
0.50: Fixing lint warnings for unused vars
|
||||
0.60: Fixes typos, BTN1 to show launcher and show app icon
|
||||
0.61: Minor code improvements
|
||||
0.70: Better wrapping of the text base (dynamic instead of hardcoded)
|
||||
0.70: Better wrapping of the text base (dynamic instead of hardcoded)
|
||||
0.80: Add analog watch, steps and date
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "rellotge",
|
||||
"name": "Rellotge en catala",
|
||||
"shortName":"Rellotge",
|
||||
"version": "0.70",
|
||||
"version": "0.80",
|
||||
"description": "A clock with traditional naming of hours in Catalan",
|
||||
"icon": "icona.png",
|
||||
"readme": "README.md",
|
||||
|
|