diff --git a/android.html b/android.html new file mode 100644 index 000000000..93999008f --- /dev/null +++ b/android.html @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + Bangle.js App Loader + + + + + + +
+ +
+ + + + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 46efe37bf..8e2e2b0a6 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -217,7 +217,12 @@ function showEditRepeatMenu(repeat, dow, dowChangeCallback) { function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) { const menu = { "": { "title": /*LANG*/"Custom Days" }, - "< Back": () => dowChangeCallback(true, dow), + "< Back": () => { + // If the user unchecks all the days then we assume repeat = once + // and we force the dow to every day. + var repeat = dow > 0; + dowChangeCallback(repeat, repeat ? dow : EVERY_DAY) + } }; require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { @@ -315,12 +320,12 @@ function showAdvancedMenu() { function enableAll(on) { if (alarms.filter(e => e.on == !on).length == 0) { - E.showPrompt(on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", { - title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All", - buttons: { /*LANG*/"Ok": true } - }).then(() => showAdvancedMenu()); + E.showAlert( + on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable", + on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" + ).then(() => showAdvancedMenu()); } else { - E.showPrompt(/*LANG*/"Are you sure?", { title: on ? "/*LANG*/Enable All" : /*LANG*/"Disable All" }).then((confirm) => { + E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => { if (confirm) { alarms.forEach(alarm => alarm.on = on); saveAndReload(); @@ -334,7 +339,7 @@ function enableAll(on) { function deleteAll() { if (alarms.length == 0) { - E.showPrompt(/*LANG*/"Nothing to delete", { title: /*LANG*/"Delete All", buttons: { /*LANG*/"Ok": true } }).then(() => showAdvancedMenu()); + E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu()); } else { E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete All" diff --git a/apps/bowserWF/metadata.json b/apps/bowserWF/metadata.json index 22df2dea4..a0bdfb8e9 100644 --- a/apps/bowserWF/metadata.json +++ b/apps/bowserWF/metadata.json @@ -1,14 +1,18 @@ -{ "id": "bowserWF", +{ + "id": "bowserWF", "name": "Bowser Watchface", "shortName":"Bowser Watchface", - "version":"0.01", + "version":"0.02", "description": "Let bowser show you the time", "icon": "app.png", - "tags": "", - "supports" : ["BANGLEJS2"], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, "readme": "README.md", "storage": [ {"name":"bowserWF.app.js","url":"app.js"}, {"name":"bowserWF.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name":"bowserWF.json"}] } diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 41eec666a..7ca8319b6 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -21,3 +21,4 @@ Adds some preset modes and a custom one Restructure the settings menu 0.08: Allow scanning for devices in settings +0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655) diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md index 42ad619bd..8d5872670 100644 --- a/apps/bthrm/README.md +++ b/apps/bthrm/README.md @@ -2,7 +2,7 @@ When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. -HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor. This means it's compatible with many Bangle.js apps including: @@ -16,19 +16,23 @@ as that requires live sensor data (rather than just BPM readings). Just install the app, then install an app that uses the heart rate monitor. -Once installed it'll automatically try and connect to the first bluetooth -heart rate monitor it finds. +Once installed you will have to go into this app's settings while your heart rate monitor + is available for bluetooth pairing and scan for devices. **To disable this and return to normal HRM, uninstall the app** ## Compatible Heart Rate Monitors This works with any heart rate monitor providing the standard Bluetooth -Heart Rate Service (`180D`) and characteristic (`2A37`). +Heart Rate Service (`180D`) and characteristic (`2A37`). It additionally supports +the location (`2A38`) characteristic and the Battery Service (`180F`), reporting +that information in the `BTHRM` event when they are available. So far it has been tested on: * CooSpo Bluetooth Heart Rate Monitor +* Polar H10 +* Polar OH1 * Wahoo TICKR X 2 ## Internals @@ -38,7 +42,6 @@ This replaces `Bangle.setHRMPower` with its own implementation. ## TODO * A widget to show connection state? -* Specify a specific device by address? ## Creator diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 3a1f1cc4c..e9e640563 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -3,7 +3,7 @@ require('Storage').readJSON("bthrm.default.json", true) || {}, require('Storage').readJSON("bthrm.json", true) || {} ); - + var log = function(text, param){ if (settings.debuglog){ var logline = new Date().toISOString() + " - " + text; @@ -13,39 +13,38 @@ print(logline); } }; - + log("Settings: ", settings); - + if (settings.enabled){ - function clearCache(){ + var clearCache = function() { return require('Storage').erase("bthrm.cache.json"); - } + }; - function getCache(){ + var getCache = function() { var cache = require('Storage').readJSON("bthrm.cache.json", true) || {}; - if (settings.btname && settings.btname == cache.name) return cache; + if (settings.btid && settings.btid === cache.id) return cache; clearCache(); return {}; - } - - function addNotificationHandler(characteristic){ + }; + + var addNotificationHandler = function(characteristic) { log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler); - characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler); - } - - function writeCache(cache){ + characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value)); + }; + + var writeCache = function(cache) { var oldCache = getCache(); - if (oldCache != cache) { + if (oldCache !== cache) { log("Writing cache"); - require('Storage').writeJSON("bthrm.cache.json", cache) + require('Storage').writeJSON("bthrm.cache.json", cache); } else { log("No changes, don't write cache"); } - - } + }; - function characteristicsToCache(characteristics){ + var characteristicsToCache = function(characteristics) { log("Cache characteristics"); var cache = getCache(); if (!cache.characteristics) cache.characteristics = {}; @@ -60,9 +59,9 @@ }; } writeCache(cache); - } + }; - function characteristicsFromCache(){ + var characteristicsFromCache = function() { log("Read cached characteristics"); var cache = getCache(); if (!cache.characteristics) return []; @@ -81,38 +80,34 @@ restored.push(r); } return restored; - } + }; log("Start"); var lastReceivedData={ }; - var serviceFilters = [{ - services: [ "180d" ] - }]; - - supportedServices = [ - "0x180d", "0x180f" + var supportedServices = [ + "0x180d", // Heart Rate + "0x180f", // Battery ]; var supportedCharacteristics = { "0x2a37": { //Heart rate measurement - handler: function (event){ - var dv = event.target.value; + handler: function (dv){ var flags = dv.getUint8(0); - + var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit - + var sensorContact; - + if (flags & 2){ - sensorContact = (flags & 4) ? true : false; + sensorContact = !!(flags & 4); } - + var idx = 2 + (flags&1); - + var energyExpended; if (flags & 8){ energyExpended = dv.getUint16(idx,1); @@ -121,11 +116,11 @@ var interval; if (flags & 16) { interval = []; - maxIntervalBytes = (dv.byteLength - idx); + var maxIntervalBytes = (dv.byteLength - idx); log("Found " + (maxIntervalBytes / 2) + " rr data fields"); for(var i = 0 ; i < maxIntervalBytes / 2; i++){ interval[i] = dv.getUint16(idx,1); // in milliseconds - idx += 2 + idx += 2; } } @@ -140,45 +135,44 @@ } if (settings.replace){ - var newEvent = { + var repEvent = { bpm: bpm, confidence: (sensorContact || sensorContact === undefined)? 100 : 0, src: "bthrm" }; - - log("Emitting HRM: ", newEvent); - Bangle.emit("HRM", newEvent); + + log("Emitting HRM: ", repEvent); + Bangle.emit("HRM", repEvent); } var newEvent = { bpm: bpm }; - + if (location) newEvent.location = location; if (interval) newEvent.rr = interval; if (energyExpended) newEvent.energy = energyExpended; if (battery) newEvent.battery = battery; if (sensorContact) newEvent.contact = sensorContact; - + log("Emitting BTHRM: ", newEvent); Bangle.emit("BTHRM", newEvent); } }, "0x2a38": { //Body sensor location - handler: function(data){ + handler: function(dv){ if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {}; - if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value; + lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10); } }, "0x2a19": { //Battery - handler: function (event){ + handler: function (dv){ if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {}; - if (!lastReceivedData["0x180f"]["0x2a19"]) lastReceivedData["0x180f"]["0x2a19"] = event.target.value.getUint8(0); + lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0); } } - }; var device; @@ -195,7 +189,7 @@ maxInterval: 1500 }; - function waitingPromise(timeout) { + var waitingPromise = function(timeout) { return new Promise(function(resolve){ log("Start waiting for " + timeout); setTimeout(()=>{ @@ -203,7 +197,7 @@ resolve(); }, timeout); }); - } + }; if (settings.enabled){ Bangle.isBTHRMOn = function(){ @@ -215,7 +209,6 @@ }; } - if (settings.replace){ var origIsHRMOn = Bangle.isHRMOn; @@ -229,15 +222,15 @@ }; } - function clearRetryTimeout(){ + var clearRetryTimeout = function() { if (currentRetryTimeout){ log("Clearing timeout " + currentRetryTimeout); clearTimeout(currentRetryTimeout); currentRetryTimeout = undefined; } - } + }; - function retry(){ + var retry = function() { log("Retry"); if (!currentRetryTimeout){ @@ -252,17 +245,17 @@ initBt(); }, clampedTime); - retryTime = Math.pow(retryTime, 1.1); + retryTime = Math.pow(clampedTime, 1.1); if (retryTime > maxRetryTime){ retryTime = maxRetryTime; } } else { log("Already in retry..."); } - } + }; var buzzing = false; - function onDisconnect(reason) { + var onDisconnect = function(reason) { log("Disconnect: " + reason); log("GATT: ", gatt); log("Characteristics: ", characteristics); @@ -277,11 +270,23 @@ if (Bangle.isBTHRMOn()){ retry(); } - } + }; - function createCharacteristicPromise(newCharacteristic){ + var createCharacteristicPromise = function(newCharacteristic) { log("Create characteristic promise: ", newCharacteristic); var 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){ + result = result.then(()=>{ + log("Reading data for " + JSON.stringify(newCharacteristic)); + return newCharacteristic.readValue().then((data)=>{ + if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) { + supportedCharacteristics[newCharacteristic.uuid].handler(data); + } + }); + }); + } if (newCharacteristic.properties.notify){ result = result.then(()=>{ log("Starting notifications for: ", newCharacteristic); @@ -290,31 +295,23 @@ log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); startPromise = startPromise.then(()=>{ log("Wait after connect"); - waitingPromise(settings.gracePeriodNotification) + return waitingPromise(settings.gracePeriodNotification); }); } return startPromise; }); - } else if (newCharacteristic.read){ - result = result.then(()=>{ - readData(newCharacteristic); - log("Reading data for " + newCharacteristic); - return newCharacteristic.read().then((data)=>{ - supportedCharacteristics[newCharacteristic.uuid].handler(data); - }); - }); } return result.then(()=>log("Handled characteristic: ", newCharacteristic)); - } - - function attachCharacteristicPromise(promise, characteristic){ + }; + + var attachCharacteristicPromise = function(promise, characteristic) { return promise.then(()=>{ log("Handling characteristic:", characteristic); return createCharacteristicPromise(characteristic); }); - } - - function createCharacteristicsPromise(newCharacteristics){ + }; + + var createCharacteristicsPromise = function(newCharacteristics) { log("Create characteristics promise: ", newCharacteristics); var result = Promise.resolve(); for (var c of newCharacteristics){ @@ -324,13 +321,13 @@ if (c.properties.notify){ addNotificationHandler(c); } - + result = attachCharacteristicPromise(result, c); } return result.then(()=>log("Handled characteristics")); - } - - function createServicePromise(service){ + }; + + var createServicePromise = function(service) { log("Create service promise: ", service); var result = Promise.resolve(); result = result.then(()=>{ @@ -338,15 +335,13 @@ return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); }); return result.then(()=>log("Handled service" + service.uuid)); - } - - function attachServicePromise(promise, service){ - return promise.then(()=>createServicePromise(service)); - } - - var reUseCounter = 0; + }; - function initBt() { + var attachServicePromise = function(promise, service) { + return promise.then(()=>createServicePromise(service)); + }; + + var initBt = function () { log("initBt with blockInit: " + blockInit); if (blockInit){ retry(); @@ -355,63 +350,58 @@ blockInit = true; - if (reUseCounter > 10){ - log("Reuse counter to high"); - gatt=undefined; - reUseCounter = 0; - } - var promise; - + var filters; + if (!device){ - var filters = serviceFilters; - if (settings.btname){ - log("Configured device name", settings.btname); - filters = [{name: settings.btname}]; + if (settings.btid){ + log("Configured device id", settings.btid); + filters = [{ id: settings.btid }]; + } else { + return; } log("Requesting device with filters", filters); - promise = NRF.requestDevice({ filters: filters }); - + promise = NRF.requestDevice({ filters: filters, active: true }); + if (settings.gracePeriodRequest){ log("Add " + settings.gracePeriodRequest + "ms grace period after request"); } - + promise = promise.then((d)=>{ log("Got device: ", d); 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 cachedName = getCache().name; - if (device.name != cachedName){ - log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache"); + var cachedId = getCache().id; + if (device.id !== cachedId){ + log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache"); clearCache(); } var newCache = getCache(); - newCache.name = device.name; + newCache.id = device.id; writeCache(newCache); gatt = device.gatt; } - + return Promise.resolve(gatt); }); - + promise = promise.then((gatt)=>{ if (!gatt.connected){ var connectPromise = gatt.connect(connectSettings); @@ -427,16 +417,28 @@ return Promise.resolve(); } }); - + +/* 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(() => console.log(gatt.getSecurityStatus())); + } + });*/ + promise = promise.then(()=>{ - if (!characteristics || characteristics.length == 0){ + if (!characteristics || characteristics.length === 0){ characteristics = characteristicsFromCache(); } }); promise = promise.then(()=>{ var characteristicsPromise = Promise.resolve(); - if (characteristics.length == 0){ + if (characteristics.length === 0){ characteristicsPromise = characteristicsPromise.then(()=>{ log("Getting services"); return gatt.getPrimaryServices(); @@ -454,24 +456,22 @@ log("Add " + settings.gracePeriodService + "ms grace period after services"); result = result.then(()=>{ log("Wait after services"); - return waitingPromise(settings.gracePeriodService) + return waitingPromise(settings.gracePeriodService); }); } return result; }); - } else { for (var characteristic of characteristics){ characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true); } } - + return characteristicsPromise; }); - - promise = promise.then(()=>{ + + return promise.then(()=>{ log("Connection established, waiting for notifications"); - reUseCounter = 0; characteristicsToCache(characteristics); clearRetryTimeout(); }).catch((e) => { @@ -479,7 +479,7 @@ log("Error:", e); onDisconnect(e); }); - } + }; Bangle.setBTHRMPower = function(isOn, app) { // Do app power handling @@ -487,7 +487,7 @@ if (Bangle._PWR===undefined) Bangle._PWR={}; if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); - if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app); + if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app); isOn = Bangle._PWR.BTHRM.length; // so now we know if we're really on if (isOn) { @@ -510,7 +510,7 @@ } } }; - + var origSetHRMPower = Bangle.setHRMPower; if (settings.startWithHrm){ @@ -525,11 +525,10 @@ } }; } - - + var fallbackInterval; - - function switchInternalHrm(){ + + var switchInternalHrm = function() { if (settings.allowFallback && !fallbackInterval){ log("Fallback to HRM enabled"); origSetHRMPower(1, "bthrm_fallback"); @@ -542,7 +541,7 @@ } }, settings.fallbackTimeout); } - } + }; if (settings.replace){ log("Replace HRM event"); @@ -557,11 +556,11 @@ } switchInternalHrm(); } - + E.on("kill", ()=>{ if (gatt && gatt.connected){ log("Got killed, trying to disconnect"); - var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); + gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); } }); } diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js index cc533eedd..dd9230386 100644 --- a/apps/bthrm/bthrm.js +++ b/apps/bthrm/bthrm.js @@ -1,7 +1,16 @@ -var btm = g.getHeight()-1; var intervalInt; var intervalBt; +var BODY_LOCS = { + 0: 'Other', + 1: 'Chest', + 2: 'Wrist', + 3: 'Finger', + 4: 'Hand', + 5: 'Ear Lobe', + 6: 'Foot', +} + function clear(y){ g.reset(); g.clearRect(0,y,g.getWidth(),y+75); @@ -15,17 +24,17 @@ function draw(y, type, event) { g.setFontAlign(0,0); g.setFontVector(40).drawString(str,px,y+20); str = "Event: " + type; - if (type == "HRM") { + if (type === "HRM") { str += " Confidence: " + event.confidence; g.setFontVector(12).drawString(str,px,y+40); str = " Source: " + (event.src ? event.src : "internal"); g.setFontVector(12).drawString(str,px,y+50); } - if (type == "BTHRM"){ + if (type === "BTHRM"){ if (event.battery) str += " Bat: " + (event.battery ? event.battery : ""); g.setFontVector(12).drawString(str,px,y+40); str= ""; - if (event.location) str += "Loc: " + event.location.toFixed(0) + "ms"; + if (event.location) str += "Loc: " + BODY_LOCS[event.location]; if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(","); g.setFontVector(12).drawString(str,px,y+50); str= ""; @@ -45,7 +54,7 @@ function onBtHrm(e) { firstEventBt = false; } draw(100, "BTHRM", e); - if (e.bpm == 0){ + if (e.bpm === 0){ Bangle.buzz(100,0.2); } if (intervalBt){ diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index 64e638b8a..fb284bcd2 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -7,10 +7,10 @@ "allowFallback": true, "warnDisconnect": false, "fallbackTimeout": 10, - "custom_replace": false, + "custom_replace": true, "custom_debuglog": false, - "custom_startWithHrm": false, - "custom_allowFallback": false, + "custom_startWithHrm": true, + "custom_allowFallback": true, "custom_warnDisconnect": false, "custom_fallbackTimeout": 10, "gracePeriodNotification": 0, diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index b35ebd6af..39c1ff8bb 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,11 +2,11 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.08", + "version": "0.09", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", "type": "app", - "tags": "health,bluetooth", + "tags": "health,bluetooth,hrm,bthrm", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index 4b564d670..b376d6a2d 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -5,14 +5,14 @@ require('Storage').writeJSON(FILE, s); readSettings(); } - + function readSettings(){ settings = Object.assign( require('Storage').readJSON("bthrm.default.json", true) || {}, require('Storage').readJSON(FILE, true) || {} ); } - + var FILE="bthrm.json"; var settings; readSettings(); @@ -61,12 +61,13 @@ } }; - if (settings.btname){ - var name = "Clear " + settings.btname; + if (settings.btname || settings.btid){ + var name = "Clear " + (settings.btname || settings.btid); mainmenu[name] = function() { - E.showPrompt("Clear current device name?").then((r)=>{ + E.showPrompt("Clear current device?").then((r)=>{ if (r) { writeSettings("btname",undefined); + writeSettings("btid",undefined); } E.showMenu(buildMainMenu()); }); @@ -78,9 +79,7 @@ mainmenu.Debug = function() { E.showMenu(submenu_debug); }; return mainmenu; } - - var submenu_debug = { '' : { title: "Debug"}, '< Back': function() { E.showMenu(buildMainMenu()); }, @@ -103,35 +102,39 @@ function createMenuFromScan(){ E.showMenu(); - E.showMessage("Scanning"); + E.showMessage("Scanning for 4 seconds"); var submenu_scan = { - '' : { title: "Scan"}, '< Back': function() { E.showMenu(buildMainMenu()); } }; - var packets=10; - var scanStart=Date.now(); - NRF.setScan(function(d) { - packets--; - if (packets<=0 || Date.now() - scanStart > 5000){ - NRF.setScan(); - E.showMenu(submenu_scan); - } else if (d.name){ - print("Found device", d); - submenu_scan[d.name] = function(){ - E.showPrompt("Set "+d.name+"?").then((r)=>{ - if (r) { - writeSettings("btname",d.name); - } - E.showMenu(buildMainMenu()); + NRF.findDevices(function(devices) { + submenu_scan[''] = { title: `Scan (${devices.length} found)`}; + if (devices.length === 0) { + E.showAlert("No devices found") + .then(() => E.showMenu(buildMainMenu())); + return; + } else { + devices.forEach((d) => { + print("Found device", d); + var shown = (d.name || d.id.substr(0, 17)); + submenu_scan[shown] = function () { + E.showPrompt("Set " + shown + "?").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.showMenu(buildMainMenu()); + }); + }; }); - }; } - }, { filters: [{services: [ "180d" ]}]}); + E.showMenu(submenu_scan); + }, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]}); } - - var submenu_custom = { '' : { title: "Custom mode"}, '< Back': function() { E.showMenu(buildMainMenu()); }, @@ -167,7 +170,7 @@ } }, }; - + var submenu_grace = { '' : { title: "Grace periods"}, '< Back': function() { E.showMenu(submenu_debug); }, @@ -212,51 +215,6 @@ } } }; - - var submenu = { - '' : { title: "Grace periods"}, - '< Back': function() { E.showMenu(buildMainMenu()); }, - 'Request': { - value: settings.gracePeriodRequest, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodRequest",v); - } - }, - 'Connect': { - value: settings.gracePeriodConnect, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodConnect",v); - } - }, - 'Notification': { - value: settings.gracePeriodNotification, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodNotification",v); - } - }, - 'Service': { - value: settings.gracePeriodService, - min: 0, - max: 3000, - step: 100, - format: v=>v+"ms", - onchange: v => { - writeSettings("gracePeriodService",v); - } - } - }; - + E.showMenu(buildMainMenu()); -}) +}); diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index ecf441925..ecd0c355f 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -5,4 +5,5 @@ 0.05: Included icons for information. 0.06: Design and usability improvements. 0.07: Improved positioning. -0.08: Select the color of widgets correctly. Additional settings to hide colon. \ No newline at end of file +0.08: Select the color of widgets correctly. Additional settings to hide colon. +0.09: Larger font size if colon is hidden to improve readability further. \ No newline at end of file diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index c22ec050f..5bfec4097 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -44,6 +44,16 @@ Graphics.prototype.setLargeFont = function(scale) { return this; }; +Graphics.prototype.setXLargeFont = function(scale) { + // Actual height 53 (55 - 3) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("FR4uHyopKyksJSssGA=="), + 70+(scale<<8)+(1<<16) + ); +}; + Graphics.prototype.setMediumFont = function(scale) { // Actual height 41 (42 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16)); @@ -304,17 +314,13 @@ function drawTime(){ g.setColor(g.theme.bg); g.setFontAlign(0,0); - var timeStr; - if(settings.hideColon){ - var hours = date.getHours(); - hours -= hours >=12 ? 12 : 0; - var minutes = date.getMinutes(); - minutes = minutes < 10 ? String("0") + minutes : minutes; - timeStr = String(hours) + minutes; - } else { - timeStr = locale.time(date,1); - } + var hours = String(date.getHours()); + var minutes = date.getMinutes(); + minutes = minutes < 10 ? String("0") + minutes : minutes; + var colon = settings.hideColon ? "" : ":"; + var timeStr = hours + colon + minutes; + // Set y coordinates correctly y += parseInt((H - y)/2) + 5; var infoEntry = getInfoEntry(); @@ -324,7 +330,11 @@ function drawTime(){ // Show large or small time depending on info entry if(infoStr == null){ - g.setLargeFont(); + if(settings.hideColon){ + g.setXLargeFont(); + } else { + g.setLargeFont(); + } } else { y -= 15; g.setMediumFont(); diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index 60634e26c..eba1449a6 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.08", + "version": "0.09", "description": "BW Clock.", "readme": "README.md", "icon": "app.png", diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png index 302117ea9..550913422 100644 Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png index 9614131ec..ccbc9aae1 100644 Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png index 148f005e6..5bf7083f0 100644 Binary files a/apps/bwclk/screenshot_3.png and b/apps/bwclk/screenshot_3.png differ diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog index 420c553f5..cb520193b 100644 --- a/apps/ffcniftya/ChangeLog +++ b/apps/ffcniftya/ChangeLog @@ -1,2 +1,4 @@ 0.01: New Clock Nifty A -0.02: Shows the current week number (ISO8601), can be disabled via settings "" +0.02: Shows the current week number (ISO8601), can be disabled via settings +0.03: Call setUI before loading widgets + Improve settings page diff --git a/apps/ffcniftya/README.md b/apps/ffcniftya/README.md index 86f1f5c2d..80005fd3c 100644 --- a/apps/ffcniftya/README.md +++ b/apps/ffcniftya/README.md @@ -1,13 +1,12 @@ # Nifty-A Clock -Colors are black/white - photos have non correct camera color "blue" +Colors are black/white - photos have non correct camera color "blue". -## This is the clock +This is the clock: ![](screenshot_nifty.png) -## The week number (ISO8601) can be turned of in settings -(default is **"On"**) +The week number (ISO8601) can be turned off in settings (default is `On`) ![](screenshot_settings_nifty.png) diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 5da1ec48e..4000a1578 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -1,6 +1,6 @@ const locale = require("locale"); -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; -const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true}; +const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; +const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"]; /* Clock *********************************************/ const scale = g.getWidth() / 176; @@ -17,16 +17,17 @@ const center = { y: Math.round(((viewport.height - widget) / 2) + widget), } -function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 - var tdt = new Date(date.valueOf()); - var dayn = (date.getDay() + 6) % 7; - tdt.setDate(tdt.getDate() - dayn + 3); - var firstThursday = tdt.valueOf(); - tdt.setMonth(0, 1); - if (tdt.getDay() !== 4) { - tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); - } - return 1 + Math.ceil((firstThursday - tdt) / 604800000); +// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 +function ISO8601_week_no(date) { + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + return 1 + Math.ceil((firstThursday - tdt) / 604800000); } function d02(value) { @@ -59,7 +60,7 @@ function draw() { g.drawString(year, centerDatesScaleX, center.y - 62 * scale); g.drawString(month, centerDatesScaleX, center.y - 44 * scale); g.drawString(day, centerDatesScaleX, center.y - 26 * scale); - if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale); + if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale); g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); } @@ -79,7 +80,6 @@ function clearTickTimer() { function queueNextTick() { clearTickTimer(); tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); - // tickTimer = setTimeout(tick, 3000); } function tick() { @@ -91,21 +91,16 @@ function tick() { // Clear the screen once, at startup g.clear(); -// Start ticking tick(); -// Stop updates when LCD is off, restart when on Bangle.on('lcdPower', (on) => { if (on) { - tick(); // Start ticking + tick(); } else { - clearTickTimer(); // stop ticking + clearTickTimer(); } }); -// Load widgets +Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json index ce91cc225..91b426cd0 100644 --- a/apps/ffcniftya/metadata.json +++ b/apps/ffcniftya/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftya", "name": "Nifty-A Clock", - "version": "0.02", + "version": "0.03", "description": "A nifty clock with time and date", "icon": "app.png", "screenshots": [{"url":"screenshot_nifty.png"}], diff --git a/apps/ffcniftya/settings.js b/apps/ffcniftya/settings.js index 46e4ef5aa..aec1d680a 100644 --- a/apps/ffcniftya/settings.js +++ b/apps/ffcniftya/settings.js @@ -1,23 +1,15 @@ -(function(back) { - var FILE = "ffcniftya.json"; - // Load settings - var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true }; +(function (back) { + const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true)); - function writeSettings() { - require('Storage').writeJSON(FILE, cfg); - } - - // Show the menu E.showMenu({ - "" : { "title" : "Nifty-A Clock" }, - "< Back" : () => back(), - 'week number?': { - value: cfg.showWeekNum, - format: v => v?"On":"Off", + "": { "title": "Nifty-A Clock" }, + "< Back": () => back(), + /*LANG*/"Show Week Number": { + value: settings.showWeekNum, onchange: v => { - cfg.showWeekNum = v; - writeSettings(); + settings.showWeekNum = v; + require("Storage").writeJSON("ffcniftya.json", settings); } } }); -}) \ No newline at end of file +}) diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index dedd31452..9fc7e3c5c 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -1,2 +1,5 @@ 0.01: New Clock Nifty B -0.02: Added configuration \ No newline at end of file +0.02: Added configuration +0.03: Call setUI before loading widgets + Fix bug with black being unselectable + Improve settings page diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md index e04243a0b..072f71cce 100644 --- a/apps/ffcniftyb/README.md +++ b/apps/ffcniftyb/README.md @@ -1,9 +1,6 @@ # Nifty Series B Clock - Display Time and Date -- Color Configuration - -## +- Colour Configuration ![](screenshot.png) - diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 75d217ab4..65c74dbd7 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,9 +1,5 @@ -const locale = require("locale"); -const storage = require('Storage'); - -const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"]; -const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */; - +const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; +const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED /* Clock *********************************************/ const scale = g.getWidth() / 176; @@ -19,7 +15,7 @@ const center = { }; function d02(value) { - return ('0' + value).substr(-2); + return ("0" + value).substr(-2); } function renderEllipse(g) { @@ -35,8 +31,8 @@ function renderText(g) { const month = d02(now.getMonth() + 1); const year = now.getFullYear(); - const month2 = locale.month(now, 3); - const day2 = locale.dow(now, 3); + const month2 = require("locale").month(now, 3); + const day2 = require("locale").dow(now, 3); g.setFontAlign(1, 0).setFont("Vector", 90 * scale); g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); @@ -96,7 +92,6 @@ function startTick(run) { stopTick(); run(); ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); - // ticker = setTimeout(() => startTick(run), 3000); } /* Init **********************************************/ @@ -104,7 +99,7 @@ function startTick(run) { g.clear(); startTick(draw); -Bangle.on('lcdPower', (on) => { +Bangle.on("lcdPower", (on) => { if (on) { startTick(draw); } else { @@ -112,7 +107,6 @@ Bangle.on('lcdPower', (on) => { } }); +Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); - -Bangle.setUI("clock"); diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json index 73f93ed36..3d26c27ea 100644 --- a/apps/ffcniftyb/metadata.json +++ b/apps/ffcniftyb/metadata.json @@ -1,8 +1,8 @@ { "id": "ffcniftyb", "name": "Nifty-B Clock", - "version": "0.02", - "description": "A nifty clock (series B) with time, date and color configuration", + "version": "0.03", + "description": "A nifty clock (series B) with time, date and colour configuration", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", diff --git a/apps/ffcniftyb/settings.js b/apps/ffcniftyb/settings.js index 00abf80b5..da350edd8 100644 --- a/apps/ffcniftyb/settings.js +++ b/apps/ffcniftyb/settings.js @@ -1,49 +1,31 @@ (function (back) { - const storage = require('Storage'); - const SETTINGS_FILE = "ffcniftyb.json"; + const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)); const colors = { - 65535: 'White', - 63488: 'Red', - 65504: 'Yellow', - 2047: 'Cyan', - 2016: 'Green', - 31: 'Blue', - 0: 'Black', + 65535: /*LANG*/"White", + 63488: /*LANG*/"Red", + 65504: /*LANG*/"Yellow", + 2047: /*LANG*/"Cyan", + 2016: /*LANG*/"Green", + 31: /*LANG*/"Blue", + 0: /*LANG*/"Black" } - function load(settings) { - return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {}); - } + const menu = {}; + menu[""] = { title: "Nifty-B Clock" }; + menu["< Back"] = back; - function save(settings) { - storage.write(SETTINGS_FILE, settings) - } - - const settings = load({ - color: 63488 /* red */, + Object.keys(colors).forEach(color => { + var label = colors[color]; + menu[label] = { + value: settings.color == color, + onchange: () => { + settings.color = color; + require("Storage").write("ffcniftyb.json", settings); + setTimeout(load, 10); + } + }; }); - const saveColor = (color) => () => { - settings.color = color; - save(settings); - back(); - }; - - function showMenu(items, opt) { - items[''] = opt || {}; - items['< Back'] = back; - E.showMenu(items); - } - - showMenu( - Object.keys(colors).reduce((menu, color) => { - menu[colors[color]] = saveColor(color); - return menu; - }, {}), - { - title: 'Color', - selected: Object.keys(colors).indexOf(settings.color) - } - ); + E.showMenu(menu); }); diff --git a/apps/health/README.md b/apps/health/README.md index c6b379c0a..3cc234a3f 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -2,7 +2,7 @@ Logs health data to a file every 10 minutes, and provides an app to view it -**BETA - requires firmware 2v11** +**BETA - requires firmware 2v11 or later** ## Usage diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog new file mode 100644 index 000000000..4a72a9f28 --- /dev/null +++ b/apps/iconlaunch/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial release +0.02: implemented "direct launch" and "one click exit" settings \ No newline at end of file diff --git a/apps/iconlaunch/README.md b/apps/iconlaunch/README.md new file mode 100644 index 000000000..0d36fdeb4 --- /dev/null +++ b/apps/iconlaunch/README.md @@ -0,0 +1,12 @@ +# Icon launcher + +A launcher inspired by smartphones, with an icon-only scrollable menu. + +This launcher shows 9 apps per screen, making it much faster to navigate versus the default launcher. + +![A screenshot](screenshot1.png) +![Another screenshot](screenshot2.png) + +## Technical note + +The app uses `E.showScroller`'s code in the app but not the function itself because `E.showScroller` doesn't report the position of a press to the select function. diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js new file mode 100644 index 000000000..4eeaff589 --- /dev/null +++ b/apps/iconlaunch/app.js @@ -0,0 +1,209 @@ +const s = require("Storage"); +const settings = s.readJSON("launch.json", true) || { showClocks: true, fullscreen: false,direct:false,oneClickExit:false }; + +if( settings.oneClickExit) + setWatch(_=> load(), BTN1); + +if (!settings.fullscreen) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} + +var apps = s + .list(/\.info$/) + .map((app) => { + var a = s.readJSON(app, 1); + return ( + a && { + name: a.name, + type: a.type, + icon: a.icon, + sortorder: a.sortorder, + src: a.src, + } + ); + }) + .filter( + (app) => + app && + (app.type == "app" || + (app.type == "clock" && settings.showClocks) || + !app.type) + ); +apps.sort((a, b) => { + var n = (0 | a.sortorder) - (0 | b.sortorder); + if (n) return n; // do sortorder first + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; +}); +apps.forEach((app) => { + if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area +}); + +let scroll = 0; +let selectedItem = -1; +const R = Bangle.appRect; + +const iconSize = 48; + +const appsN = Math.floor(R.w / iconSize); +const whitespace = (R.w - appsN * iconSize) / (appsN + 1); + +const itemSize = iconSize + whitespace; + +function drawItem(itemI, r) { + g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1); + let x = 0; + for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) { + if (!apps[i]) break; + x += whitespace; + if (!apps[i].icon) { + g.setFontAlign(0,0,0).setFont("12x20:2").drawString("?", x + r.x+iconSize/2, r.y + iconSize/2); + } else { + g.drawImage(apps[i].icon, x + r.x, r.y); + } + if (selectedItem == i) { + g.drawRect( + x + r.x - 1, + r.y - 1, + x + r.x + iconSize + 1, + r.y + iconSize + 1 + ); + } + x += iconSize; + } + drawText(itemI); +} + +function drawItemAuto(i) { + var y = idxToY(i); + g.reset().setClipRect(R.x, y, R.x2, y + itemSize); + drawItem(i, { + x: R.x, + y: y, + w: R.w, + h: itemSize + }); + g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); +} + +let lastIsDown = false; + +function drawText(i) { + const selectedApp = apps[selectedItem]; + const idy = (selectedItem - (selectedItem % 3)) / 3; + if (!selectedApp || i != idy) return; + const appY = idxToY(idy) + iconSize / 2; + g.setFontAlign(0, 0, 0); + g.setFont("12x20"); + const rect = g.stringMetrics(selectedApp.name); + g.clearRect( + R.w / 2 - rect.width / 2, + appY - rect.height / 2, + R.w / 2 + rect.width / 2, + appY + rect.height / 2 + ); + g.drawString(selectedApp.name, R.w / 2, appY); +} + +function selectItem(id, e) { + const iconN = E.clip(Math.floor((e.x - R.x) / itemSize), 0, appsN - 1); + const appId = id * appsN + iconN; + if( settings.direct && apps[appId]) + { + load(apps[appId].src); + return; + } + if (appId == selectedItem && apps[appId]) { + const app = apps[appId]; + if (!app.src || s.read(app.src) === undefined) { + E.showMessage( /*LANG*/ "App Source\nNot found"); + } else { + load(app.src); + } + } + selectedItem = appId; + drawItems(); +} + +function idxToY(i) { + return i * itemSize + R.y - (scroll & ~1); +} + +function YtoIdx(y) { + return Math.floor((y + (scroll & ~1) - R.y) / itemSize); +} + +function drawItems() { + g.reset().clearRect(R.x, R.y, R.x2, R.y2); + g.setClipRect(R.x, R.y, R.x2, R.y2); + var a = YtoIdx(R.y); + var b = Math.min(YtoIdx(R.y2), 99); + for (var i = a; i <= b; i++) + drawItem(i, { + x: R.x, + y: idxToY(i), + w: R.w, + h: itemSize, + }); + g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); +} + +drawItems(); +g.flip(); + +const itemsN = Math.ceil(apps.length / appsN); + +Bangle.setUI({ + mode: "custom", + drag: (e) => { + let dy = e.dy; + if (scroll + R.h - dy > itemsN * itemSize) { + dy = scroll + R.h - itemsN * itemSize; + } + if (scroll - dy < 0) { + dy = scroll; + } + scroll -= dy; + scroll = E.clip(scroll, 0, itemSize * (itemsN - 1)); + g.setClipRect(R.x, R.y, R.x2, R.y2); + g.scroll(0, dy); + if (dy < 0) { + g.setClipRect(R.x, R.y2 - (1 - dy), R.x2, R.y2); + let i = YtoIdx(R.y2 - (1 - dy)); + let y = idxToY(i); + while (y < R.y2) { + drawItem(i, { + x: R.x, + y: y, + w: R.w, + h: itemSize, + }); + i++; + y += itemSize; + } + } else { + // d>0 + g.setClipRect(R.x, R.y, R.x2, R.y + dy); + let i = YtoIdx(R.y + dy); + let y = idxToY(i); + while (y > R.y - itemSize) { + drawItem(i, { + x: R.x, + y: y, + w: R.w, + h: itemSize, + }); + y -= itemSize; + i--; + } + } + g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1); + }, + touch: (_, e) => { + if (e.y < R.y - 4) return; + var i = YtoIdx(e.y); + selectItem(i, e); + }, +}); diff --git a/apps/iconlaunch/app.png b/apps/iconlaunch/app.png new file mode 100644 index 000000000..1c8068c50 Binary files /dev/null and b/apps/iconlaunch/app.png differ diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json new file mode 100644 index 000000000..01e447672 --- /dev/null +++ b/apps/iconlaunch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "iconlaunch", + "name": "Icon Launcher", + "shortName" : "Icon launcher", + "version": "0.02", + "icon": "app.png", + "description": "A launcher inspired by smartphones, with an icon-only scrollable menu.", + "tags": "tool,system,launcher", + "type": "launch", + "supports": ["BANGLEJS2"], + "storage": [ + { "name": "iconlaunch.app.js", "url": "app.js" }, + { "name": "iconlaunch.settings.js", "url": "settings.js" } + ], + "screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }], + "readme": "README.md", + "sortorder": -10 +} diff --git a/apps/iconlaunch/screenshot1.png b/apps/iconlaunch/screenshot1.png new file mode 100644 index 000000000..8695ead7a Binary files /dev/null and b/apps/iconlaunch/screenshot1.png differ diff --git a/apps/iconlaunch/screenshot2.png b/apps/iconlaunch/screenshot2.png new file mode 100644 index 000000000..b17efa78b Binary files /dev/null and b/apps/iconlaunch/screenshot2.png differ diff --git a/apps/iconlaunch/settings.js b/apps/iconlaunch/settings.js new file mode 100644 index 000000000..e9667047c --- /dev/null +++ b/apps/iconlaunch/settings.js @@ -0,0 +1,38 @@ +// make sure to enclose the function in parentheses +(function(back) { + let settings = Object.assign({ + showClocks: true, + fullscreen: false + }, require("Storage").readJSON("launch.json", true) || {}); + + let fonts = g.getFonts(); + function save(key, value) { + settings[key] = value; + require("Storage").write("launch.json",settings); + } + const appMenu = { + "": { "title": /*LANG*/"Launcher" }, + /*LANG*/"< Back": back, + /*LANG*/"Show Clocks": { + value: settings.showClocks == true, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: (m) => { save("showClocks", m) } + }, + /*LANG*/"Fullscreen": { + value: settings.fullscreen == true, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: (m) => { save("fullscreen", m) } + }, + /*LANG*/"Direct launch": { + value: settings.direct == true, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: (m) => { save("direct", m) } + }, + /*LANG*/"One click exit": { + value: settings.oneClickExit == true, + format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", + onchange: (m) => { save("oneClickExit", m) } + } + }; + E.showMenu(appMenu); +}); diff --git a/apps/multitimer/ChangeLog b/apps/multitimer/ChangeLog index 624f1b0fb..9b60f403a 100644 --- a/apps/multitimer/ChangeLog +++ b/apps/multitimer/ChangeLog @@ -1 +1,2 @@ -0.01: Initial version \ No newline at end of file +0.01: Initial version +0.02: Update for time_utils module diff --git a/apps/multitimer/alarm.js b/apps/multitimer/alarm.js index fc0195455..97cbaa5fa 100644 --- a/apps/multitimer/alarm.js +++ b/apps/multitimer/alarm.js @@ -73,7 +73,7 @@ function showAlarm(alarm) { const settings = require("sched").getSettings(); let msg = ""; - msg += require("sched").formatTime(alarm.timer); + if (alarm.timer) msg += require("time_utils").formatTime(alarm.timer); if (alarm.msg) { msg += "\n"+alarm.msg; } @@ -86,7 +86,7 @@ function showAlarm(alarm) { if (alarm.data.hm && alarm.data.hm == true) { //hard mode extends auto-snooze time - buzzCount = buzzCount * 2; + buzzCount = buzzCount * 3; startHM(); } diff --git a/apps/multitimer/app.js b/apps/multitimer/app.js index becaf6169..e5d77d860 100644 --- a/apps/multitimer/app.js +++ b/apps/multitimer/app.js @@ -258,7 +258,7 @@ function editTimer(idx, a) { a.last = 0; a.data.ot = a.timer; a.appid = "multitimer"; - a.js = "load('multitimer.alarm.js')"; + a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')"; if (idx < 0) alarms.push(a); else alarms[timerIdx[idx]] = a; require("sched").setAlarms(alarms); @@ -585,7 +585,7 @@ function editAlarm(idx, a) { var menu = { "": { "title": "Alarm" }, "< Back": () => { - if (a.data.hm == true) a.js = "load('multitimer.alarm.js')"; + if (a.data.hm == true) a.js = "(require('Storage').read('multitimer.alarm.js') !== undefined) ? load('multitimer.alarm.js') : load('sched.js')"; if (a.data.hm == false && a.js) delete a.js; if (idx >= 0) alarms[alarmIdx[idx]] = a; else alarms.push(a); diff --git a/apps/multitimer/metadata.json b/apps/multitimer/metadata.json index 6e53e2c8c..abb958b90 100644 --- a/apps/multitimer/metadata.json +++ b/apps/multitimer/metadata.json @@ -1,7 +1,7 @@ { "id": "multitimer", "name": "Multi Timer", - "version": "0.01", + "version": "0.02", "description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.", "icon": "app.png", "screenshots": [ @@ -19,4 +19,4 @@ ], "data": [{"name":"multitimer.json"}], "dependencies": {"scheduler":"type"} -} \ No newline at end of file +} diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 5dd28453e..4b415c1c5 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -4,4 +4,5 @@ 0.04: Fixed icon and png to 48x48 pixels 0.05: added charging icon 0.06: Add 12h support and autocycle control -0.07: added localization; removed deprecated code \ No newline at end of file +0.07: added localization, removed deprecated code +0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting diff --git a/apps/rebble/KdamThmor.ttf b/apps/rebble/KdamThmor.ttf new file mode 100644 index 000000000..ca484ccbd Binary files /dev/null and b/apps/rebble/KdamThmor.ttf differ diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index e1f18ea55..e28c67784 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,11 +2,11 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.07", + "version": "0.08", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", - "dependencies": {"mylocation":"app", "widpedom":"app"}, + "dependencies": {"mylocation":"app"}, "screenshots": [{"url":"screenshot_rebble.png"}], "type": "clock", "tags": "clock", @@ -14,6 +14,7 @@ "storage": [ {"name":"rebble.app.js","url":"rebble.app.js"}, {"name":"rebble.settings.js","url":"rebble.settings.js"}, - {"name":"rebble.img","url":"rebble.icon.js","evaluate":true} + {"name":"rebble.img","url":"rebble.icon.js","evaluate":true}, + {"name":"suncalc","url":"suncalc.js"} ] } diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index bdc7ed1a3..8ba61f818 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -1,4 +1,4 @@ -var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); +var SunCalc = require("suncalc"); const SETTINGS_FILE = "rebble.json"; const LOCATION_FILE = "mylocation.json"; const GLOBAL_SETTINGS = "setting.json"; @@ -6,14 +6,15 @@ let settings; let location; let is12Hour; -Graphics.prototype.setFontLECO1976Regular22 = function(scale) { - // Actual height 22 (21 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22+(scale<<8)+(1<<16)); -} - Graphics.prototype.setFontKdamThmor = function(scale) { - // Actual height 72 (71 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAAAAAAAAAAAAH+AAAAAAAAAAAAAP/AAAAAAAAAAAAAf/gAAAAAAAAAAAA//gAAAAAAAAAAAA//wAAAAAAAAAAAA//wAAAAAAAAAAAA//wAAAAAAAAAAAA//wAAAAAAAAAAAA//gAAAAAAAAAAAAf/gAAAAAAAAAAAAf/AAAAAAAAAAAAAP+AAAAAAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAB/AAAAAAAAAAAAAP/AAAAAAAAAAAAA//AAAAAAAAAAAAH/+AAAAAAAAAAAAf/+AAAAAAAAAAAD//+AAAAAAAAAAAf//8AAAAAAAAAAB///4AAAAAAAAAAP///wAAAAAAAAAA////AAAAAAAAAAH///4AAAAAAAAAAf///gAAAAAAAAAD///8AAAAAAAAAAf///wAAAAAAAAAB///+AAAAAAAAAAP///4AAAAAAAAAA////AAAAAAAAAAH///4AAAAAAAAAAf///gAAAAAAAAAD///8AAAAAAAAAAP///wAAAAAAAAAB///+AAAAAAAAAAP///4AAAAAAAAAA////AAAAAAAAAAD///4AAAAAAAAAAP///gAAAAAAAAAAf//8AAAAAAAAAAA///wAAAAAAAAAAA//+AAAAAAAAAAAA//4AAAAAAAAAAAA//AAAAAAAAAAAAA/4AAAAAAAAAAAAA/gAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAAAAAAAAAAP///+AAAAAAAAAD/////wAAAAAAAAP/////+AAAAAAAA///////gAAAAAAD///////4AAAAAAH///////8AAAAAAf///////+AAAAAA/////////gAAAAB/////////wAAAAB/////////wAAAAD/////////4AAAAH///AAA///8AAAAH//wAAAB//8AAAAP/+AAAAAP/+AAAAP/4AAAAAD/+AAAAf/gAAAAAA//AAAAf/AAAAAAAf/AAAAf+AAAAAAAP/AAAA/+AAAAAAAP/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/8AAAAAAAH/gAAA/+AAAAAAAP/gAAAf+AAAAAAAP/AAAAf/AAAAAAAf/AAAAf/gAAAAAA//AAAAP/4AAAAAD/+AAAAP/+AAAAAP/+AAAAH//gAAAA//8AAAAH///AAAf//8AAAAD/////////4AAAAD/////////wAAAAB/////////wAAAAA/////////gAAAAAf////////AAAAAAH///////8AAAAAAD///////4AAAAAAA///////gAAAAAAAP/////+AAAAAAAAD/////4AAAAAAAAAf////AAAAAAAAAAA///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAADwAAAAAAAAAAAAAD4AAAAAAAAAAAAAH8AAAAAAAAAAAAAP+AAAAAAAAAAAAAf/AAAAAH/AAAAAA//AAAAAH/AAAAAB//AAAAAH/AAAAAB//AAAAAH/AAAAAD/+AAAAAH/AAAAAH/8AAAAAH/AAAAAP/4AAAAAH/AAAAAf/wAAAAAH/AAAAA//gAAAAAH/AAAAB//gAAAAAH/AAAAB//AAAAAAH/AAAAD/+AAAAAAH/AAAAH/8AAAAAAH/AAAAP//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AAAAAAHgAAAAAD/AAAAAA/wAAAAAH/AAAAAD/wAAAAAP/AAAAAH/wAAAAAf/AAAAAP/wAAAAA//AAAAA//wAAAAB//AAAAB//wAAAAD//AAAAB//wAAAAH//AAAAD//wAAAAP//AAAAH//wAAAAf//AAAAH//gAAAA///AAAAP/+AAAAB///AAAAP/4AAAAD///AAAAf/gAAAAH///AAAAf/AAAAAP///AAAAf+AAAAAf///AAAAf+AAAAA//v/AAAA/8AAAAB//P/AAAA/8AAAAD/+P/AAAA/8AAAAH/8P/AAAA/8AAAAP/4P/AAAA/8AAAAf/wf/AAAA/8AAAA//gf/AAAA/8AAAD//Af/AAAA/8AAAH/+Af/AAAA/+AAAP/8Af/AAAA/+AAAf/4Af/AAAAf/AAB//wAf/AAAAf/gAD//gAf/AAAAf/wAf//AAf/AAAAf/+D//+AAf/AAAAP/////8AAf/AAAAP/////4AAf/AAAAH/////wAAf/AAAAH/////gAAf/AAAAD/////AAAf/AAAAB////8AAAf/AAAAA////4AAAf/AAAAAf///wAAAf/AAAAAP///AAAAf/AAAAAD//8AAAAf/AAAAAA//gAAAAP/AAAAAABwAAAAAH/AAAAAAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAfAAAAAAAAHgAAAA/wAAAAAAA/wAAAA/8AAAAAAD/wAAAB/+AAAAAAH/wAAAB//AAAAAAP/wAAAB//gAAAAA//wAAAB//wAAAAB//wAAAB//4AAAAB//wAAAB//8AAAAD//wAAAA//8AAAAH//wAAAAf/+AAAAH//gAAAAH/+AAAAP/+AAAAAB//AAAAP/4AAAAAA//AAAAf/gAAAAAAf/AAAAf/AAAAAAAf/AAAAf/AAAAAAAP/gAAAf+AAAAAAAP/gAAA/+AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAD/gAAH/gAAA/8AAH/wAAH/gAAA/8AAH/wAAP/gAAA/+AAH/wAAP/AAAAf/AAP/4AAf/AAAAf/AAf/4AA//AAAAf/wA//8AB//AAAAf/+H//+AD/+AAAAP//////4f/+AAAAP////v////8AAAAH////v////8AAAAH////H////4AAAAD////H////wAAAAB///+D////wAAAAA///8D////gAAAAAf//4B////AAAAAAP//wA///+AAAAAAD//AAf//4AAAAAAAf4AAH//gAAAAAAAAAAAB/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAAAAAD/gAAAAAAAAAAAAP/wAAAAAAAAAAAAf/wAAAAAAAAAAAA//wAAAAAAAAAAAD//wAAAAAAAAAAAH//wAAAAAAAAAAAP//wAAAAAAAAAAA///wAAAAAAAAAAB///wAAAAAAAAAAD///wAAAAAAAAAAP///wAAAAAAAAAAf///wAAAAAAAAAA//9/wAAAAAAAAAD//x/wAAAAAAAAAH//h/wAAAAAAAAAP/+B/wAAAAAAAAA//8B/wAAAAAAAAB//4B/wAAAAAAAAD//gB/wAAAAAAAAP//AB/wAAAAAAAAf/+AB/wAAAAAAAA//4AB/wAAAAAAAD//wAB/wAAAAAAAH//AAB/wAAAAAAAP/+AAB/wAAAAAAA//8AAB/wAAAAAAB//wAAB/wAAAAAAD//gAAB/wAAAAAAP/+AAAB/wAAAAAAf/8AAAB/wAAAAAAf/5////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAf//////////AAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/wAAAAAAAAAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAPgAAAAAAAAAAAAA/wAAAAAAAAD4AAB/4AAAAAAAD/4AAB/4AAAAAAD//4AAB/8AAAAAD///8AAB/8AAAAD////8AAB/+AAAAf////8AAA/+AAAAf////+AAA/+AAAAf////+AAAf/AAAAf////8AAAf/AAAAf////8AAAP/AAAAf////8AAAP/AAAAf//4/4AAAH/gAAAf/8A/4AAAH/gAAAf+AA/4AAAH/gAAAf+AA/4AAAH/gAAAf+AA/4AAAH/gAAAf+AA/4AAAH/gAAAf+AA/4AAAH/gAAAf+AB/4AAAH/gAAAf+AB/4AAAH/gAAAf+AA/8AAAP/gAAAf+AA/8AAAP/AAAAf+AA/8AAAP/AAAAf+AA/+AAAf/AAAAf+AA/+AAA//AAAAf+AA//AAB/+AAAAf+AAf/gAD/+AAAAf+AAf/4Af/8AAAAf+AAf/////8AAAAf+AAP/////4AAAAf+AAP/////wAAAAf+AAH/////wAAAAf+AAD/////gAAAAf+AAD/////AAAAAf+AAB////+AAAAAf8AAA////8AAAAAf4AAAP///wAAAAAfgAAAH///AAAAAAAAAAAA//8AAAAAAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AAAAAAAAAAAB///AAAAAAAAAAAP///wAAAAAAAAAA////8AAAAAAAAAB////+AAAAAAAAAH/////AAAAAAAAAP/////gAAAAAAAA//////wAAAAAAAB//////4AAAAAAAH//////8AAAAAAAP//////8AAAAAAAf//+AP/+AAAAAAB///4AB/+AAAAAAD///wAA/+AAAAAAH///gAAf/AAAAAAf///AAAP/AAAAAA///+AAAP/AAAAAB///+AAAH/gAAAAH//3+AAAH/gAAAAP//v8AAAH/gAAAAf//P8AAAH/gAAAB//+P8AAAH/gAAAD//4f8AAAH/gAAAH//wf8AAAH/gAAAP//gf8AAAH/gAAAf//Af8AAAH/gAAAf/8Af+AAAH/gAAAf/4Af+AAAP/AAAAf/wAf+AAAP/AAAAf/gAf/AAAf/AAAAf/AAP/gAA//AAAAf8AAP/wAB/+AAAAf4AAP/4AD/+AAAAfwAAP//Af/8AAAAfgAAH/////8AAAAeAAAH/////4AAAAcAAAD/////4AAAAYAAAD/////wAAAAQAAAB/////gAAAAAAAAA/////AAAAAAAAAAf///+AAAAAAAAAAP///8AAAAAAAAAAD///wAAAAAAAAAAB///AAAAAAAAAAAAP/8AAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAf+AAAAAAAAAAAAAf+AAAAAAAAAAAAAf+AAAAAAAAAAAAAf+AAAAAAAAAAAAAf+AAAAAAAABAAAAf+AAAAAAAAHAAAAf+AAAAAAAAfAAAAf+AAAAAAAB/AAAAf+AAAAAAAH/AAAAf+AAAAAAAf/AAAAf+AAAAAAB//AAAAf+AAAAAAH//AAAAf+AAAAAAf//AAAAf+AAAAAB///AAAAf+AAAAAH///AAAAf+AAAAAf///AAAAf+AAAAB///+AAAAf+AAAAH///8AAAAf+AAAAf///wAAAAf+AAAA////AAAAAf+AAAD///8AAAAAf+AAAP///wAAAAAf+AAA////AAAAAAf+AAD///8AAAAAAf+AAP///wAAAAAAf+AA////AAAAAAAf+AD///8AAAAAAAf+AP///wAAAAAAAf+A////AAAAAAAAf+D///8AAAAAAAAf+P///wAAAAAAAAf+f//+AAAAAAAAAf////4AAAAAAAAAf////gAAAAAAAAAf///+AAAAAAAAAAf///4AAAAAAAAAAf///gAAAAAAAAAAf//+AAAAAAAAAAAf//4AAAAAAAAAAAf//gAAAAAAAAAAAf/+AAAAAAAAAAAAf/4AAAAAAAAAAAAf/gAAAAAAAAAAAAf+AAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAAAAAB//gAAAAAAADwAAH//4AAAAAAA//AAf//8AAAAAAD//wA////AAAAAAP//4B////gAAAAAf//+B////wAAAAA////D////wAAAAB////H////4AAAAD////n////8AAAAH/////////8AAAAH/////////+AAAAP//////wP/+AAAAP//////AB/+AAAAf/wD//8AA//AAAAf/AA//4AAf/AAAAf+AAf/4AAP/AAAAf8AAP/wAAH/AAAA/8AAH/wAAH/gAAA/4AAH/gAAH/gAAA/4AAH/gAAD/gAAA/4AAD/gAAD/gAAA/4AAD/gAAD/gAAA/4AAD/gAAD/gAAA/4AAD/gAAD/gAAA/4AAD/gAAD/gAAA/4AAH/gAAH/gAAA/8AAH/wAAH/gAAAf8AAP/wAAH/gAAAf+AAP/wAAP/AAAAf/AAf/4AAf/AAAAf/wB//8AAf/AAAAP/////+AB//AAAAP//////wH/+AAAAH/////////+AAAAH/////////8AAAAD////n////8AAAAB////H////4AAAAA////D////wAAAAAf//+B////wAAAAAP//8B////gAAAAAH//wA////AAAAAAB//AAf//8AAAAAAAH4AAH//4AAAAAAAAAAAB//gAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAf//AAAAAAAAAAAB///wAAAAAAAAAAH///4AAAAAAAAAAP///+AAAAAAAAAAf////AAAAAAAAAA/////AAAADAAAAB/////gAAAHAAAAD/////wAAAPAAAAH/////wAAAfAAAAH/////4AAB/AAAAP//A//4AAD/AAAAP/4AH/8AAH/AAAAf/gAD/8AAP/AAAAf/AAB/8AA//AAAAf+AAA/8AB//AAAAf+AAA/8AD//AAAA/8AAAf8AH//AAAA/8AAAf8Af//AAAA/8AAAf8A//+AAAA/8AAAf8B//+AAAA/8AAAf8D//8AAAA/8AAAf8P//wAAAA/8AAAf8f//gAAAA/8AAAf4//+AAAAA/8AAAf5//8AAAAA/8AAAf3//4AAAAAf+AAA////gAAAAAf+AAB////AAAAAAf/AAB///8AAAAAAf/gAD///4AAAAAAP/4AP///gAAAAAAP//B////AAAAAAAH//////+AAAAAAAH//////4AAAAAAAD//////wAAAAAAAB//////AAAAAAAAA/////+AAAAAAAAAf////4AAAAAAAAAP////wAAAAAAAAAH////AAAAAAAAAAB///8AAAAAAAAAAAf//gAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAD8AAAAAAAA/4AAAP+AAAAAAAB/8AAAf/AAAAAAAB/+AAAf/gAAAAAAD/+AAA//gAAAAAAD//AAA//wAAAAAAD//AAA//wAAAAAAD//AAA//wAAAAAAD//AAA//wAAAAAAD/+AAA//gAAAAAAB/+AAAf/gAAAAAAA/8AAAP/AAAAAAAAf4AAAH+AAAAAAAAHgAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("FCM0NDQ0NDQ0NDQ0GA=="), 90+(scale<<8)+(1<<16)); + // Actual height 70 (69 - 0) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("GBo2NjY2NjY2NjY2Gg=="), + 94+(scale<<8)+(1<<16) + ); + return this; } var boot_img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); @@ -27,6 +28,7 @@ var sunSet = "00:00"; function log_debug(o) { //console.log(o); + } // requires the myLocation app @@ -35,7 +37,16 @@ function loadLocation() { } function loadSettings() { - settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'autoCycle': true}; + settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true,'sideTap':0}; + //sideTap 0 = on | 1 = sidebar1... + + let tmp = require('Storage').readJSON(SETTINGS_FILE, 1) || settings; + for (const key in tmp) { + settings[key] = tmp[key] + } + + if(settings.sideTap!=0) + sideBar=parseInt(settings.sideTap)-1; //tab to show is12Hour = (require("Storage").readJSON(GLOBAL_SETTINGS, 1) || {})["12hour"] || false; } @@ -236,20 +247,11 @@ function drawBattery(x,y,wi,hi) { } -function getSteps() { - if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return '????'; -} - // format steps so they fit in the place function formatSteps() { - var s = getSteps(); + var s = Bangle.getHealthStatus("day").steps; - if ( s == '????') { - return s; - } else if (s < 1000) { + if (s < 1000) { return s + ''; } else if (s < 10000) { return '' + (s/1000).toFixed(1) + 'K'; @@ -258,20 +260,19 @@ function formatSteps() { } function nextSidebar() { + if (++sideBar > 2) sideBar = 0; log_debug("next: " + sideBar); + } function prevSidebar() { + if (--sideBar < 0) sideBar = 2; log_debug("prev: " + sideBar); + } -Bangle.setUI("clockupdown", btn=> { - if (btn<0) prevSidebar(); - if (btn>0) nextSidebar(); - draw(); -}); // timeout used to update every minute @@ -282,7 +283,7 @@ function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; - if (!settings.autoCycle) { + if (settings.autoCycle) { nextSidebar(); } draw(); @@ -301,6 +302,24 @@ Bangle.loadWidgets(); for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} loadSettings(); loadLocation(); + + + +if(settings.autoCycle || settings.sideTap==0) +{ + Bangle.setUI("clockupdown", btn=> { + if (btn<0) prevSidebar(); + if (btn>0) nextSidebar(); + draw(); + }); +} +else{ + Bangle.setUI("clock"); +} + + + + draw(); // queues the next draw for a minutes time Bangle.on('charging', function(charging) { //redraw the sidebar ( with the battery ) diff --git a/apps/rebble/rebble.settings.js b/apps/rebble/rebble.settings.js index 91142d72d..37b7be3a1 100644 --- a/apps/rebble/rebble.settings.js +++ b/apps/rebble/rebble.settings.js @@ -2,12 +2,14 @@ const SETTINGS_FILE = "rebble.json"; // initialize with default settings... - let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true} + let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0}; + //sideTap 0 = on| 1= sideBar1 | 2 = ... // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings const storage = require('Storage') let settings = storage.readJSON(SETTINGS_FILE, 1) || localSettings; + const saved = settings || {} for (const key in saved) { localSettings[key] = saved[key] @@ -21,26 +23,55 @@ var color_options = ['Green','Orange','Cyan','Purple','Red','Blue']; var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f']; - E.showMenu({ - '': { 'title': 'Rebble Clock' }, - '< Back': back, - 'Colour': { - value: 0 | color_options.indexOf(localSettings.color), - min: 0, max: 5, - format: v => color_options[v], - onchange: v => { - localSettings.color = color_options[v]; - localSettings.bg = bg_code[v]; - save(); + function showMenu() + { + const menu={ + '': { 'title': 'Rebble Clock' }, + '< Back': back, + 'Colour': { + value: 0 | color_options.indexOf(localSettings.color), + min: 0, max: 5, + format: v => color_options[v], + onchange: v => { + localSettings.color = color_options[v]; + localSettings.bg = bg_code[v]; + save(); + }, }, - }, - 'Auto Cycle': { - value: "autoCycle" in localSettings ? localSettings.autoCycle : true, - format: () => (localSettings.autoCycle ? 'Yes' : 'No'), - onchange: () => { - localSettings.autoCycle = !localSettings.autoCycle; - save(); + 'Auto Cycle': { + value: localSettings.autoCycle, + onchange: (v) => { + localSettings.autoCycle = v; + save(); + showMenu(); + } } + }; + + if( !localSettings.autoCycle) + { + menu['Tap to Cycle']= { + value: localSettings.sideTap, + min: 0, + max: 3, + step: 1, + format: v => NumberToSideTap(v), + onchange: v => { + localSettings.sideTap=v + save(); + setTimeout(showMenu, 10); + } + }; } - }); -}) + E.showMenu(menu); + } + + function NumberToSideTap(Number) + { + if(Number==0) + return 'on'; + return Number+""; + } + + showMenu(); +}) \ No newline at end of file diff --git a/apps/rebble/suncalc.js b/apps/rebble/suncalc.js new file mode 100644 index 000000000..d86f039c5 --- /dev/null +++ b/apps/rebble/suncalc.js @@ -0,0 +1,143 @@ +/* + (c) 2011-2015, Vladimir Agafonkin + SunCalc is a JavaScript library for calculating sun/moon position and light phases. + https://github.com/mourner/suncalc + + edit for banglejs +*/ + +(function () { 'use strict'; + +// shortcuts for easier to read formulas + +var PI = Math.PI, + sin = Math.sin, + cos = Math.cos, + tan = Math.tan, + asin = Math.asin, + atan = Math.atan2, + acos = Math.acos, + rad = PI / 180; + +// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + + +// date/time constants and conversions + +var dayMs = 1000 * 60 * 60 * 24, + J1970 = 2440588, + J2000 = 2451545; + +function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } +function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } +function toDays(date) { return toJulian(date) - J2000; } + + +// general calculations for position + +var e = rad * 23.4397; // obliquity of the Earth + +function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } + + +// general sun calculations + +function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } + +function eclipticLongitude(M) { + + var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center + P = rad * 102.9372; // perihelion of the Earth + + return M + C + P + PI; +} + +var SunCalc = {}; + + +// sun times configuration (angle, morning name, evening name) + +var times = SunCalc.times = [ + [-0.833, 'sunrise', 'sunset' ], + [ -0.3, 'sunriseEnd', 'sunsetStart' ], + [ -6, 'dawn', 'dusk' ], + [ -12, 'nauticalDawn', 'nauticalDusk'], + [ -18, 'nightEnd', 'night' ], + [ 6, 'goldenHourEnd', 'goldenHour' ] +]; + + + +// calculations for sun times + +var J0 = 0.0009; + +function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } + +function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } +function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } + +function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } +function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } + +// returns set time for the given sun altitude +function getSetJ(h, lw, phi, dec, n, M, L) { + + var w = hourAngle(h, phi, dec), + a = approxTransit(w, lw, n); + return solarTransitJ(a, M, L); +} + + +// calculates sun times for a given date, latitude/longitude, and, optionally, +// the observer height (in meters) relative to the horizon + +SunCalc.getTimes = function (date, lat, lng, height) { + + height = height || 0; + + var lw = rad * -lng, + phi = rad * lat, + + dh = observerAngle(height), + + d = toDays(date), + n = julianCycle(d, lw), + ds = approxTransit(0, lw, n), + + M = solarMeanAnomaly(ds), + L = eclipticLongitude(M), + dec = declination(L, 0), + + Jnoon = solarTransitJ(ds, M, L), + + i, len, time, h0, Jset, Jrise; + + + var result = { + solarNoon: fromJulian(Jnoon), + nadir: fromJulian(Jnoon - 0.5) + }; + + for (i = 0, len = times.length; i < len; i += 1) { + time = times[i]; + h0 = (time[0] + dh) * rad; + + Jset = getSetJ(h0, lw, phi, dec, n, M, L); + Jrise = Jnoon - (Jset - Jnoon); + + result[time[1]] = fromJulian(Jrise); + result[time[2]] = fromJulian(Jset); + } + + return result; +}; + + +// export as Node module / AMD module / browser variable +if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; +else if (typeof define === 'function' && define.amd) define(SunCalc); +else window.SunCalc = SunCalc; + + +}()); \ No newline at end of file diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index de070dbd8..95945be78 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -12,3 +12,4 @@ 0.11: Notifications fixes 0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) +0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working diff --git a/apps/run/app.js b/apps/run/app.js index 19dcd7e88..4038b8c1a 100644 --- a/apps/run/app.js +++ b/apps/run/app.js @@ -55,6 +55,7 @@ function onStartStop() { prepPromises.push( WIDGETS["recorder"].setRecording(true).then(() => { isMenuDisplayed = false; + layout.setUI(); // grab our input handling again layout.forgetLazyState(); layout.render(); }) diff --git a/apps/run/metadata.json b/apps/run/metadata.json index afa52b2f7..933576a5d 100644 --- a/apps/run/metadata.json +++ b/apps/run/metadata.json @@ -1,6 +1,6 @@ { "id": "run", "name": "Run", - "version":"0.13", + "version":"0.14", "description": "Displays distance, time, steps, cadence, pace and more for runners.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps", diff --git a/apps/sched/boot.js b/apps/sched/boot.js index 3020f4945..98bb0ff7d 100644 --- a/apps/sched/boot.js +++ b/apps/sched/boot.js @@ -24,7 +24,7 @@ will then clearInterval() to get rid of this call so it can proceed normally. If active[0].js is defined, just run that code as-is and not alarm.js */ - Bangle.SCHED = setTimeout(require("Storage").read(active[0].js)!==undefined ? active[0].js : 'load("sched.js")',t); + Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t); } else { // check for new alarms at midnight (so day of week works) Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000)); } diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index bfd32a130..6d3bbb468 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -48,3 +48,4 @@ 0.43: Add some Bangle 1 colours to theme customizer 0.44: Add "Start Week On X" option (#1780) UI improvements to Locale and Date & Time menu +0.45: Add calibrate battery option diff --git a/apps/setting/README.md b/apps/setting/README.md index 451b48c06..657b96f71 100644 --- a/apps/setting/README.md +++ b/apps/setting/README.md @@ -13,7 +13,6 @@ This is Bangle.js's settings menu * **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below. * **Theme** Adjust the colour scheme * **Utils** Utilities - including resetting settings (see below) -* **Turn Off** Turn Bangle.js off ## BLE - Bluetooth Settings @@ -61,5 +60,7 @@ The exact effects depend on the app. In general the watch will not wake up by i * **Compact Storage** Removes deleted/old files from Storage - this will speed up your Bangle.js * **Rewrite Settings** Should not normally be required, but if `.boot0` has been deleted/corrupted (and so no settings are being loaded) this will fix it. * **Flatten Battery** Turns on all devices and draws as much power as possible, attempting to flatten the Bangle.js battery. This can still take 5+ hours. +* **Calibrate Battery** If you're finding your battery percentage meter isn't accurate, leave your Bangle.js on charge for at least 3 hours, and then choose this menu option. It will measure the battery voltage when full and will allow Bangle.js to report a more accurate battery percentage. * **Reset Settings** Reset the settings (as set in this app) to defaults. Does not reset settings for other apps. * **Factory Reset** (not available on Bangle.js 1) - wipe **everything** and return to a factory state +* **Turn Off** Turn Bangle.js off diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index 85dddf9db..c5c368fae 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.44", + "version": "0.45", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 9b5bdae68..5c14e914d 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -546,6 +546,18 @@ function showUtilMenu() { var i=1000;while (i--); }, 1); }, + /*LANG*/'Calibrate Battery': () => { + E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate"}).then(ok => { + if (ok) { + var s=require("Storage").readJSON("setting.json"); + s.batFullVoltage = (analogRead(D3)+analogRead(D3)+analogRead(D3)+analogRead(D3))/4; + require("Storage").writeJSON("setting.json",s); + E.showAlert(/*LANG*/"Calibrated!").then(() => load("settings.app.js")); + } else { + E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(() => load("settings.app.js")); + } + }); + }, /*LANG*/'Reset Settings': () => { E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => { if (v) { diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index 73e9bfc40..9e2abb4ef 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -13,3 +13,4 @@ 1.14: Add VMG and coordinates screens 1.43: Adds mirroring of the watch face to an Android device. See README.md 1.49: Droidscript mirroring prog automatically uses last connection address. Auto connects when run. +1.50: Add configuration item Wpt File Suffix. A one character suffix to append to the waypoints.json file. A number of other apps also use this file name. Using the file name suffix allows the speedalt2 waypoints to be retained if one of these other apps is installed for a different use. diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index e1c6b0a5a..c124e0c00 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -78,6 +78,10 @@ Waypoints are used in Distance and VMG modes. Create a file waypoints.json and w The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) +By default the waypoints file is called waypoints.json + +**Note** : The waypoints.json file is used by a number of different gps apps. The setting 'Wpt File Suffix' allows one of waypoints1.json, waypoints2.json or waypoints3.json to be used instead. This allows the other apps to be used with a different set of waypoints without losing the speedalt2 waypoint set. + Sample waypoints.json (My sailing waypoints)
diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js
index ed16131a4..4cdf71913 100644
--- a/apps/speedalt2/app.js
+++ b/apps/speedalt2/app.js
@@ -5,8 +5,9 @@ Mike Bennett mike[at]kereru.com
 1.14 : Add VMG screen
 1.34 : Add bluetooth data stream for Droidscript
 1.43 : Keep GPS in SuperE mode while using Droiscript screen mirroring
+1.50 : Add cfg.wptSfx one char suffix to append to waypoints.json filename. Protects speedalt2 waypoints from other apps that use the same file name for waypoints.
 */
-var v = '1.49';
+var v = '1.50';
 var vDroid = '1.50';    // Required DroidScript program version
 
 /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
@@ -209,7 +210,7 @@ function nxtWp(){
 }
 
 function loadWp() {
-  var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}];
+  var w = require("Storage").readJSON('waypoints'+cfg.wptSfx+'.json')||[{name:"NONE"}];
   if (cfg.wp>=w.length) cfg.wp=0;
   if (cfg.wp<0) cfg.wp = w.length-1;
   savSettings();
@@ -718,6 +719,7 @@ cfg.primSpd = cfg.primSpd||0;    // 1 = Spd in primary, 0 = Spd in secondary
 cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; 
 cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
 cfg.touch = cfg.touch==undefined?true:cfg.touch;
+cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx; 
 
 if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
 if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
diff --git a/apps/speedalt2/metadata.json b/apps/speedalt2/metadata.json
index 4ace46854..2a111af28 100644
--- a/apps/speedalt2/metadata.json
+++ b/apps/speedalt2/metadata.json
@@ -2,7 +2,7 @@
   "id": "speedalt2",
   "name": "GPS Adventure Sports II",
   "shortName":"GPS Adv Sport II",
-  "version":"1.49",
+  "version":"1.50",
   "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
   "icon": "app.png",
   "type": "app",
@@ -15,5 +15,11 @@
     {"name":"speedalt2.img","url":"app-icon.js","evaluate":true},
     {"name":"speedalt2.settings.js","url":"settings.js"}
   ],
-  "data": [{"name":"speedalt2.json"}]
+  "data": [
+    {"name":"speedalt2.json"},
+    {"name":"waypoints.json"},
+    {"name":"waypoints1.json"},
+    {"name":"waypoints2.json"},
+    {"name":"waypoints3.json"}
+  ]
 }
diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js
index babb03061..1bdb58f9d 100644
--- a/apps/speedalt2/settings.js
+++ b/apps/speedalt2/settings.js
@@ -30,6 +30,11 @@
     writeSettings();
   }
 
+  function setSfx(s) {
+    settings.wptSfx = s;
+    writeSettings();
+  }
+
   
   const appMenu = {
     '': {'title': 'GPS Adv Sprt II'},
@@ -38,6 +43,7 @@
     'Units' : function() { E.showMenu(unitsMenu); },
     'Colours' : function() { E.showMenu(colMenu); },
     'Kalman Filter' : function() { E.showMenu(kalMenu); },
+    'Wpt File Suffix' : function() { E.showMenu(sfxMenu); },
     'Touch' : {
        value : settings.touch,
        format : v => v?"On":"Off",
@@ -69,6 +75,15 @@
     'Inverted' : function() { setColour(3); }
   };
   
+  const sfxMenu = {
+    '': {'title': 'Wpt File Suffix'},
+    '< Back': function() { E.showMenu(appMenu); },
+    'Default' : function() { setSfx(''); },
+    '1' : function() { setSfx('1'); },
+    '2' : function() { setSfx('2'); },
+    '3' : function() { setSfx('3'); }
+  };
+  
   const kalMenu = {
     '': {'title': 'Kalman Filter'},
     '< Back': function() { E.showMenu(appMenu); },
diff --git a/core b/core
index 147892754..404e98183 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 147892754eaf50c8581ebfb4d8651b9ec24aa44e
+Subproject commit 404e981834f2e8df9c505a8fab12ae12fe3bd562
diff --git a/lang/it_IT.json b/lang/it_IT.json
index 4bc36ee48..310af8580 100644
--- a/lang/it_IT.json
+++ b/lang/it_IT.json
@@ -192,7 +192,15 @@
         "Notifications": "Notifiche",
         "Scheduler": "Schedulatore",
         "Stop": "Stop",
-        "Min Font": "Dimensione minima del font"
+        "Min Font": "Dimensione minima del font",
+        "White": "Bianco",
+        "Red": "Rosso",
+        "Yellow": "Giallo",
+        "Cyan": "Ciano",
+        "Green": "Verde",
+        "Blue": "Blu",
+        "Black": "Nero",
+        "Show Week Number": "Mostra numero settimana"
     },
     "//2": "App-specific overrides",
     "alarm": {
diff --git a/modules/ClockFace.js b/modules/ClockFace.js
index 25e2430bf..d6c3a2e66 100644
--- a/modules/ClockFace.js
+++ b/modules/ClockFace.js
@@ -66,6 +66,10 @@ ClockFace.prototype.tick = function() {
 };
 
 ClockFace.prototype.start = function() {
+  /* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally
+  .CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then*
+  call setUI. see #1864 */
+  Bangle.CLOCK = 1; 
   Bangle.loadWidgets();
   if (this.init) this.init.apply(this);
   if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d]));
@@ -103,4 +107,4 @@ ClockFace.prototype.redraw = function() {
   this.tick();
 };
 
-exports = ClockFace;
\ No newline at end of file
+exports = ClockFace;
diff --git a/modules/Layout.js b/modules/Layout.js
index 019d63815..43419a397 100644
--- a/modules/Layout.js
+++ b/modules/Layout.js
@@ -65,6 +65,8 @@ Other functions:
 * `layout.debug(obj)` - draw outlines for objects on screen
 * `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
 * `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
+* `layout.setUI()` - (called when module initialised) This sets up input (buttons, touch, etc) with Bangle._setUI
+                     This can be useful if you called E.showMenu/showPrompt/etc and those grabbed input away from layour
 */
 
 
@@ -73,11 +75,10 @@ function Layout(layout, options) {
   // Do we have >1 physical buttons?
   this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
 
-  options = options || {};
-  this.lazy = options.lazy || false;
+  this.options = options || {};
+  this.lazy = this.options.lazy || false;
 
-  var btnList, uiSet;
-  Bangle.setUI(); // remove all existing input handlers
+  var btnList;
   if (process.env.HWVERSION!=2) {
     // no touchscreen, find any buttons in 'layout'
     btnList = [];
@@ -91,48 +92,19 @@ function Layout(layout, options) {
       this.physBtns = 0;
       this.buttons = btnList;
       this.selectedButton = -1;
-      Bangle.setUI({mode:"updown", back:options.back}, dir=>{
-        var s = this.selectedButton, l=this.buttons.length;
-        if (dir===undefined && this.buttons[s])
-          return this.buttons[s].cb();
-        if (this.buttons[s]) {
-          delete this.buttons[s].selected;
-          this.render(this.buttons[s]);
-        }
-        s = (s+l+dir) % l;
-        if (this.buttons[s]) {
-          this.buttons[s].selected = 1;
-          this.render(this.buttons[s]);
-        }
-        this.selectedButton = s;
-      });
-      uiSet = true;
     }
   }
-  if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back});
 
-  if (options.btns) {
-    var buttons = options.btns;
+  if (this.options.btns) {
+    var buttons = this.options.btns;
     this.b = buttons;
     if (this.physBtns >= buttons.length) {
-      // Handler for button watch events
-      function pressHandler(btn,e) {
-        if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
-          this.b[btn].cbl(e);
-        else
-          if (this.b[btn].cb) this.b[btn].cb(e);
-      }
       // enough physical buttons
       let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns);
-      if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch);
-      Bangle.btnWatches = [];
       if (this.physBtns > 2 && buttons.length==1)
         buttons.unshift({label:""}); // pad so if we have a button in the middle
       while (this.physBtns > buttons.length)
         buttons.push({label:""});
-      if (buttons[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
-      if (buttons[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
-      if (buttons[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
       this._l.width = g.getWidth()-8; // text width
       this._l = {type:"h", filly:1, c: [
         this._l,
@@ -149,19 +121,8 @@ function Layout(layout, options) {
       if (btnList) btnList.push.apply(btnList, this._l.c[1].c);
     }
   }
-  if (process.env.HWVERSION==2) {
-
-    // Handler for touch events
-    function touchHandler(l,e) {
-      if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) {
-        if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e);
-      }
-      if (l.c) l.c.forEach(n => touchHandler(n,e));
-    }
-    Bangle.touchHandler = (_,e)=>touchHandler(this._l,e);
-    Bangle.on('touch',Bangle.touchHandler);
-  }
-
+  // Link in all buttons/touchscreen/etc
+  this.setUI();
   // recurse over layout doing some fixing up if needed
   var ll = this;
   function recurser(l) {
@@ -175,16 +136,57 @@ function Layout(layout, options) {
   this.updateNeeded = true;
 }
 
-Layout.prototype.remove = function (l) {
-  if (Bangle.btnWatches) {
-    Bangle.btnWatches.forEach(clearWatch);
-    delete Bangle.btnWatches;
+Layout.prototype.setUI = function() {
+  Bangle.setUI(); // remove all existing input handlers
+
+  var uiSet;
+  if (this.buttons) {
+    // multiple buttons so we'll jus use back/next/select
+    Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{
+      var s = this.selectedButton, l=this.buttons.length;
+      if (dir===undefined && this.buttons[s])
+        return this.buttons[s].cb();
+      if (this.buttons[s]) {
+        delete this.buttons[s].selected;
+        this.render(this.buttons[s]);
+      }
+      s = (s+l+dir) % l;
+      if (this.buttons[s]) {
+        this.buttons[s].selected = 1;
+        this.render(this.buttons[s]);
+      }
+      this.selectedButton = s;
+    });
+    uiSet = true;
   }
-  if (Bangle.touchHandler) {
-    Bangle.removeListener("touch",Bangle.touchHandler);
-    delete Bangle.touchHandler;
+  if (this.options.back && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back});
+  // physical buttons -> actual applications
+  if (this.b) {
+    // Handler for button watch events
+    function pressHandler(btn,e) {
+      if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
+        this.b[btn].cbl(e);
+      else
+        if (this.b[btn].cb) this.b[btn].cb(e);
+    }
+    if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch);
+    Bangle.btnWatches = [];
+    if (this.b[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
+    if (this.b[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
+    if (this.b[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
   }
-};
+  // Handle touch events on new Bangle.js
+  if (process.env.HWVERSION==2) {
+    function touchHandler(l,e) {
+      if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) {
+        if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e);
+      }
+      if (l.c) l.c.forEach(n => touchHandler(n,e));
+    }
+    Bangle.touchHandler = (_,e)=>touchHandler(this._l,e);
+    Bangle.on('touch',Bangle.touchHandler);
+  }
+}
 
 function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
   var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol);
diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts
index 2ef52dcdf..e82c3da3d 100644
--- a/typescript/types/globals.d.ts
+++ b/typescript/types/globals.d.ts
@@ -140,7 +140,7 @@ declare const require: ((module: 'heatshrink') => {
 
 declare const Bangle: {
   // functions
-  buzz: () => void;
+  buzz: (duration?: number, intensity?: number) => Promise;
   drawWidgets: () => void;
   isCharging: () => boolean;
   // events
@@ -158,9 +158,9 @@ declare type Image = {
 };
 
 declare type GraphicsApi = {
-  reset: () => void;
+  reset: () => GraphicsApi;
   flip: () => void;
-  setColor: (color: string) => void; // TODO we can most likely type color more usefully than this
+  setColor: (color: string) => GraphicsApi; // TODO we can most likely type color more usefully than this
   drawImage: (
     image: string | Image | ArrayBuffer,
     xOffset: number,
@@ -169,7 +169,7 @@ declare type GraphicsApi = {
       rotate?: number;
       scale?: number;
     }
-  ) => void;
+  ) => GraphicsApi;
   // TODO add more
 };