diff --git a/README.md b/README.md index ee555cad2..b3da9f685 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,7 @@ and which gives information about the app for the Launcher. // 'clock' - a clock - required for clocks to automatically start // 'widget' - a widget // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js' + // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js' // 'RAM' - code that runs and doesn't upload anything to storage // 'launch' - replacement 'Launcher' // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle 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/ChangeLog b/apps/alarm/ChangeLog index b952b1dcd..b00055334 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -27,3 +27,5 @@ 0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu 0.26: Add support for Monday as first day of the week (#1780) 0.27: New UI! +0.28: Fix bug with alarms not firing when configured to fire only once +0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 0cf1f3d6f..fe0f67dbb 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -37,8 +37,8 @@ function handleFirstDayOfWeek(dow) { return dow; } -// Check the first day of week and update the dow field accordingly. -alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow)); +// Check the first day of week and update the dow field accordingly (alarms only!) +alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); function showMainMenu() { const menu = { @@ -50,7 +50,7 @@ function showMainMenu() { alarms.forEach((e, index) => { var label = e.timer ? require("time_utils").formatDuration(e.timer) - : require("time_utils").formatTime(e.t) + (e.dow > 0 ? (" " + decodeDOW(e)) : ""); + : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""); menu[label] = { value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) @@ -111,8 +111,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) { }, /*LANG*/"Repeat": { value: decodeDOW(alarm), - onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.dow, dow => { - alarm.rp = dow > 0; + onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => { + alarm.rp = repeat; alarm.dow = dow; alarm.t = require("time_utils").encodeTime(time); setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); @@ -158,14 +158,14 @@ function saveAlarm(alarm, alarmIndex, time) { } function saveAndReload() { - // Before saving revert the dow to the standard format - alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); + // Before saving revert the dow to the standard format (alarms only!) + alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); require("sched").setAlarms(alarms); require("sched").reload(); // Fix after save - alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); + alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow)); } function decodeDOW(alarm) { @@ -178,42 +178,51 @@ function decodeDOW(alarm) { : "Once" } -function showEditRepeatMenu(dow, dowChangeCallback) { +function showEditRepeatMenu(repeat, dow, dowChangeCallback) { + var originalRepeat = repeat; var originalDow = dow; - var isCustom = dow > 0 && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; + var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY; const menu = { "": { "title": /*LANG*/"Repeat Alarm" }, - "< Back": () => dowChangeCallback(dow), - /*LANG*/"Once": { // No days set: the alarm will fire once - value: dow == 0, - onchange: () => dowChangeCallback(0) + "< Back": () => dowChangeCallback(repeat, dow), + /*LANG*/"Once": { + // The alarm will fire once. Internally it will be saved + // as "fire every days" BUT the repeat flag is false so + // we avoid messing up with the scheduler. + value: !repeat, + onchange: () => dowChangeCallback(false, EVERY_DAY) }, /*LANG*/"Workdays": { - value: dow == WORKDAYS, - onchange: () => dowChangeCallback(WORKDAYS) + value: repeat && dow == WORKDAYS, + onchange: () => dowChangeCallback(true, WORKDAYS) }, /*LANG*/"Weekends": { - value: dow == WEEKEND, - onchange: () => dowChangeCallback(WEEKEND) + value: repeat && dow == WEEKEND, + onchange: () => dowChangeCallback(true, WEEKEND) }, /*LANG*/"Every Day": { - value: dow == EVERY_DAY, - onchange: () => dowChangeCallback(EVERY_DAY) + value: repeat && dow == EVERY_DAY, + onchange: () => dowChangeCallback(true, EVERY_DAY) }, /*LANG*/"Custom": { value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false, - onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalDow) + onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow) } }; E.showMenu(menu); } -function showCustomDaysMenu(dow, dowChangeCallback, originalDow) { +function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) { const menu = { "": { "title": /*LANG*/"Custom Days" }, - "< Back": () => dowChangeCallback(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) => { @@ -223,7 +232,7 @@ function showCustomDaysMenu(dow, dowChangeCallback, originalDow) { }; }); - menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalDow, dowChangeCallback) + menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback) E.showMenu(menu); } @@ -311,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(); @@ -330,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/alarm/metadata.json b/apps/alarm/metadata.json index b9ce55756..cac837b5e 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.27", + "version": "0.29", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 55f456795..f13ccd95c 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -8,3 +8,4 @@ 0.07: Include charging state in battery updates to phone 0.08: Handling of alarms 0.09: Alarm vibration, repeat, and auto-snooze now handled by sched +0.10: Fix SMS bug diff --git a/apps/android/boot.js b/apps/android/boot.js index fb2101aa4..efd7e7e46 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -3,6 +3,7 @@ Bluetooth.println(""); Bluetooth.println(JSON.stringify(message)); } + var lastMsg; var settings = require("Storage").readJSON("android.settings.json",1)||{}; //default alarm settings @@ -18,7 +19,17 @@ /* TODO: Call handling, fitness */ var HANDLERS = { // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add - "notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); }, + "notify" : function() { + Object.assign(event,{t:"add",positive:true, negative:true}); + // Detect a weird GadgetBridge bug and fix it + // For some reason SMS messages send two GB notifications, with different sets of info + if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") { + // Mutate the other message + event.id = lastMsg.id; + } + lastMsg = event; + require("messages").pushMessage(event); + }, // {t:"notify~",id:int, title:string} // modified "notify~" : function() { event.t="modify";require("messages").pushMessage(event); }, // {t:"notify-",id:int} // remove diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 4051a344b..bf37b8407 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.09", + "version": "0.10", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", 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/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 46eddd32b..c3e7918e7 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -23,3 +23,4 @@ 0.11: New color option: foreground color Improve performance, reduce memory usage Small optical adjustments +0.12: Allow configuration of update interval diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index a6aa1a8b1..48e3a1a1a 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -848,8 +848,8 @@ Bangle.loadWidgets(); // schedule a draw for the next minute setTimeout(function() { - // draw every 60 seconds - setInterval(draw,60000); + // draw in interval + setInterval(draw, settings.updateInterval * 1000); }, 60000 - (Date.now() % 60000)); draw(); diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json index cb6bfcff8..ea00dc347 100644 --- a/apps/circlesclock/default.json +++ b/apps/circlesclock/default.json @@ -21,5 +21,6 @@ "circle2colorizeIcon": true, "circle3colorizeIcon": true, "circle4colorizeIcon": false, - "hrmValidity": 60 + "hrmValidity": 60, + "updateInterval": 60 } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index b16f14c06..c35d99334 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.11", + "version":"0.12", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index 0b9e94aca..fb23f8d5e 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -58,6 +58,16 @@ min: 0, max: 2, format: v => weatherData[v], onchange: x => save('weatherCircleData', weatherData[x]), + }, + /*LANG*/'update interval': { + value: settings.updateInterval, + min: 0, + max : 3600, + step: 30, + format: x => { + return x + 's'; + }, + onchange: x => save('updateInterval', x), } }; E.showMenu(menu); @@ -100,7 +110,7 @@ /*LANG*/'valid period': { value: settings.hrmValidity, min: 10, - max : 600, + max : 1800, step: 10, format: x => { return x + "s"; @@ -117,9 +127,9 @@ /*LANG*/'< Back': ()=>showMainMenu(), /*LANG*/'goal': { value: settings.stepGoal, - min: 2000, + min: 1000, max : 50000, - step: 2000, + step: 500, format: x => { return x; }, @@ -127,9 +137,9 @@ }, /*LANG*/'distance goal': { value: settings.stepDistanceGoal, - min: 2000, - max : 30000, - step: 1000, + min: 1000, + max : 50000, + step: 500, format: x => { return x; }, 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/locale/locales.js b/apps/locale/locales.js index d1ac7687f..de56503fd 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -11,6 +11,8 @@ const speedUnits = { // how many kph per X? "kmh": 1, "kph": 1, "km/h": 1, + "kmt": 1, + "km/tim": 1, "mph": 1.60934, "kts": 1.852 }; diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index d6ad393d6..53157f0d8 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -50,4 +50,4 @@ 0.35: Reset graphics colors before rendering a message (possibly fix #1752) 0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) 0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items - +0.38: Add telegram foss handling diff --git a/apps/messages/app.js b/apps/messages/app.js index aac59e246..745f7d208 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -67,100 +67,6 @@ function saveMessages() { require("Storage").writeJSON("messages.json",MESSAGES) } -function getNotificationImage() { - return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); -} -function getFBIcon() { - return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); -} -function getPosImage() { - return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="); -} -function getNegImage() { - return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="); -} -/* -* icons should be 24x24px with 1bpp colors and 'Transparency to Color' -* http://www.espruino.com/Image+Converter -*/ -function getMessageImage(msg) { - if (msg.img) return atob(msg.img); - var s = (msg.src||"").toLowerCase(); - if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); - if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); - if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); - if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); - if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); - if (s=="facebook") return getFBIcon(); - if (s=="gmail") return getNotificationImage(); - if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); - if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); - if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); - if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); - if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); - if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); - if (s=="mail") return getNotificationImage(); - if (s=="messenger") return getFBIcon(); - if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); - if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); - if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); - if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); - if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); - if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); - if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); - if (s=="sms message") return getNotificationImage(); - if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); - if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); - if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); - if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); - if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); - if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); - if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); - if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); - if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); - if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); - return getNotificationImage(); -} -function getMessageImageCol(msg,def) { - return { - // generic colors, using B2-safe colors - "alarm": "#fff", - "mail": "#ff0", - "music": "#f0f", - "phone": "#0f0", - "sms message": "#0ff", - // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) - // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) - "bibel": "#54342c", - "discord": "#738adb", - "facebook": "#4267b2", - "gmail": "#ea4335", - "google home": "#fbbc05", - "hangouts": "#1ba261", - "home assistant": "#fff", // ha-blue is #41bdf5, but that's the background - "instagram": "#dd2a7b", - "liferando": "#ee5c00", - "messenger": "#0078ff", - "nina": "#e57004", - "outlook mail": "#0072c6", - "post & dhl": "#f2c101", - "signal": "#00f", - "skype": "#00aff0", - "slack": "#e51670", - "snapchat": "#ff0", - "teams": "#464eb8", - "telegram": "#0088cc", - "threema": "#000", - "to do": "#3999e5", - "twitch": "#6441A4", - "twitter": "#1da1f2", - "whatsapp": "#4fce5d", - "wordfeud": "#e7d3c7", - "youtube": "#f00", - }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); -} - function showMapMessage(msg) { active = "map"; var m; @@ -387,7 +293,7 @@ function showMessage(msgid) { var buttons = [ ]; if (msg.positive) { - buttons.push({type:"btn", src:getPosImage(), cb:()=>{ + buttons.push({type:"btn", src:atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="), cb:()=>{ msg.new = false; saveMessages(); cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,true); @@ -396,7 +302,7 @@ function showMessage(msgid) { } if (msg.negative) { if (buttons.length) buttons.push({width:32}); // nasty hack... - buttons.push({type:"btn", src:getNegImage(), cb:()=>{ + buttons.push({type:"btn", src:atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="), cb:()=>{ msg.new = false; saveMessages(); cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,false); @@ -411,7 +317,7 @@ function showMessage(msgid) { {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, - { type:"btn", src:getMessageImage(msg), col:getMessageImageCol(msg), pad: 3, cb:()=>{ + { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, @@ -467,14 +373,14 @@ function checkMessages(options) { g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); if (!msg) return; var x = r.x+2, title = msg.title, body = msg.body; - var img = getMessageImage(msg); + var img = require("messages").getMessageImage(msg); if (msg.id=="music") { title = msg.artist || /*LANG*/"Music"; body = msg.track; } if (img) { var fg = g.getColor(); - g.setColor(getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering + g.setColor(require("messages").getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering .setColor(fg); // only color the icon x += 50; } diff --git a/apps/messages/lib.js b/apps/messages/lib.js index c39c8886c..3f801e101 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -104,3 +104,84 @@ exports.clearAll = function(event) { if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.hide(); } + +exports.getMessageImage = function(msg) { + /* + * icons should be 24x24px with 1bpp colors and 'Transparency to Color' + * http://www.espruino.com/Image+Converter + */ + if (msg.img) return atob(msg.img); + var s = (msg.src||"").toLowerCase(); + if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); + if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); + if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); + if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); + if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); + if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); + if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); + if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); + if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); + if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); + if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); + if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); + if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); + if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); + if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); + if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); + if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); + if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); + if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); + if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); + if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); + if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); + if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); + if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); + if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +}; + +exports.getMessageImageCol = function(msg,def) { + return { + // generic colors, using B2-safe colors + "alarm": "#fff", + "mail": "#ff0", + "music": "#f0f", + "phone": "#0f0", + "sms message": "#0ff", + // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) + // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) + "bibel": "#54342c", + "discord": "#738adb", + "facebook": "#4267b2", + "gmail": "#ea4335", + "google home": "#fbbc05", + "hangouts": "#1ba261", + "home assistant": "#fff", // ha-blue is #41bdf5, but that's the background + "instagram": "#dd2a7b", + "liferando": "#ee5c00", + "messenger": "#0078ff", + "nina": "#e57004", + "outlook mail": "#0072c6", + "post & dhl": "#f2c101", + "signal": "#00f", + "skype": "#00aff0", + "slack": "#e51670", + "snapchat": "#ff0", + "teams": "#464eb8", + "telegram": "#0088cc", + "telegram foss": "#0088cc", + "threema": "#000", + "to do": "#3999e5", + "twitch": "#6441A4", + "twitter": "#1da1f2", + "whatsapp": "#4fce5d", + "wordfeud": "#e7d3c7", + "youtube": "#f00", + }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); +}; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index ab9b03273..fd09fdfe4 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.37", + "version": "0.38", "description": "App to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", 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/mylocation/ChangeLog b/apps/mylocation/ChangeLog index 1239554f0..c14e64ba9 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -4,3 +4,4 @@ 0.04: Fixed issue selecting Frankfurt not saved 0.05: Fixed issue with back option 0.06: renamed source files to match standard +0.07: Move mylocation app into 'Settings -> Apps' diff --git a/apps/mylocation/README.md b/apps/mylocation/README.md index fd597397a..a6a16ce83 100644 --- a/apps/mylocation/README.md +++ b/apps/mylocation/README.md @@ -2,6 +2,8 @@ *Sets and stores GPS lat and lon of your preferred city* +To access, go to `Settings -> Apps -> My Location` + * Select one of the preset Cities or setup through the GPS * Other Apps can read this information to do calculations based on location * When the City shows ??? it means the location has been set through the GPS diff --git a/apps/mylocation/icon.js b/apps/mylocation/icon.js deleted file mode 100644 index b79f5875f..000000000 --- a/apps/mylocation/icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA==")) diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index 16549b2ba..4ab9aa37e 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -2,16 +2,15 @@ "name": "My Location", "shortName":"My Location", "icon": "app.png", - "type": "app", + "type": "settings", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.06", + "version":"0.07", "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ - {"name":"mylocation.app.js","url":"app.js"}, - {"name":"mylocation.img","url":"icon.js","evaluate": true } + {"name":"mylocation.settings.js","url":"settings.js"} ], "data": [ {"name":"mylocation.json"} diff --git a/apps/mylocation/app.js b/apps/mylocation/settings.js similarity index 73% rename from apps/mylocation/app.js rename to apps/mylocation/settings.js index fd5c9cc6d..7033500fa 100644 --- a/apps/mylocation/app.js +++ b/apps/mylocation/settings.js @@ -1,5 +1,4 @@ -Bangle.loadWidgets(); -Bangle.drawWidgets(); +(function(back) { const SETTINGS_FILE = "mylocation.json"; let settings; @@ -18,7 +17,7 @@ function loadSettings() { } } -function save() { +function saveSettings() { settings = s; require('Storage').write(SETTINGS_FILE, settings); } @@ -34,29 +33,29 @@ function setFromGPS() { //console.log("fix from GPS"); s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' }; Bangle.buzz(1500); // buzz on first position - Bangle.setGPSPower(0); - save(); + Bangle.setGPSPower(0, "mylocation"); + saveSettings(); Bangle.setUI("updown", ()=>{ load(); }); - E.showPrompt("Location has been saved from the GPS fix",{ - title:"Location Saved", - buttons : {"OK":1} + E.showPrompt(/*LANG*/"Location has been saved from the GPS fix",{ + title:/*LANG*/"Location Saved", + buttons : {/*LANG*/"OK":1} }).then(function(v) { load(); // load default clock }); }); - Bangle.setGPSPower(1); - E.showMessage("Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running"); + Bangle.setGPSPower(1, "mylocation"); + E.showMessage(/*LANG*/"Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running"); Bangle.setUI("updown", undefined); } function showMainMenu() { //console.log("showMainMenu"); const mainmenu = { - '': { 'title': 'My Location' }, - '< Back': ()=>{ load(); }, - 'City': { + '': { 'title': /*LANG*/'My Location' }, + '< Back': ()=>{ back(); }, + /*LANG*/'City': { value: 0 | locations.indexOf(s.location), min: 0, max: locations.length - 1, format: v => locations[v], @@ -65,14 +64,15 @@ function showMainMenu() { s.location = locations[v]; s.lat = lats[v]; s.lon = lons[v]; - save(); + saveSettings(); } } }, - 'Set From GPS': ()=>{ setFromGPS(); } + /*LANG*/'Set From GPS': ()=>{ setFromGPS(); } }; return E.showMenu(mainmenu); } loadSettings(); showMainMenu(); +}) diff --git a/apps/pokeclk/ChangeLog b/apps/pokeclk/ChangeLog index 09ae62ed3..8e506ce50 100644 --- a/apps/pokeclk/ChangeLog +++ b/apps/pokeclk/ChangeLog @@ -1 +1,2 @@ 0.01: New face :) +0.02: Color image compressed diff --git a/apps/pokeclk/README.md b/apps/pokeclk/README.md index 6a76623c9..a7b3ea6b1 100644 --- a/apps/pokeclk/README.md +++ b/apps/pokeclk/README.md @@ -1,7 +1,17 @@ -# About this Watchface +# Poketch Clock -Based on the electronic device from the Pokemon game. +A clock based on the Poketch electronic device found in Sinnoh -# Features +![](https://user-images.githubusercontent.com/44651387/157491789-1b608c11-8af2-4519-a90f-41b8a58a9a14.png) + +## Features Has a dark mode + +## Requests + +If you have any issues or would like to suggest a feature, click here to send a message -> [here](https://github.com/elykittytee/BangleApps/issues/new?title=Poketch%20Clock%20Bug). + +## Creator + +Eleanor Tayam diff --git a/apps/pokeclk/app.js b/apps/pokeclk/app.js index 8c1a00556..17a487bc0 100644 --- a/apps/pokeclk/app.js +++ b/apps/pokeclk/app.js @@ -8,9 +8,10 @@ const font = "Vector:12"; const locale = require("locale"); var img = { - width : 176, height : 176, bpp : 8, - transparent : 254, - buffer : E.toArrayBuffer(atob("/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+DQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXAAAAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcAAAAXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcAAAAXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwAAABcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NAAAAAABcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXAAAAAAADQ1cXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQBcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwADQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXAANDQ0NDQ1cXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ1cXFxcXFxcXFxcXA0NDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDVxcXFxcXFxcXFxcDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ1cXAAADQ0NDQ0NDQ0NXFwAAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDVxcAAANDQ0NDQ0NDQ1cXAAAXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NAAAAAA0NDQ0NDQ0NDQAAAABcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0AAAAADQ0NDQ0NDQ0NAAAAAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQAAAAAAAA0NDQ0NDQ0NDQAADQ0NDQ0NAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXA0NDQ0NAAAAAAAADQ0NDQ0NDQ0NAAANDQ0NDQ0AXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFwNDQ0NDQ0AAAAAAAANDQ0NDQAAAAAAAAAADQ0NDQBcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDVxcXFxcXFxcXA0NDQ0NDQAAAAAAAA0NDQ0NAAAAAAAAAAANDQ0NAFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcDQ0NDQ0NAAAAAAAADQ0NDQ0NDQ0NDQ0NDQ0NDQ0AXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFwNDQ0NDQ0AAAAAAAANDQ0NDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDVxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ1cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NDQ0NDQ0NDQ0NDVxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcDQ0NDQ0NXFxcDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ1cXFwNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDVxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFwNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ==")) + width : 176, height : 149, bpp : 4, + transparent : -1, + palette : new Uint16Array([25804,806,0,21514]), + buffer : require("heatshrink").decompress((atob("iIA/AH4A/AH4AGgAA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ATgUiIP5IVK/5IVgMSkRX/K5MiiJXJiMSJ/5YJiJX/K7BYHKwJjKAH5MKK/5YWBAZX/K65M/LB4AHJf5XWJX5X/K98RiBM/K/5WtK/5XQgEiKokCkBN/K/5XmkRXEiUiK/5WOK5EjJf75BBZJUBKoshAYRX/K/77aiMQBYYHCK4kSAoRYBK/5X/K9IAFK/5XXiJX/K45QIK35XcK3pXHLASu/LipXPKX5X/AChHKK54A/AH4AUXKgaPAHhXQiBN/AH4A/AH4A/AAY"))) }; var night= { @@ -18,6 +19,7 @@ var night= { transparent : 2, buffer : (atob("ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERABEREREREREAEREREREREREREREREREREREREREREREREREREREREREREREQABERERERHwABEREREREREREREREREREREREREREREREREREREREREREREREQ/xERERER/wERERERERERERERERERERERERERERERERERERERERERERERERH//xERERH//xERERERERERERERERERERERERERERERERERERERERERERERERH//xEREf/xERERERERERERERERERERERERERERERERERERERERERERERERERH///////ERERERERERERERERERERERERERERERERERERERERERERERERERER////////8RERERERERERERERERERERERERERERERERERERERERERERERERH/////////ERERERERERERERERERERERERERERERERERERERERERERERERER////D///DxERERERERERERERERERERERERERERERERERERERERERERERERH////w///w8RERERERERERERERERERERERERERERERERERERERER//ERERER//AA///w/w8REREREREREREREREREREREREREREREREREREREREf////EREf/wAP/wAA8PERERERERERERERERERERERERERERERERERERERERH////xERH/8AD/////DxERERERERERERERERERERERERERERERERERERERER////8REf//////////ERERERERERERERERERERERERERERERERERERERERERERH/8R/////////xERERERERERERERERERERERERERERERERERERERER////////Ef////////////////////////////////////////////////////////////////////////////////////////////////////////////8RERERH////////////xERERERERERERERERERERERERERERERERERERERERERERERH///////////EREREREREREREREREREREREREREREREREREREREREf/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==")) }; + var time= "10:20"; function time() { //numbers @@ -31,16 +33,14 @@ function time() { //numbers const damo = d.getDate(); var dayMonth = mo+"-"+damo; - - + // time require("Font4x5").add(Graphics); isDark(); g.setFontAlign(0,0); //g.setFont("6x8:4x5"); g.setFont("4x5",7); - g.drawString(time, width/2, height/2); - + g.drawString(time, width/2, height/2); // date require("Font4x5").add(Graphics); g.setFontAlign(1,1); @@ -64,19 +64,15 @@ function draw() { //poketch background g.drawImage(night, 0, 25, {scale:2}); //poketch is life } else { - g.drawImage(img, 0, 0); //poketch is life - } - + g.drawImage(img, 0, 25); //poketch is life + } time(); } //program start -// Clear the screen once, at startup g.clear(); -// draw immediately at first draw(); -var secondInterval = setInterval(draw, 1000); -// Stop updates when LCD is off, restart when on +var secondInterval = setInterval(draw, 1000); // Stop updates when LCD is off, restart when on Bangle.on('lcdPower',on=>{ if (secondInterval) clearInterval(secondInterval); secondInterval = undefined; diff --git a/apps/pokeclk/metadata.json b/apps/pokeclk/metadata.json index b7fca3273..433077efe 100644 --- a/apps/pokeclk/metadata.json +++ b/apps/pokeclk/metadata.json @@ -2,7 +2,7 @@ "id": "pokeclk", "name": "Poketch Clock", "shortName":"Poketch Clock", - "version": "0.01", + "version": "0.02", "description": "A clock based on the Poketch electronic device found in Sinnoh", "icon": "app.png", "type": "clock", diff --git a/apps/pokeclk/screenshot (1).png b/apps/pokeclk/screenshot (1).png new file mode 100644 index 000000000..1e17a632b Binary files /dev/null and b/apps/pokeclk/screenshot (1).png differ diff --git a/apps/r2d2clk/ChangeLog b/apps/r2d2clk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/r2d2clk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/r2d2clk/README.md b/apps/r2d2clk/README.md new file mode 100644 index 000000000..cd98b9170 --- /dev/null +++ b/apps/r2d2clk/README.md @@ -0,0 +1,11 @@ +# R2D2 Clock + +A clock with R2D2's shiny metal face on it. :) + +![](screenshot.png) + +## Creator + +Made by [Noah Howard](https://github.com/nh-99) + +Based on [Interlaced Clock](https://github.com/espruino/BangleApps/tree/master/apps/intclock) \ No newline at end of file diff --git a/apps/r2d2clk/app-icon.js b/apps/r2d2clk/app-icon.js new file mode 100644 index 000000000..246df9376 --- /dev/null +++ b/apps/r2d2clk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("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")) \ No newline at end of file diff --git a/apps/r2d2clk/app.js b/apps/r2d2clk/app.js new file mode 100644 index 000000000..a7ead76f1 --- /dev/null +++ b/apps/r2d2clk/app.js @@ -0,0 +1,67 @@ +Graphics.prototype.setFontUndo = function(scale) { + // Actual height 19 (20 - 2) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqKAqooCqigKqKAAAACgAAKAAAoAACgAAAAAAAAACgAAKAAAoAACgAAAAAAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAKqqAqqoCqqgKqqAKCgAoKAAAAAKgoAqCgKqKAqooKiioqKKiooqKiioKKqAoqoCgqAKCoAAAACoCgKgKAiCoCIKgKioAqKgACoAAKgACoAAKgACoqAKioCoIgKgiAoCoCgKgAAAAKqgAqqAKqqAqqoKiioqKKiooqKiioKAKAoAoCgCgKAKAAAACgAAKAAAoAACgAAAAAAKqgAqqAKqqAqqoCgCgKAKAAAACgCgKAKAqqoCqqgCqoAKqgAAAACigAKKAAqoACqgAqoACqgAKqAAqoAAqoACqgAKKAAooAAAAAAoAACgAAKAAAoAAqqACqoAKqgAqqAAKAAAoAACgAAKAAAAAAACoAAKgAAqAACoAAAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAAAAACgAAKAAAoAACgAAAAAAoAACgAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAqAACoAAKAAAoAAAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoAAAAKCqAoKoCiqgKKqAoooCiigKKKAoooCqigKqKAKgoAqCgAAAAoAoCgCgKAKAoAoCiigKKKAoooCiigKqqAqqoAqqACqoAAAACqAAKoAAqoACqgAAKAAAoAACgAAKAAqqoCqqgKqqAqqoAAAAKoKAqgoCqigKqKAoooCiigKKKAoooCiqgKKqAoKgCgqAAAAAKqgAqqAKqqAqqoCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKAAAoAACgAAKAAAqqoCqqgCqqAKqoAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCqqgKqqAKqgAqqAAAAAKgAAqAAKqAAqoACigAKKAAooACigAKqqAqqoAqqgCqqAAAACgCgKAKAoAoCgCgAAAAoAqCgCoKAKgoAqAAAAAKAAAoAAKoAAqgAKqgAqqAKgqAqCoCgCgKAKAAAAAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAoKACgoAKCgAAAAKAKAoAoCoKgKgqAKqgAqqAAqgACqAACgAAKAAAAACgAAKAAAoAACgAAKKKAoooCiigKKKAqoACqgACoAAKgAAAAACqqAKqoCqqgKqqAoAACgAAKCoAoKgCiqgKKqAoooCiigKqqAqqoAqqACqoAAAAAqqgCqqAqqoCqqgKKAAooACigAKKAAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAoooCiigKKKAoooCqqgKqqAKCgAoKAAAAAKqgAqqAKqqAqqoCgCgKAKAoAoCgCgKAKAoAoCgCgKAKAAAACqqgKqqAqqoCqqgKAKAoAoCoKgKgqAKqgAqqAAqgACqAAAAACqoAKqgCqqgKqqAoooCiigKKKAoooCgCgKAKAoAoCgCgAAAAKqoAqqgKqqAqqoCigAKKAAooACigAKAAAoAACgAAKAAAAAAAqqACqoAqqoCqqgKAKAoAoCiigKKKAoqoCiqgKKqAoqoAAAAKqqAqqoCqqgKqqAAoAACgAAKAAAoACqqgKqqAqqoCqqgAAAAoAoCgCgKAKAoAoCqqgKqqAqqoCqqgKAKAoAoCgCgKAKAAAAAAKAAAoAACoAAKgAAKAAAoAACgAAKAqqoCqqgKqoAqqgAAAAKqqAqqoCqqgKqqACqAAKoACqoAKqgCoKgKgqAoAoCgCgAAAAqqgCqqAKqqAqqoAACgAAKAAAoAACgAAKAAAoAACgAAKAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAKgAAqAAKqqAqqoCqqgKqqAAAACqqgKqqAqqoCqqgCoAAKgAAKgAAqAAqqoCqqgKqqAqqoAAAACqoAKqgCqqgKqqAoAoCgCgKAKAoAoCqqgKqqAKqgAqqAAAAAqqoCqqgKqqAqqoCigAKKAAooACigAKqAAqoAAqAACoAAAAAAqqACqoAqqoCqqgKAKAoAoCgKgKAqAqqoCqqgCqqAKqoAAAAKqqAqqoCqqgKqqAooACigAKKgAoqACqqgKqqAKioAqKgAAAAKgoAqCgKqKAqooCiigKKKAoooCiigKKqAoqoCgqAKCoAAAACgAAKAAAoAACgAAKqqAqqoCqqgKqqAoAACgAAKAAAoAAAAAAKqoAqqgCqqgKqqAAAoAACgAAKAAAoCqqgKqqAqqgCqqAAAAAqqACqoAKqoAqqgAAKgAAqAACoAAKgKqoAqqgCqoAKqgAAAACqqgKqqAqqoCqqgACoAAKgACoAAKgAAKgAAqAKqqAqqoCqqgKqqAAAACoKgKgqAqqoCqqgAqgACqAAKoAAqgAqqoCqqgKgqAqCoAAAAKoAAqgACqgAKqAAAqoACqgAKqAAqoCqgAKqAAqgACqAAAAAAoCoCgKgKCqAoKoCiqgKKqAqooCqigKoKAqgoCoCgKgKAAAACqqgKqqAqqoCqqgKAKAoAoAAAAKAAAoAACoAAKgAAKgAAqAAAqAACoAACoAAKgAAKgAAqAAAqAACoAACgAAKAAAACgCgKAKAqqoCqqgKqqAqqoAAA"), 32, atob("DQULDw0RDQUHBw0NBQ0FEQ0FDQ0NDQ0NDQ0FBQsNCw0RDQ0NDQ0NDQ0NDQ0NDw0NDQ0NDQ0NDQ8NDQ0HEQc="), 22+(scale<<8)+(1<<16)); + return this; +}; + +var IMAGEWIDTH = 117; +var IMAGEHEIGHT = 60; +var r2d2 = require("heatshrink").decompress(atob("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")); + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + //reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); + // draw r2d2 + g.drawImage(r2d2, (g.getWidth()/2)-(IMAGEWIDTH/2), g.getHeight()-IMAGEHEIGHT); + + var x = g.getWidth()/2; + var y = g.getHeight()/2 - 30; + g.reset(); + // work out locale-friendly date/time + var date = new Date(); + var timeStr = require("locale").time(date,1).trim(); + var dateStr = require("locale").date(date).toUpperCase(); + + + // draw time + g.setFontAlign(0,0).setFont("Undo:3"); + g.clearRect(0,y-30,g.getWidth(),y+30); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += 40; + g.setFontAlign(0,0).setFont("Undo"); + g.clearRect(0,y-10,g.getWidth(),y+20); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/r2d2clk/app.png b/apps/r2d2clk/app.png new file mode 100644 index 000000000..94502501d Binary files /dev/null and b/apps/r2d2clk/app.png differ diff --git a/apps/r2d2clk/metadata.json b/apps/r2d2clk/metadata.json new file mode 100644 index 000000000..249180ac8 --- /dev/null +++ b/apps/r2d2clk/metadata.json @@ -0,0 +1,16 @@ +{ "id": "r2d2clk", + "name": "R2D2 Clock", + "shortName":"R2D2 Clock", + "version":"0.01", + "description": "A clock with R2D2's shiny metal face on it. :)", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"r2d2clk.app.js","url":"app.js"}, + {"name":"r2d2clk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/r2d2clk/screenshot.png b/apps/r2d2clk/screenshot.png new file mode 100644 index 000000000..f0b3ca84f Binary files /dev/null and b/apps/r2d2clk/screenshot.png differ 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('AH4AMgfABZM/BZMB/4WJg/+BZMf/ALJ//gIpP/wAugLpUAvyBKsDC/ACKYJQIKYJgaYKv6YJh7HJeoP8VxLSJg//+D0JIhMf/7RIf4JPJv//LX5a6CwLvJn5aJLYIKJgY4IADn/KpKvBAAKvIAARiGBQanGOwILJBQgLFFogvGIgZHGWAIAEdwg5FNYreBAAjvDeoIAFYQcfBYy3DEQRKEKQQiCAoRiCIogoDCIJGDEQLlEIwZoBCwYLCHQQoBQwgGEj7aFGoKuDKwYSFE4LZFv41Ch6dEIITICn5FEDwQuDeAwuEBQgeEB4b8EFwbADNIZdaHQoSBFwUfNIoGEv5GFXYpGEIoJBCZgjZGHQILDCwIpDj//GgQoBMggcBAApkDBQwiDDoQAEEQY0BERJGBERBGCERC8BBYrYFBQj8FLwrBGBQbkFEYoKFBYgtFL4jLFZ4gKJAH4AciALKRA73DbIgAFj/ABZLOGEQjDEj40En6tEv4oDgLPEAoLRFCIcHDgouJDgP4FxAiFFwt//xXEFwcDEQouEj4iEFwv/EQguEEQJ6EFwgiBS4guE/5uEFwiiBAAyiDBQwdDCw4uCIoIAGFwSLBF34unAAy7EAAy7EAAzqEAArqEF34ukAH4AGgfgNJWAAod8Cwn+SQn4RggFEv4oE/4FDg//FAYFFn4oEAoidBFAYFFh//YIYFBFwd//7BDAoIuCgf/YIYFBFwcfFAgFFDgIoDDgIFCEQpcBFwZFFn4uEAoJcEFwYFBLgouDQoo/BAwcf/hcEFwgiELgPfFwQRBEQYVBFwcPDYYzB+YSDn55DKwOPFwgbCKwP8CQYuBXIouEKIZcBIIgbF/BBEDYZcB4ASFDYI5BCgIuEHQSzCFwo6CeYQuEv4nBOYIPBFwa7Ddoa7FJoLtCFwhNBAAQfBFwiTBAAXAT4oKDCYSfFAAQ9BFwg6BAAQHBFwhDCLgQuFIwY5BFwhGDDwT9FOQI5CFwpSDDoYuDBYQWCFwoLCAgQuFCIsHFwgAFh4uEAH4AWjgLKvwGFj6LDP4sBcgjhCCwaGDn4LEgKjDAgKXEh61Dg7LEdQIuDj7AEZgIpDfYPACIgdCFwLjDdIQRCFwIoDEQJdEFAgiBJgYoEEQoLCAoRFFBYRjCFAIWDQII0Dv6SFv40CRYg1DHQRXBBQg1BFISpDBwQSEEQTQDj4SCDYJKBh42Cv4uCh4TCn4aBIIIuDCYIHBDQIeBFwYPBg4aCe4YPDfAYuHv4uNLo6bBLpJ4EFwYTBEQIHBCQYbBHQIqBEwIGCXYl/IQTwDD4P+CwIfBFILCCBAQACwACBEQQQBAArlDn4LGcoY3BGAIlEHQYAB+YiGMQIAB54DCOgRGD/0fEQpGD+A+CEQZ6BLYhFEKQX8HwYKDBYXgHwQ5DBYQpBBYQ5DHYRWDUQQAGgK5DADsBBZUfb4IAIOYoAETgJcFAAbLBBRBoBUQg5FRYxQDRYJGIZQQ5KFxDtCFxDpCFw7dIfAouICwQuHHIP+FxBQB8YuHf4UPFw6KCn4uGKAWAFw6KB/glBHJHAFw5QCQQIuGRQLzBFww5CKgRQH/A9BFwxQCFw45BCYQuGKAI5BFwwGBKAIuHRQRVCFwhQDFw6KBKAIuHfwQAEGAYKGGgbQCAAowCFwIAGF34ugAAjqHTojqFfQrqFcYoWJF0f+CxMH8ALJAEkCBZU8BRMB/CCKOw0DA4V/OwqhBA4IDBwAKFVoTlBBQytCn6xDBQX/IQQDDAgIACSwIRBTQQWDGwUHHQYzBAAK5CHQk/Fwo6EFwppBNoQuGgIPDFwYeCOoguC34eCh74DEASMCCQI+CDYQCBCQYuDDYMPFwQ6BFwYbBn4uCg4uE8ASBFwUfFwqIBCQV/FwsfLpAbBPgZdFFwpdGFwhdHDwQPELoYeCHwYbD/46CAYaMEBwLqFFwRGCv5RDFYUfBYIWBGQQuDv7iDMIQuCNIIADCwQuCfIgiDFwT5DEQYuDHQIiFVAc/EQyJDIwYiDc4RGDNAYuBCAJGDRYQHBCAQLDCwcPCAR+BHIgAEBYQKHEYQtDAH4Ak/gKJZALMBRhLGDAAjSGWYgLCEY7qDBYwtCXhBEBewzpF/5fGj4LDdYwKD//gKBBeHKAZGGHIX+gJGGKAQfBHQoSBCYQEB+A5GA4InBHQiJEQgKKGOIUPHQg5CFQU/HQaKDVgR1ERQQeCIwK8DBQPvDwUHFwZQB/0/DwUfFwaKB+IeDv4PCHIWHFw45B/geDFwjBCDwYPDEQKsCLoxFB+CIDCQIPCP4OAj6MCj4uEBAN/FQV/SAS0CFwIqBXYioCA4ZYBVwYbBHoIaCQAY+CHoPACwKADGwa+CEQcPFQIfBAARVCgE+dgiGCBYRVCHQLiFganEEQsIZQgiFAAZFGAAZGDNAYADcQSLDAAhSCVwYLHHI4LCCxC5FAH4AIJhRYBXgQAGh5vJgE/VI4uDSRAuJoAuJg4uKvguJg/wFxN/OAQuGaoIuJv/8FxAWBFxN/T4YuFCwIuJCwIuICwQuICwIuICwQGDFwgWCEQQuECwQpDFwk/BQIdDFwYPBCwguECwwuDCw4uDCw4uCCw4uDCw4uCCxAuCCxAuBCwYKEFwQWCRIYuD8YWIEAO/CxEPCoQWGLQYWHFwIWJJ4YWHFwYKGFwYWHFwYKHFwQWIFwQKHFwQWIFwQKIFwIWJdQQuJ8ALJAH8f/BuK/gIFv6RDBYqlBwEBSIIjFA4OAWgSSEA4WAv4LGA4TXC//Ab4v+j4LCwBYDAwP8DQTNEAwXzAYTCDFQfvAYRSDFQYADIwYqDAAZGCEQYAB8A6ENARHCDoI6DAgKKCD4N/HQQIB8ACBCYQGBAYMHE4IxBIQIPBHQU/DYIOBA4ISCDYQHBh4iCh7ICD4IaEAYJpCB4d/GwQuEGwasBDwYPBA4MHFw4HCj4uHA4QuULqyUDRgxCCRhC0Cn46CEwYbB+DhCYQa7DAAQyBcoIaBdQoLBawYrCAApRCHQILGKIT/C//7Eoh1DAAPvAYRRCIwkfEQpGD/AyDBQSBBCQQiGKQX+HwYiDKQXwGQRFDBYYyDNAYLCAwILCBQg+FHIgAEC4IKIQwKtCAH4AWnwKJPoKrEOAi3GaY4WJ/6KHW4ShIfwTbFAAMDCwX8A4UYHIrQE8AiFeYcHHwQiDKQZ6DEQZSCgYmDEQZGCj4uCEQQZBCYRtDNAPAg46Cg5hDv5aBBYI6Bn4aCRYInBDQIpCFwQTBGwQaBGQIuCn59Cn4uBSAgbDHoYuCE4JlCEwJjBCQUPEQUH/hjCFwaUCj/wHIKzDSgd/4AWBQAhhDcYTpDFwg5BUYYuE8Y5ELoufHIhdFaoguBYYbJESgjWDGgQHCH4IiDBQZZBCIIiCKAa7CIwIWCKAbPC8AWCKAZpCCgRQFIQhQGHQQADKAhOEKApGDAARQEIwZQHIwpQFBYpQFKQgWHPwYWHBYQWIEYREGL4YKJAH4AegIEDsCxGPIfgCwr/Dn6nFh6jCgKcGn/wEQQbDXgYqCn/4BQkDDwYPDFzV/JoUfB4RdOgI1DnjG/ACoA='))), + 46, + atob("GBo2NjY2NjY2NjY2Gg=="), + 94+(scale<<8)+(1<<16) + ); + return this; } var 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/rndmclk/metadata.json b/apps/rndmclk/metadata.json index e837c4bce..bb8e92f95 100644 --- a/apps/rndmclk/metadata.json +++ b/apps/rndmclk/metadata.json @@ -6,7 +6,7 @@ "icon": "rndmclk.png", "type": "widget", "tags": "widget,clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"rndmclk.wid.js","url":"widget.js"} 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/ChangeLog b/apps/sched/ChangeLog index 9c2f68348..e003248a3 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -6,6 +6,7 @@ 0.06: Refactor some methods to library 0.07: Update settings Correct `decodeTime(t)` to return a more likely expected time -0.08: add day of week check to getActiveAlarms() +0.08: Add day of week check to getActiveAlarms() 0.09: Move some functions to new time_utils module 0.10: Default to sched.js if custom js not found +0.11: Fix default dow diff --git a/apps/sched/boot.js b/apps/sched/boot.js index 06dc4f308..98bb0ff7d 100644 --- a/apps/sched/boot.js +++ b/apps/sched/boot.js @@ -8,12 +8,12 @@ var time = new Date(); var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000); var d = time.getDate(); - var active = alarms.filter( - a=>a.on && // enabled - a.last!=d && // not already fired today - a.t+60000>currentTime && // is not in the past by >1 minute - (a.dow>>time.getDay())&1 && // is allowed on this day of the week - (!a.date || a.date==time.toISOString().substr(0,10)) // is allowed on this date + var active = alarms.filter(a => + a.on // enabled + && (a.last != d) // not already fired today + && (a.t + 60000 > currentTime) // is not in the past by >1 minute + && (a.dow >> time.getDay() & 1) // is allowed on this day of the week + && (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date ); if (active.length) { active = active.sort((a,b)=>a.t-b.t); // sort by time @@ -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/sched/lib.js b/apps/sched/lib.js index 063402e3d..315e4e387 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -11,16 +11,19 @@ exports.getAlarm = function(id) { return exports.getAlarms().find(a=>a.id==id); }; // Given a list of alarms from getAlarms, return a list of active alarms for the given time (or current time if time not specified) -exports.getActiveAlarms = function(alarms, time) { +exports.getActiveAlarms = function (alarms, time) { if (!time) time = new Date(); - var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000) - +10000;// get current time - 10s in future to ensure we alarm if we've started the app a tad early - return alarms.filter(a=>a.on - &&(a.t> (time).getDay() & 1) - .sort((a,b)=>a.t-b.t); + // get current time 10s in future to ensure we alarm if we've started the app a tad early + var currentTime = (time.getHours() * 3600000) + (time.getMinutes() * 60000) + (time.getSeconds() * 1000) + 10000; + return alarms + .filter(a => + a.on // enabled + && (a.last != time.getDate()) // not already fired today + && (a.t < currentTime) + && (a.dow >> time.getDay() & 1) // is allowed on this day of the week + && (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date + ) + .sort((a, b) => a.t - b.t); } // Set an alarm object based on ID. Leave 'alarm' undefined to remove it exports.setAlarm = function(id, alarm) { @@ -66,7 +69,7 @@ exports.newDefaultAlarm = function () { on: true, rp: settings.defaultRepeat, as: settings.defaultAutoSnooze, - dow: settings.defaultRepeat ? 0b1111111 : 0b0000000, + dow: 0b1111111, last: 0, vibrate: settings.defaultAlarmPattern, }; diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index c3fc7ca8a..76341a7ad 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.10", + "version": "0.11", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index bfd32a130..eca2b7938 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -48,3 +48,5 @@ 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 +0.46: Fix regression after making 'calibrate battery' only for Bangle.js 2 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..183290a85 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.44", + "version": "0.46", "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..150251e7d 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -545,8 +545,22 @@ function showUtilMenu() { setInterval(function() { var i=1000;while (i--); }, 1); - }, - /*LANG*/'Reset Settings': () => { + } + }; + if (BANGLEJS2) + menu[/*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")); + } + }); + }; + menu[/*LANG*/'Reset Settings'] = () => { E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => { if (v) { E.showMessage('Resetting'); @@ -554,9 +568,9 @@ function showUtilMenu() { setTimeout(showMainMenu, 50); } else showUtilMenu(); }); - }, - /*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() } - }; + }; + menu[/*LANG*/'Turn Off'] = ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }; + if (Bangle.factoryReset) { menu[/*LANG*/'Factory Reset'] = ()=>{ E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((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/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog
index b752c829d..ce31583e9 100644
--- a/apps/terminalclock/ChangeLog
+++ b/apps/terminalclock/ChangeLog
@@ -3,3 +3,4 @@
 0.03: Add Banglejs 1 compatibility
 0.04: Fix settings bug
 0.05: Add altitude display (only Bangle.js 2)
+0.06: Add power related settings to control the HR and pressure(altitude) sensor from the watchface
diff --git a/apps/terminalclock/README.md b/apps/terminalclock/README.md
index c7452397d..93967e8a7 100644
--- a/apps/terminalclock/README.md
+++ b/apps/terminalclock/README.md
@@ -8,3 +8,8 @@ It can display :
 - hrm
 - motion
 - steps
+
+
+"Power saving" setting control the HR and pressure (altitude) sensors. 
+If "Off" they will always be on. 
+If "On" the sensors will be turned on every "Power on interval" minutes for 45 secondes
diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js
index 61861f745..7dc3bf1d1 100644
--- a/apps/terminalclock/app.js
+++ b/apps/terminalclock/app.js
@@ -3,15 +3,14 @@ var fontColor = g.theme.dark ? "#0f0" : "#000";
 var heartRate = 0;
 var altitude = -9001;
 
-// handling the differents versions of the Banglejs smartwatch
+// handling the differents versions of the Banglejs smartwatch screen sizes
 if (process.env.HWVERSION == 1){
   var paddingY = 3;
   var font6x8At4Size = 48;
   var font6x8At2Size = 27;
   var font6x8FirstTextSize = 6;
   var font6x8DefaultTextSize = 3;
-}
-else{
+} else{
   var paddingY = 2;
   var font6x8At4Size = 32;
   var font6x8At2Size = 18;
@@ -66,7 +65,7 @@ function drawDate(now, pos){
   drawLine(locale_date, pos);
 }
 
-function drawInput(now, pos){
+function drawInput(pos){
   clearField(pos);
   drawLine(">", pos);
 }
@@ -129,16 +128,52 @@ function draw(){
     drawStepCount(curPos);
     curPos++;
   }
-  drawInput(now, curPos);
+  drawInput(curPos);
 }
 
+function turnOnServices(){
+  if(settings.showHRM){
+    Bangle.setHRMPower(true, "terminalclock");
+  }
+  if(settings.showAltitude && process.env.HWVERSION != 1){
+    Bangle.setBarometerPower(true, "terminalclock");
+  }
+  if(settings.powerSaving){
+    setTimeout(function () {
+      turnOffServices();
+    }, 45000);
+  }
+}
+
+function turnOffServices(){
+  if(settings.showHRM){
+    Bangle.setHRMPower(false, "terminalclock");
+  }
+  if(settings.showAltitude && process.env.HWVERSION != 1){
+    Bangle.setBarometerPower(false, "terminalclock");
+  }
+}
+
+var unlockDrawIntervalID = -1;
+Bangle.on('lock', function(on){
+  if(!on){ // unclock
+    if(settings.powerSaving){
+      turnOnServices();
+    }
+    unlockDrawIntervalID = setInterval(draw, 1000); // every second
+  }
+  if(on && unlockDrawIntervalID != -1){ // lock
+    clearInterval(unlockDrawIntervalID);
+  }
+});
+
 Bangle.on('HRM',function(hrmInfo) {
   if(hrmInfo.confidence >= settings.HRMinConfidence)
     heartRate = hrmInfo.bpm;
 });
 
-var MEDIANLENGTH = 20;
-var avr = [], median;
+var MEDIANLENGTH = 20; // technical
+var avr = [], median; // technical
 Bangle.on('pressure', function(e) {
   while (avr.length>MEDIANLENGTH) avr.pop();
   avr.unshift(e.altitude);
@@ -161,18 +196,20 @@ var settings = Object.assign({
   showActivity: true,
   showStepCount: true,
   showAltitude: process.env.HWVERSION != 1 ? true : false,
+  powerSaving: true,
+  PowerOnInterval: 15,
 }, require('Storage').readJSON("terminalclock.json", true) || {});
 
-if(settings.showAltitude && process.env.HWVERSION != 1){
-  Bangle.setBarometerPower(true, "app");
+// turn the services before drawing anything
+turnOnServices();
+if(settings.powerSaving){
+  setInterval(turnOnServices, settings.PowerOnInterval*60000); // every PowerOnInterval min
 }
-
 // Show launcher when middle button pressed
 Bangle.setUI("clock");
-// Load widgets
+// Load and draw widgets
 Bangle.loadWidgets();
 Bangle.drawWidgets();
 // draw immediately at first
 draw();
-
-var secondInterval = setInterval(draw, 10000);
+setInterval(draw, 10000); // every 10 seconds
diff --git a/apps/terminalclock/metadata.json b/apps/terminalclock/metadata.json
index 7bc00bca4..9f76ed8f2 100644
--- a/apps/terminalclock/metadata.json
+++ b/apps/terminalclock/metadata.json
@@ -3,7 +3,7 @@
   "name": "Terminal Clock",
   "shortName":"Terminal Clock",
   "description": "A terminal cli like clock displaying multiple sensor data",
-  "version":"0.05",
+  "version":"0.06",
   "icon": "app.png",
   "type": "clock",
   "tags": "clock",
diff --git a/apps/terminalclock/settings.js b/apps/terminalclock/settings.js
index 4b09aad6a..bd860b491 100644
--- a/apps/terminalclock/settings.js
+++ b/apps/terminalclock/settings.js
@@ -8,6 +8,8 @@
     showHRM: true,
     showActivity: true,
     showStepCount: true,
+    powerSaving: true,
+    PowerOnInterval: 15,
   }, require('Storage').readJSON(FILE, true) || {});
 
   function writeSettings() {
@@ -65,10 +67,29 @@
         settings.showStepCount = v;
         writeSettings();
       }
+    },
+    'Power saving': {
+      value: settings.powerSaving,
+      format: v => v?"On":"Off",
+      onchange: v => {
+        settings.powerSaving = v;
+        writeSettings();
+      }
+    },
+    'Power on interval': {
+      value: settings.PowerOnInterval,
+      min: 3, max: 60,
+      onchange: v => {
+        settings.PowerOnInterval = v;
+        writeSettings();
+      },
+      format: x => {
+          return x + " min";
+      }
     }
   }
   if (process.env.HWVERSION == 1) {
     delete menu['Show Altitude']
   }
   E.showMenu(menu);
-})
+})
\ No newline at end of file
diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js
index 850b793f4..81c0f75ac 100755
--- a/bin/sanitycheck.js
+++ b/bin/sanitycheck.js
@@ -65,7 +65,7 @@ const APP_KEYS = [
 const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports'];
 const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
 const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports'
-const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale"]; // values allowed for "type" field
+const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale","settings"]; // values allowed for "type" field
 const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
 const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
 const GRANDFATHERED_ICONS = ["s7clk",  "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
@@ -140,7 +140,7 @@ apps.forEach((app,appIdx) => {
           ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`);
         if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency))
           ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES);
-          
+
       });
     } else
       ERROR(`App ${app.id} 'dependencies' must be an object`);
diff --git a/core b/core
index 147892754..2054537a9 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 147892754eaf50c8581ebfb4d8651b9ec24aa44e
+Subproject commit 2054537a9958f9812ae2cad908b6597ff01e449d
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..19cfabe11 100644
--- a/modules/Layout.js
+++ b/modules/Layout.js
@@ -1,83 +1,16 @@
 /* Copyright (c) 2022 Bangle.js contributors. See the file LICENSE for copying permission. */
-/*
-Take a look at README.md for hints on developing with this library.
-Usage:
-```
-var Layout = require("Layout");
-var layout = new Layout( layoutObject, options )
-layout.render(optionalObject);
-```
-For example:
-```
-var Layout = require("Layout");
-var layout = new Layout( {
-  type:"v", c: [
-    {type:"txt", font:"20%", label:"12:00" },
-    {type:"txt", font:"6x8", label:"The Date" }
-  ]
-});
-g.clear();
-layout.render();
-```
-layoutObject has:
-* A `type` field of:
-  * `undefined` - blank, can be used for padding
-  * `"txt"` - a text label, with value `label`. 'font' is required
-  * `"btn"` - a button, with value `label` and callback `cb`
-       optional `src` specifies an image (like img) in which case label is ignored
-       Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields.
-  * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw.
-  * `"custom"` - a custom block where `render(layoutObj)` is called to render
-  * `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
-  * `"v"` - Vertical layout, `c` is an array of more `layoutObject`
-* A `id` field. If specified the object is added with this name to the
-  returned `layout` object, so can be referenced as `layout.foo`
-* A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`.
-* A `scale` field, eg `2` to set scale of an image
-* A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
-* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height`
-  and `fillx`/`filly` to be set. Not compatible with text rotation.
-* A `col` field, eg `#f00` for red
-* A `bgCol` field for background color (will automatically fill on render)
-* A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
-* A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
-* A `pad` integer field to set pixels padding
-* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
-* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
-* `width` and `height` fields to optionally specify minimum size
-options is an object containing:
-* `lazy` - a boolean specifying whether to enable automatic lazy rendering
-* `btns` - array of objects containing:
-  * `label` - the text on the button
-  * `cb` - a callback function
-  * `cbl` - a callback function for long presses
-* `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left)
-
-If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
-determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
-Once `layout.update()` is called, the following fields are added
-to each object:
-* `x` and `y` for the top left position
-* `w` and `h` for the width and height
-* `_w` and `_h` for the **minimum** width and height
-Other functions:
-* `layout.update()` - update positions of everything if contents have changed
-* `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
-*/
 
+// See Layout.md for documentation
 
 function Layout(layout, options) {
   this._l = this.l = layout;
   // 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 +24,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 +53,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 +68,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/modules/Layout.md b/modules/Layout.md
new file mode 100644
index 000000000..7a4177957
--- /dev/null
+++ b/modules/Layout.md
@@ -0,0 +1,81 @@
+Bangle.js Layout Library
+========================
+
+> Take a look at README.md for hints on developing with this library.
+
+Usage
+-----
+
+```JS
+var Layout = require("Layout");
+var layout = new Layout(layoutObject, options)
+
+layout.render(optionalObject);
+```
+
+For example:
+
+```JS
+var Layout = require("Layout");
+var layout = new Layout({
+  type:"v",
+  c: [
+    { type: "txt", font: "20%", label: "12:00" },
+    { type: "txt", font: "6x8", label: "The Date" }
+  ]
+});
+
+g.clear();
+
+layout.render();
+```
+
+`layoutObject` has:
+
+- A `type` field of:
+  - `undefined` - blank, can be used for padding
+  - `"txt"` - a text label, with value `label`. `font` is required
+  - `"btn"` - a button, with value `label` and callback `cb`. Optional `src` specifies an image (like img) in which case label is ignored. Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields.
+  - `"img"` - an image where `src` is an image, or a function which is called to return an image to draw
+  - `"custom"` - a custom block where `render(layoutObj)` is called to render
+  - `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
+  - `"v"` - Vertical layout, `c` is an array of more `layoutObject`
+- A `id` field. If specified the object is added with this name to the returned `layout` object, so can be referenced as `layout.foo`
+- A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`.
+- A `scale` field, eg `2` to set scale of an image
+- A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
+- A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` and `fillx`/`filly` to be set. Not compatible with text rotation.
+- A `col` field, eg `#f00` for red
+- A `bgCol` field for background color (will automatically fill on render)
+- A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
+- A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
+- A `pad` integer field to set pixels padding
+- A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
+- A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
+- `width` and `height` fields to optionally specify minimum size options is an object containing:
+- `lazy` - a boolean specifying whether to enable automatic lazy rendering
+- `btns` - array of objects containing:
+  - `label` - the text on the button
+  - `cb` - a callback function
+  - `cbl` - a callback function for long presses
+- `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left)
+
+If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
+
+Once `layout.update()` is called, the following fields are added to each object:
+
+- `x` and `y` for the top left position
+- `w` and `h` for the width and height
+- `_w` and `_h` for the **minimum** width and height
+
+Other functions:
+
+- `layout.update()` - update positions of everything if contents have changed
+- `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
+
+Links
+-----
+
+- [Official tutorial](https://www.espruino.com/Bangle.js+Layout)
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
 };