diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index ca1417b5b..b952b1dcd 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -26,3 +26,4 @@ Add "Enable All", "Disable All" and "Remove All" actions 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! diff --git a/apps/alarm/README.md b/apps/alarm/README.md index e979dbaf1..741946b0c 100644 --- a/apps/alarm/README.md +++ b/apps/alarm/README.md @@ -1,7 +1,31 @@ -Alarms & Timers -=============== +# Alarms & Timers This app allows you to add/modify any alarms and timers. -It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) -to handle the alarm scheduling in an efficient way that can work alongside other apps. +It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps. + +## Menu overview + +- `New...` + - `New Alarm` → Configure a new alarm + - `Repeat` → Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely) + - `New Timer` → Configure a new timer +- `Advanced` + - `Scheduler settings` → Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details + - `Enable All` → Enable _all_ disabled alarms & timers + - `Disable All` → Disable _all_ enabled alarms & timers + - `Delete All` → Delete _all_ alarms & timers + +## Creator + +- [Gordon Williams](https://github.com/gfwilliams) + +## Main Contributors + +- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features +- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support +- [storm64](https://github.com/storm64) - Fix redrawing in submenus + +## Attributions + +All icons used in this app are from [icons8](https://icons8.com). diff --git a/apps/alarm/app.js b/apps/alarm/app.js index cf46823d6..0cf1f3d6f 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -1,20 +1,160 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +// 0 = Sunday (default), 1 = Monday +const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; +const WORKDAYS = 62 +const WEEKEND = firstDayOfWeek ? 192 : 65; +const EVERY_DAY = firstDayOfWeek ? 254 : 127; + +const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA=="); +const iconAlarmOff = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=") + : atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")); + +const iconTimerOn = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=") + : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")); +const iconTimerOff = "\0" + (g.theme.dark + ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=") + : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")); + // An array of alarm objects (see sched/README.md) var alarms = require("sched").getAlarms(); -// 0 = Sunday -// 1 = Monday -var firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; +function handleFirstDayOfWeek(dow) { + if (firstDayOfWeek == 1) { + if ((dow & 1) == 1) { + // In the scheduler API Sunday is 1. + // Here the week starts on Monday and Sunday is ON so + // when I read the dow I need to move Sunday to 128... + dow += 127; + } else if ((dow & 128) == 128) { + // ... and then when I write the dow I need to move Sunday back to 1. + dow -= 127; + } + } + return dow; +} -function getCurrentTime() { - var time = new Date(); - return ( - time.getHours() * 3600000 + - time.getMinutes() * 60000 + - time.getSeconds() * 1000 - ); +// Check the first day of week and update the dow field accordingly. +alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow)); + +function showMainMenu() { + const menu = { + "": { "title": /*LANG*/"Alarms & Timers" }, + "< Back": () => load(), + /*LANG*/"New...": () => showNewMenu() + }; + + 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)) : ""); + menu[label] = { + value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), + onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) + }; + }); + + menu[/*LANG*/"Advanced"] = () => showAdvancedMenu(); + + E.showMenu(menu); +} + +function showNewMenu() { + E.showMenu({ + "": { "title": /*LANG*/"New..." }, + "< Back": () => showMainMenu(), + /*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined), + /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined) + }); +} + +function showEditAlarmMenu(selectedAlarm, alarmIndex) { + var isNew = alarmIndex === undefined; + + var alarm = require("sched").newDefaultAlarm(); + alarm.dow = handleFirstDayOfWeek(alarm.dow); + + if (selectedAlarm) { + Object.assign(alarm, selectedAlarm); + } + + var time = require("time_utils").decodeTime(alarm.t); + + const menu = { + "": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" }, + "< Back": () => { + saveAlarm(alarm, alarmIndex, time); + showMainMenu(); + }, + /*LANG*/"Hour": { + value: time.h, + format: v => ("0" + v).substr(-2), + min: 0, + max: 23, + wrap: true, + onchange: v => time.h = v + }, + /*LANG*/"Minute": { + value: time.m, + format: v => ("0" + v).substr(-2), + min: 0, + max: 59, + wrap: true, + onchange: v => time.m = v + }, + /*LANG*/"Enabled": { + value: alarm.on, + onchange: v => alarm.on = v + }, + /*LANG*/"Repeat": { + value: decodeDOW(alarm), + onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.dow, dow => { + alarm.rp = dow > 0; + alarm.dow = dow; + alarm.t = require("time_utils").encodeTime(time); + setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); + }) + }, + /*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v), + /*LANG*/"Auto Snooze": { + value: alarm.as, + onchange: v => alarm.as = v + }, + /*LANG*/"Cancel": () => showMainMenu() + }; + + if (!isNew) { + menu[/*LANG*/"Delete"] = () => { + E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => { + if (confirm) { + alarms.splice(alarmIndex, 1); + saveAndReload(); + showMainMenu(); + } else { + alarm.t = require("time_utils").encodeTime(time); + setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); + } + }); + }; + } + + E.showMenu(menu); +} + +function saveAlarm(alarm, alarmIndex, time) { + alarm.t = require("time_utils").encodeTime(time); + alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0; + + if (alarmIndex === undefined) { + alarms.push(alarm); + } else { + alarms[alarmIndex] = alarm; + } + + saveAndReload(); } function saveAndReload() { @@ -23,249 +163,187 @@ function saveAndReload() { require("sched").setAlarms(alarms); require("sched").reload(); + + // Fix after save + alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek)); } -function showMainMenu() { - // Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w") - // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA") - const menu = { - '': { 'title': /*LANG*/'Alarms&Timers' }, - /*LANG*/'< Back': () => { load(); }, - /*LANG*/'New Alarm': () => editAlarm(-1), - /*LANG*/'New Timer': () => editTimer(-1) - }; - alarms.forEach((alarm, idx) => { - alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek); +function decodeDOW(alarm) { + return alarm.rp + ? require("date_utils") + .dows(firstDayOfWeek, 2) + .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_") + .join("") + .toLowerCase() + : "Once" +} - var type, txt; // a leading space is currently required (JS error in Espruino 2v12) - if (alarm.timer) { - type = /*LANG*/"Timer"; - txt = " " + require("sched").formatTime(alarm.timer); - } else { - type = /*LANG*/"Alarm"; - txt = " " + require("sched").formatTime(alarm.t); +function showEditRepeatMenu(dow, dowChangeCallback) { + var originalDow = dow; + var isCustom = dow > 0 && 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) + }, + /*LANG*/"Workdays": { + value: dow == WORKDAYS, + onchange: () => dowChangeCallback(WORKDAYS) + }, + /*LANG*/"Weekends": { + value: dow == WEEKEND, + onchange: () => dowChangeCallback(WEEKEND) + }, + /*LANG*/"Every Day": { + value: dow == EVERY_DAY, + onchange: () => dowChangeCallback(EVERY_DAY) + }, + /*LANG*/"Custom": { + value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false, + onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalDow) } - if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); - // rename duplicate alarms - if (menu[type + txt]) { - var n = 2; - while (menu[type + " " + n + txt]) n++; - txt = type + " " + n + txt; - } else txt = type + txt; - // add to menu - menu[txt] = { - value: "\0" + atob(alarm.on ? "EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g" : "EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"), - onchange: function () { - setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm); - } - }; - }); + }; - if (alarms.some(e => !e.on)) { - menu[/*LANG*/"Enable All"] = () => enableAll(true); - } - if (alarms.some(e => e.on)) { - menu[/*LANG*/"Disable All"] = () => enableAll(false); - } - if (alarms.length > 0) { - menu[/*LANG*/"Delete All"] = () => deleteAll(); - } - - if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); - return E.showMenu(menu); + E.showMenu(menu); } -function editDOW(dow, onchange) { +function showCustomDaysMenu(dow, dowChangeCallback, originalDow) { const menu = { - '': { 'title': /*LANG*/'Days of Week' }, - /*LANG*/'< Back': () => onchange(dow) + "": { "title": /*LANG*/"Custom Days" }, + "< Back": () => dowChangeCallback(dow), }; require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { menu[day] = { value: !!(dow & (1 << (i + firstDayOfWeek))), - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek))) }; }); + menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalDow, dowChangeCallback) + E.showMenu(menu); } -function editAlarm(alarmIndex, alarm) { - var newAlarm = alarmIndex < 0; - var a = require("sched").newDefaultAlarm(); - a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek); +function showEditTimerMenu(selectedTimer, timerIndex) { + var isNew = timerIndex === undefined; - if (!newAlarm) Object.assign(a, alarms[alarmIndex]); - if (alarm) Object.assign(a, alarm); - var t = require("sched").decodeTime(a.t); + var timer = require("sched").newDefaultTimer(); + + if (selectedTimer) { + Object.assign(timer, selectedTimer); + } + + var time = require("time_utils").decodeTime(timer.timer); const menu = { - '': { 'title': /*LANG*/'Alarm' }, - /*LANG*/'< Back': () => { - saveAlarm(newAlarm, alarmIndex, a, t); + "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" }, + "< Back": () => { + saveTimer(timer, timerIndex, time); showMainMenu(); }, - /*LANG*/'Hours': { - value: t.hrs, min: 0, max: 23, wrap: true, - onchange: v => t.hrs = v + /*LANG*/"Hours": { + value: time.h, + min: 0, + max: 23, + wrap: true, + onchange: v => time.h = v }, - /*LANG*/'Minutes': { - value: t.mins, min: 0, max: 59, wrap: true, - onchange: v => t.mins = v + /*LANG*/"Minutes": { + value: time.m, + min: 0, + max: 59, + wrap: true, + onchange: v => time.m = v }, - /*LANG*/'Enabled': { - value: a.on, - format: v => v ? /*LANG*/"On" : /*LANG*/"Off", - onchange: v => a.on = v + /*LANG*/"Enabled": { + value: timer.on, + onchange: v => timer.on = v }, - /*LANG*/'Repeat': { - value: a.rp, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", - onchange: v => a.rp = v - }, - /*LANG*/'Days': { - value: decodeDOW(a.dow), - onchange: () => setTimeout(editDOW, 100, a.dow, d => { - a.dow = d; - a.t = require("sched").encodeTime(t); - editAlarm(alarmIndex, a); - }) - }, - /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), - /*LANG*/'Auto Snooze': { - value: a.as, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", - onchange: v => a.as = v - } + /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v), }; - menu[/*LANG*/"Cancel"] = () => showMainMenu(); - - if (!newAlarm) { - menu[/*LANG*/"Delete"] = function () { - alarms.splice(alarmIndex, 1); - saveAndReload(); - showMainMenu(); + if (!isNew) { + menu[/*LANG*/"Delete"] = () => { + E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => { + if (confirm) { + alarms.splice(timerIndex, 1); + saveAndReload(); + showMainMenu(); + } else { + timer.timer = require("time_utils").encodeTime(time); + setTimeout(showEditTimerMenu, 10, timer, timerIndex) + } + }); }; } - return E.showMenu(menu); + E.showMenu(menu); } -function saveAlarm(newAlarm, alarmIndex, a, t) { - a.t = require("sched").encodeTime(t); - a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0; +function saveTimer(timer, timerIndex, time) { + timer.timer = require("time_utils").encodeTime(time); + timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer; + timer.last = 0; - if (newAlarm) { - alarms.push(a); + if (timerIndex === undefined) { + alarms.push(timer); } else { - alarms[alarmIndex] = a; + alarms[timerIndex] = timer; } saveAndReload(); } -function editTimer(alarmIndex, alarm) { - var newAlarm = alarmIndex < 0; - var a = require("sched").newDefaultTimer(); - if (!newAlarm) Object.assign(a, alarms[alarmIndex]); - if (alarm) Object.assign(a, alarm); - var t = require("sched").decodeTime(a.timer); - - const menu = { - '': { 'title': /*LANG*/'Timer' }, - /*LANG*/'< Back': () => { - saveTimer(newAlarm, alarmIndex, a, t); - showMainMenu(); - }, - /*LANG*/'Hours': { - value: t.hrs, min: 0, max: 23, wrap: true, - onchange: v => t.hrs = v - }, - /*LANG*/'Minutes': { - value: t.mins, min: 0, max: 59, wrap: true, - onchange: v => t.mins = v - }, - /*LANG*/'Enabled': { - value: a.on, - format: v => v ? /*LANG*/"On" : /*LANG*/"Off", - onchange: v => a.on = v - }, - /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), - }; - - menu[/*LANG*/"Cancel"] = () => showMainMenu(); - - if (!newAlarm) { - menu[/*LANG*/"Delete"] = function () { - alarms.splice(alarmIndex, 1); - saveAndReload(); - showMainMenu(); - }; - } - return E.showMenu(menu); -} - -function saveTimer(newAlarm, alarmIndex, a, t) { - a.timer = require("sched").encodeTime(t); - a.t = getCurrentTime() + a.timer; - a.last = 0; - - if (newAlarm) { - alarms.push(a); - } else { - alarms[alarmIndex] = a; - } - - saveAndReload(); -} - -function handleFirstDayOfWeek(dow, firstDayOfWeek) { - if (firstDayOfWeek == 1) { - if ((dow & 1) == 1) { - // By default 1 = Sunday. - // Here the week starts on Monday and Sunday is ON so move Sunday to 128. - dow += 127; - } else if ((dow & 128) == 128) { - dow -= 127; - } - } - return dow; -} - -function decodeDOW(dow) { - return require("date_utils") - .dows(firstDayOfWeek, 2) - .map((day, index) => dow & (1 << (index + firstDayOfWeek)) ? day : "_") - .join(""); +function showAdvancedMenu() { + E.showMenu({ + "": { "title": /*LANG*/"Advanced" }, + "< Back": () => showMainMenu(), + /*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()), + /*LANG*/"Enable All": () => enableAll(true), + /*LANG*/"Disable All": () => enableAll(false), + /*LANG*/"Delete All": () => deleteAll() + }); } function enableAll(on) { - 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(); - } - - showMainMenu(); - }); + 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()); + } else { + 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(); + showMainMenu(); + } else { + showAdvancedMenu(); + } + }); + } } function deleteAll() { - E.showPrompt(/*LANG*/"Are you sure?", { - title: /*LANG*/"Delete All" - }).then((confirm) => { - if (confirm) { - alarms = []; - saveAndReload(); - } - - showMainMenu(); - }); + if (alarms.length == 0) { + E.showPrompt(/*LANG*/"Nothing to delete", { title: /*LANG*/"Delete All", buttons: { /*LANG*/"Ok": true } }).then(() => showAdvancedMenu()); + } else { + E.showPrompt(/*LANG*/"Are you sure?", { + title: /*LANG*/"Delete All" + }).then((confirm) => { + if (confirm) { + alarms = []; + saveAndReload(); + showMainMenu(); + } else { + showAdvancedMenu(); + } + }); + } } showMainMenu(); diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index c062b030d..b9ce55756 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,16 +2,29 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.26", + "version": "0.27", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", - "supports": ["BANGLEJS","BANGLEJS2"], + "supports": [ "BANGLEJS", "BANGLEJS2" ], "readme": "README.md", - "dependencies": {"scheduler":"type"}, + "dependencies": { "scheduler":"type" }, "storage": [ - {"name":"alarm.app.js","url":"app.js"}, - {"name":"alarm.img","url":"app-icon.js","evaluate":true}, - {"name":"alarm.wid.js","url":"widget.js"} + { "name": "alarm.app.js", "url": "app.js" }, + { "name": "alarm.img", "url": "app-icon.js", "evaluate": true }, + { "name": "alarm.wid.js", "url": "widget.js" } + ], + "screenshots": [ + { "url": "screenshot-1.png" }, + { "url": "screenshot-2.png" }, + { "url": "screenshot-3.png" }, + { "url": "screenshot-4.png" }, + { "url": "screenshot-5.png" }, + { "url": "screenshot-6.png" }, + { "url": "screenshot-7.png" }, + { "url": "screenshot-8.png" }, + { "url": "screenshot-9.png" }, + { "url": "screenshot-10.png" }, + { "url": "screenshot-11.png" } ] } diff --git a/apps/alarm/screenshot-1.png b/apps/alarm/screenshot-1.png new file mode 100644 index 000000000..d2bd3a409 Binary files /dev/null and b/apps/alarm/screenshot-1.png differ diff --git a/apps/alarm/screenshot-10.png b/apps/alarm/screenshot-10.png new file mode 100644 index 000000000..1e6e516c3 Binary files /dev/null and b/apps/alarm/screenshot-10.png differ diff --git a/apps/alarm/screenshot-11.png b/apps/alarm/screenshot-11.png new file mode 100644 index 000000000..197c84194 Binary files /dev/null and b/apps/alarm/screenshot-11.png differ diff --git a/apps/alarm/screenshot-2.png b/apps/alarm/screenshot-2.png new file mode 100644 index 000000000..1cbc255a9 Binary files /dev/null and b/apps/alarm/screenshot-2.png differ diff --git a/apps/alarm/screenshot-3.png b/apps/alarm/screenshot-3.png new file mode 100644 index 000000000..a165d3594 Binary files /dev/null and b/apps/alarm/screenshot-3.png differ diff --git a/apps/alarm/screenshot-4.png b/apps/alarm/screenshot-4.png new file mode 100644 index 000000000..7fd7e99b6 Binary files /dev/null and b/apps/alarm/screenshot-4.png differ diff --git a/apps/alarm/screenshot-5.png b/apps/alarm/screenshot-5.png new file mode 100644 index 000000000..4174c5670 Binary files /dev/null and b/apps/alarm/screenshot-5.png differ diff --git a/apps/alarm/screenshot-6.png b/apps/alarm/screenshot-6.png new file mode 100644 index 000000000..dc579ca5c Binary files /dev/null and b/apps/alarm/screenshot-6.png differ diff --git a/apps/alarm/screenshot-7.png b/apps/alarm/screenshot-7.png new file mode 100644 index 000000000..49da44710 Binary files /dev/null and b/apps/alarm/screenshot-7.png differ diff --git a/apps/alarm/screenshot-8.png b/apps/alarm/screenshot-8.png new file mode 100644 index 000000000..86d69cd93 Binary files /dev/null and b/apps/alarm/screenshot-8.png differ diff --git a/apps/alarm/screenshot-9.png b/apps/alarm/screenshot-9.png new file mode 100644 index 000000000..2d8c7fc83 Binary files /dev/null and b/apps/alarm/screenshot-9.png differ diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 96b50c3a0..55f456795 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -7,3 +7,4 @@ 0.06: Option to keep messages after a disconnect (default false) (fix #1186) 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 diff --git a/apps/android/README.md b/apps/android/README.md index 580eeec9a..c10718aac 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -21,7 +21,6 @@ of Gadgetbridge - making your phone make noise so you can find it. * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js keep any messages it has received, or should it delete them? * `Messages` - launches the messages app, showing a list of messages -* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze ## How it works diff --git a/apps/android/boot.js b/apps/android/boot.js index 9e24c9893..fb2101aa4 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -67,17 +67,13 @@ var dow = event.d[j].rep; if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; - var a = { - id : "gb"+j, - appid : "gbalarms", - on : true, - t : event.d[j].h * 3600000 + event.d[j].m * 60000, - dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format - last : last, - rp : settings.rp, - as : settings.as, - vibrate : settings.vibrate - }; + var a = require("sched").newDefaultAlarm(); + a.id = "gb"+j; + a.appid = "gbalarms"; + a.on = true; + a.t = event.d[j].h * 3600000 + event.d[j].m * 60000; + a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format + a.last = last; alarms.push(a); } sched.setAlarms(alarms); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 203cd18b1..4051a344b 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.08", + "version": "0.09", "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/android/settings.js b/apps/android/settings.js index 9f72947ab..695d483c6 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -25,27 +25,6 @@ } }, /*LANG*/"Messages" : ()=>load("messages.app.js"), - /*LANG*/"Alarms" : () => E.showMenu({ - "" : { "title" : /*LANG*/"Alarms" }, - "< Back" : ()=>E.showMenu(mainmenu), - /*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}), - /*LANG*/"Repeat": { - value: settings.rp, - format : v=>v?/*LANG*/"Yes":/*LANG*/"No", - onchange: v => { - settings.rp = v; - updateSettings(); - } - }, - /*LANG*/"Auto snooze": { - value: settings.as, - format : v=>v?/*LANG*/"Yes":/*LANG*/"No", - onchange: v => { - settings.as = v; - updateSettings(); - } - }, - }) }; E.showMenu(mainmenu); }) diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 316660fc6..5df032c4d 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -7,3 +7,5 @@ 0.07: Update to use Bangle.setUI instead of setWatch 0.08: Use theme colors, Layout library 0.09: Fix time/date disappearing after fullscreen notification +0.10: Use ClockFace library +0.11: Use ClockFace.is12Hour diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 5d46a1cb4..987d41cc6 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -3,7 +3,6 @@ * A simple digital clock showing seconds as a bar **/ // Check settings for what type our clock should be -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; let locale = require("locale"); { // add some more info to locale let date = new Date(); @@ -11,13 +10,9 @@ let locale = require("locale"); date.setMonth(1, 3); // februari: months are zero-indexed const localized = locale.date(date, true); locale.dayFirst = /3.*2/.test(localized); - - locale.hasMeridian = false; - if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed - locale.hasMeridian = (locale.meridian(date)!==""); - } + locale.hasMeridian = (locale.meridian(date)!==""); } -Bangle.loadWidgets(); + function renderBar(l) { if (!this.fraction) { // zero-size fillRect stills draws one line of pixels, we don't want that @@ -27,35 +22,9 @@ function renderBar(l) { g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1); } -const Layout = require("Layout"); -const layout = new Layout({ - type: "v", c: [ - { - type: "h", c: [ - {id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below - {id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg}, - ], - }, - {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, - {height: 40}, - {id: "date", type: "txt", font: "10%", valign: 1}, - ], -}, {lazy: true}); -// adjustments based on screen size and whether we display am/pm -let thickness; // bar thickness, same as time font "pixel block" size -if (is12Hour) { - // Maximum font size = ( - ) / (5chars * 6px) - thickness = Math.floor((g.getWidth()-24)/(5*6)); -} else { - layout.ampm.label = ""; - thickness = Math.floor(g.getWidth()/(5*6)); -} -layout.bar.height = thickness+1; -layout.time.font = "6x8:"+thickness; -layout.update(); function timeText(date) { - if (!is12Hour) { + if (!clock.is12Hour) { return locale.time(date, true); } const date12 = new Date(date.getTime()); @@ -68,7 +37,7 @@ function timeText(date) { return locale.time(date12, true); } function ampmText(date) { - return (is12Hour && locale.hasMeridian)? locale.meridian(date) : ""; + return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : ""; } function dateText(date) { const dayName = locale.dow(date, true), @@ -78,31 +47,48 @@ function dateText(date) { return `${dayName} ${dayMonth}`; } -draw = function draw(force) { - if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled - const date = new Date(); - layout.time.label = timeText(date); - layout.ampm.label = ampmText(date); - layout.date.label = dateText(date); - const SECONDS_PER_MINUTE = 60; - layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; - if (force) { - Bangle.drawWidgets(); - layout.forgetLazyState(); - } - layout.render(); - // schedule update at start of next second - const millis = date.getMilliseconds(); - setTimeout(draw, 1000-millis); -}; -// Show launcher when button pressed -Bangle.setUI("clock"); -Bangle.on("lcdPower", function(on) { - if (on) { - draw(true); - } -}); -g.reset().clear(); -Bangle.drawWidgets(); -draw(); +const ClockFace = require("ClockFace"), + clock = new ClockFace({ + precision:1, + init: function() { + const Layout = require("Layout"); + this.layout = new Layout({ + type: "v", c: [ + { + type: "h", c: [ + {id: "time", label: "88:88", type: "txt", font: "6x8:5", col:g.theme.fg, bgCol: g.theme.bg}, // size updated below + {id: "ampm", label: " ", type: "txt", font: "6x8:2", col:g.theme.fg, bgCol: g.theme.bg}, + ], + }, + {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, + {height: 40}, + {id: "date", type: "txt", font: "10%", valign: 1}, + ], + }, {lazy: true}); + // adjustments based on screen size and whether we display am/pm + let thickness; // bar thickness, same as time font "pixel block" size + if (this.is12Hour) { + // Maximum font size = ( - ) / (5chars * 6px) + thickness = Math.floor((Bangle.appRect.w-24)/(5*6)); + } else { + this.layout.ampm.label = ""; + thickness = Math.floor(Bangle.appRect.w/(5*6)); + } + this.layout.bar.height = thickness+1; + this.layout.time.font = "6x8:"+thickness; + this.layout.update(); + }, + update: function(date, c) { + if (c.m) this.layout.time.label = timeText(date); + if (c.h) this.layout.ampm.label = ampmText(date); + if (c.d) this.layout.date.label = dateText(date); + const SECONDS_PER_MINUTE = 60; + if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; + this.layout.render(); + }, + resume: function() { + this.layout.forgetLazyState(); + }, + }); +clock.start(); diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json index 2b7be355f..7bc61096d 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.09", + "version": "0.11", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index e3f492d3b..a43ecf86e 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -51,3 +51,4 @@ 0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk) 0.46: Fix no clock found error on Bangle.js 2 0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed) +0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 119cd2c2c..4cb3c52e4 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -197,8 +197,18 @@ bootFiles.forEach(bootFile=>{ require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset); fileOffset+=2+bootFile.length+1; var bf = require('Storage').read(bootFile); - require('Storage').write('.boot0',bf,fileOffset); - fileOffset+=bf.length; + // we can't just write 'bf' in one go because at least in 2v13 and earlier + // Espruino wants to read the whole file into RAM first, and on Bangle.js 1 + // it can be too big (especially BTHRM). + var bflen = bf.length; + var bfoffset = 0; + while (bflen) { + var bfchunk = Math.min(bflen, 2048); + require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset); + fileOffset+=bfchunk; + bfoffset+=bfchunk; + bflen-=bfchunk; + } require('Storage').write('.boot0',";\n",fileOffset); fileOffset+=2; }); diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index d1bf2edde..62adc4db1 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.47", + "version": "0.48", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/diceroll/ChangeLog b/apps/diceroll/ChangeLog new file mode 100644 index 000000000..89dff4011 --- /dev/null +++ b/apps/diceroll/ChangeLog @@ -0,0 +1 @@ +0.01: App created \ No newline at end of file diff --git a/apps/diceroll/app-icon.js b/apps/diceroll/app-icon.js new file mode 100644 index 000000000..4d6e7da16 --- /dev/null +++ b/apps/diceroll/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA=")) diff --git a/apps/diceroll/app.js b/apps/diceroll/app.js new file mode 100644 index 000000000..d514ce92f --- /dev/null +++ b/apps/diceroll/app.js @@ -0,0 +1,108 @@ +var init_message = true; +var acc_data; +var die_roll = 1; +var selected_die = 0; +var roll = 0; +const dices = [4, 6, 10, 12, 20]; + +g.setFontAlign(0,0); + +Bangle.on('touch', function(button, xy) { + // Change die if not rolling + if(roll < 1){ + if(selected_die <= 3){ + selected_die++; + }else{ + selected_die = 0; + } + } + //Disable initial message + init_message = false; +}); + +function rect(){ + x1 = g.getWidth()/2 - 35; + x2 = g.getWidth()/2 + 35; + y1 = g.getHeight()/2 - 35; + y2 = g.getHeight()/2 + 35; + g.drawRect(x1, y1, x2, y2); +} + +function pentagon(){ + x1 = g.getWidth()/2; + y1 = g.getHeight()/2 - 50; + x2 = g.getWidth()/2 - 50; + y2 = g.getHeight()/2 - 10; + x3 = g.getWidth()/2 - 30; + y3 = g.getHeight()/2 + 30; + x4 = g.getWidth()/2 + 30; + y4 = g.getHeight()/2 + 30; + x5 = g.getWidth()/2 + 50; + y5 = g.getHeight()/2 - 10; + g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true); +} + +function triangle(){ + x1 = g.getWidth()/2; + y1 = g.getHeight()/2 - 57; + x2 = g.getWidth()/2 - 50; + y2 = g.getHeight()/2 + 23; + x3 = g.getWidth()/2 + 50; + y3 = g.getHeight()/2 + 23; + g.drawPoly([x1, y1, x2, y2, x3, y3], true); +} + +function drawDie(variant) { + if(variant == 1){ + //Rect, 6 + rect(); + }else if(variant == 3){ + //Pentagon, 12 + pentagon(); + }else{ + //Triangle, 4, 10, 20 + triangle(); + } +} + +function initMessage(){ + g.setFont("6x8", 2); + g.drawString("Dice-n-Roll", g.getWidth()/2, 20); + g.drawString("Shake to roll", g.getWidth()/2, 60); + g.drawString("Tap to change", g.getWidth()/2, 80); + g.drawString("Tap to start", g.getWidth()/2, 150); +} + +function rollDie(){ + acc_data = Bangle.getAccel(); + if(acc_data.diff > 0.3){ + roll = 3; + } + //Mange the die "roll" by chaning the number a few times + if(roll > 0){ + g.drawString("Rolling!", g.getWidth()/2, 150); + die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1; + roll--; + } + //Draw dice graphics + drawDie(selected_die); + //Draw dice number + g.setFontAlign(0,0); + g.setFont("Vector", 45); + g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2); + //Draw selected die in right corner + g.setFont("6x8", 2); + g.drawString(dices[selected_die], g.getWidth()-15, 15); +} + +function main() { + g.clear(); + if(init_message){ + initMessage(); + }else{ + rollDie(); + } + Bangle.setLCDPower(1); +} + +var interval = setInterval(main, 300); \ No newline at end of file diff --git a/apps/diceroll/app.png b/apps/diceroll/app.png new file mode 100644 index 000000000..b695b7080 Binary files /dev/null and b/apps/diceroll/app.png differ diff --git a/apps/diceroll/diceroll_screenshot.png b/apps/diceroll/diceroll_screenshot.png new file mode 100644 index 000000000..71024edbb Binary files /dev/null and b/apps/diceroll/diceroll_screenshot.png differ diff --git a/apps/diceroll/metadata.json b/apps/diceroll/metadata.json new file mode 100644 index 000000000..81a2f8bfd --- /dev/null +++ b/apps/diceroll/metadata.json @@ -0,0 +1,14 @@ +{ "id": "diceroll", + "name": "Dice-n-Roll", + "shortName":"Dice-n-Roll", + "icon": "app.png", + "version":"0.01", + "description": "A dice app with a few different dice.", + "screenshots": [{"url":"diceroll_screenshot.png"}], + "tags": "game", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"diceroll.app.js","url":"app.js"}, + {"name":"diceroll.img","url":"app-icon.js","evaluate":true} + ] + } \ No newline at end of file diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 95952b9fe..09804b82e 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -11,3 +11,4 @@ 0.11: Fix bangle.js 1 white icons not displaying 0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock. 0.13: Added swipeExit setting so that left-right to exit is an option +0.14: Don't move pages when doing exit swipe. diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md index bea20ef65..55c9f53b8 100644 --- a/apps/dtlaunch/README.md +++ b/apps/dtlaunch/README.md @@ -29,6 +29,6 @@ Bangle 2: **Touch** - icon to select, scond touch launches app -**Swipe Left** - move to next page of app icons +**Swipe Left/Up** - move to next page of app icons -**Swipe Right** - move to previous page of app icons +**Swipe Right/Down** - move to previous page of app icons diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 8466a7414..46194ec5d 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -93,7 +93,7 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ if (dirUpDown==-1||dirLeftRight==-1){ ++page; if (page>maxPage) page=0; drawPage(page); - } else if (dirUpDown==1||dirLeftRight==1){ + } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){ --page; if (page<0) page=maxPage; drawPage(page); } diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 7784972ca..4a0b8067c 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.13", + "version": "0.14", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/f9lander/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/f9lander/README.md b/apps/f9lander/README.md new file mode 100644 index 000000000..16202f166 --- /dev/null +++ b/apps/f9lander/README.md @@ -0,0 +1,33 @@ +# F9 Lander + +Land a Falcon 9 booster on a drone ship. + +## Game play + +Attempt to land your Falcon 9 booster on a drone ship before running out of fuel. +A successful landing requires: + * setting down on the ship + * the booster has to be mostly vertical + * the landing speed cannot be too high + +## Controls + +The angle of the booster is controlled by tilting the watch side-to-side. The +throttle level is controlled by tilting the watch forward and back: + * screen horizontal (face up) means no throttle + * screen vertical corresponds to full throttle + +The fuel burn rate is proportional to the throttle level. + +## Creators +Liam Kl. B. + +Marko Kl. B. + +## Screenshots + +![](f9lander_screenshot1.png) + +![](f9lander_screenshot2.png) + +![](f9lander_screenshot3.png) diff --git a/apps/f9lander/app-icon.js b/apps/f9lander/app-icon.js new file mode 100644 index 000000000..572768a28 --- /dev/null +++ b/apps/f9lander/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA")) diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js new file mode 100644 index 000000000..7e52104c0 --- /dev/null +++ b/apps/f9lander/app.js @@ -0,0 +1,150 @@ +const falcon9 = Graphics.createImage(` + xxxxx + xxxxx xxxxx + x x + x x + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxx + xxxxxxxxx + xx xxxxx xx +xx xx`); + +const droneShip = Graphics.createImage(` +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +`); + +const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20) +const cloudOffs = Math.floor(Math.random()*g.getWidth()/2); + +const oceanHeight = g.getHeight()*0.1; + +const targetY = g.getHeight()-oceanHeight-falcon9.height/2; + +var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2, + y : 20, + vx : 0, + vy : 0, + mass : 100, + fuel : 100 }; + +var exploded = false; +var nExplosions = 0; +var landed = false; + +const gravity = 4; +const dt = 0.1; +const fuelBurnRate = 20*(176/g.getHeight()); +const maxV = 12; + +function flameImageGen (throttle) { + var str = " xxx \n xxx \n"; + str += "xxxxx\n".repeat(throttle); + str += " xxx \n x \n"; + return Graphics.createImage(str); +} + +function drawFalcon(x, y, throttle, angle) { + g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle}); + if (throttle>0) { + var flameImg = flameImageGen(throttle); + var r = falcon9.height/2 + flameImg.height/2-1; + var xoffs = -Math.sin(angle)*r; + var yoffs = Math.cos(angle)*r; + if (Math.random()>0.7) g.setColor(1, 0.5, 0); + else g.setColor(1, 1, 0); + g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + } +} + +function drawBG() { + g.setBgColor(0.2, 0.2, 1).clear(); + g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1); + g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10); + g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20); + g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1); +} + +function showFuel() { + g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4); +} + +function renderScreen(input) { + drawBG(); + showFuel(); + drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle); +} + +function getInputs() { + var accel = Bangle.getAccel(); + var a = Math.PI/2 + Math.atan2(accel.y, accel.x); + var t = (1+accel.z); + if (t > 1) t = 1; + if (t < 0) t = 0; + if (booster.fuel<=0) t = 0; + return {throttle: t, angle: a}; +} + +function epilogue(str) { + g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip(); + g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20); + clearInterval(stepInterval); + Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); }); +} + +function gameStep() { + if (exploded) { + if (nExplosions++ < 15) { + var r = Math.random()*25; + var x = Math.random()*30 - 15; + var y = Math.random()*30 - 15; + g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r); + if (nExplosions==1) Bangle.buzz(600); + } + else epilogue("You crashed!"); + } + else { + var input = getInputs(); + if (booster.y >= targetY) { +// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle); + if (Math.abs(booster.x-droneX-droneShip.width/2) { + stepInterval = setInterval(gameStep, Math.floor(1000*dt)); + Bangle.removeListener("swipe"); +}); diff --git a/apps/f9lander/f9lander.png b/apps/f9lander/f9lander.png new file mode 100644 index 000000000..f03cc1645 Binary files /dev/null and b/apps/f9lander/f9lander.png differ diff --git a/apps/f9lander/f9lander_screenshot1.png b/apps/f9lander/f9lander_screenshot1.png new file mode 100644 index 000000000..ea7d8a834 Binary files /dev/null and b/apps/f9lander/f9lander_screenshot1.png differ diff --git a/apps/f9lander/f9lander_screenshot2.png b/apps/f9lander/f9lander_screenshot2.png new file mode 100644 index 000000000..a2f13d6c7 Binary files /dev/null and b/apps/f9lander/f9lander_screenshot2.png differ diff --git a/apps/f9lander/f9lander_screenshot3.png b/apps/f9lander/f9lander_screenshot3.png new file mode 100644 index 000000000..61b8be82f Binary files /dev/null and b/apps/f9lander/f9lander_screenshot3.png differ diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json new file mode 100644 index 000000000..75c6a0164 --- /dev/null +++ b/apps/f9lander/metadata.json @@ -0,0 +1,15 @@ +{ "id": "f9lander", + "name": "Falcon9 Lander", + "shortName":"F9lander", + "version":"0.01", + "description": "Land a rocket booster", + "icon": "f9lander.png", + "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }], + "readme": "README.md", + "tags": "game", + "supports" : ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"f9lander.app.js","url":"app.js"}, + {"name":"f9lander.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 74be2faec..62d93e606 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -13,3 +13,4 @@ 0.12: Add setting for Daily Step Goal 0.13: Add support for internationalization 0.14: Move settings +0.15: Fix charts (fix #1366) diff --git a/apps/health/app.js b/apps/health/app.js index a1fe1a4f7..c0a40bd93 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -46,7 +46,7 @@ function menuHRM() { function stepsPerHour() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(24); + var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); g.clear(1); Bangle.drawWidgets(); @@ -57,7 +57,7 @@ function stepsPerHour() { function stepsPerDay() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(31); + var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); g.clear(1); Bangle.drawWidgets(); @@ -68,8 +68,8 @@ function stepsPerDay() { function hrmPerHour() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(24); - let cnt = new Uint8Array(23); + var data = new Uint16Array(24); + var cnt = new Uint8Array(23); require("health").readDay(new Date(), h=>{ data[h.hr]+=h.bpm; if (h.bpm) cnt[h.hr]++; @@ -84,8 +84,8 @@ function hrmPerHour() { function hrmPerDay() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(31); - let cnt = new Uint8Array(31); + var data = new Uint16Array(31); + var cnt = new Uint8Array(31); require("health").readDailySummaries(new Date(), h=>{ data[h.day]+=h.bpm; if (h.bpm) cnt[h.day]++; @@ -100,7 +100,7 @@ function hrmPerDay() { function movementPerHour() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(24); + var data = new Uint16Array(24); require("health").readDay(new Date(), h=>data[h.hr]+=h.movement); g.clear(1); Bangle.drawWidgets(); @@ -111,7 +111,7 @@ function movementPerHour() { function movementPerDay() { E.showMessage(/*LANG*/"Loading..."); - let data = new Uint16Array(31); + var data = new Uint16Array(31); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); g.clear(1); Bangle.drawWidgets(); @@ -183,7 +183,7 @@ function drawBarChart() { } // draw a fake 0 height bar if chart_index is outside the bounds of the array - if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len) + if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len && chart_max_datum > 0) bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum; else bar_top = bar_bot; diff --git a/apps/health/metadata.json b/apps/health/metadata.json index 58762c77a..a038f67b5 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -1,7 +1,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.14", + "version": "0.15", "description": "Logs health data and provides an app to view it", "icon": "app.png", "tags": "tool,system,health", diff --git a/apps/homework/README.md b/apps/homework/README.md new file mode 100644 index 000000000..adad4ef39 --- /dev/null +++ b/apps/homework/README.md @@ -0,0 +1,4 @@ +# This is a simple homework app +Use the touchscreen to navigate. + +Requires the "textinput" library. (Tap keyboard) diff --git a/apps/homework/app-icon.js b/apps/homework/app-icon.js new file mode 100644 index 000000000..202b52b09 --- /dev/null +++ b/apps/homework/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4AbhvQCyvd7oYTCwQYTCwgYRCwwYPIgpKQCA4YOBxIYMBhYLLHhgYEC5BsKDAYXHCwUBiUikAYIC4wtDC5IYCA4pEEC5QYBYRUCkQXJAA8K1Wq0AXHhGIxGAC5ZHHC8ZDDC4cM5qaBC8ZHHC68N6czmAXrL94X/C/4XHgUiCYIDDa54XXO/4XHAH4A/ABY=")) diff --git a/apps/homework/app.js b/apps/homework/app.js new file mode 100644 index 000000000..4ba786690 --- /dev/null +++ b/apps/homework/app.js @@ -0,0 +1,212 @@ +var Layout = require("Layout"); + +var homework = require("Storage").readJSON("homework.txt", "r"); +var mainCheckHomeworkMenu; + +var nhwmn = { // New homework Menu + "": { + "title": "New HW Subject:" + } + +}; + + + +function newHomeworkMenu() { + E.showMessage("Getting subjects..."); + var rawsubjects = require("Storage").read("subjects.txt"); // This code reads out the subjects list and removes the newline character at the end + var splitsubjects = rawsubjects.split(","); + var lastItem = splitsubjects[splitsubjects.length - 1]; + var thiscurrentsubject; + var command; + lastItem = lastItem.slice(0, -1); + splitsubjects[splitsubjects.length - 1] = lastItem; + for (let i = 0; i < splitsubjects.length; i++) { // loop through array and add to menu + thiscurrentsubject = splitsubjects[i]; + command = addNewHomework(thiscurrentsubject); + nhwmn[splitsubjects[i]] = addNewHomework.bind(null, thiscurrentsubject); + } + nhwmn["Back"] = function() {E.showMenu(mainMenu);}; + console.log(nhwmn); + E.showMenu(nhwmn); +} +var mode = "mainmenu"; +var statusmsg; +var mainMenu = { + "": { + title: "--Main Menu--" + }, + "New Homework": function() { + newHomeworkMenu(); + mode = "newhomework"; + }, + "Check Homework": function() { + checkUnfinishedHomeworkAssembler(); + }, + "Reset Homework": function() { + E.showPrompt("Are you sure you want to delete homework.txt?", { + buttons: { + "No": false, + "Yes": true + } + }).then(function(v) { + if (v) { + require("Storage").write("homework.txt", '{"homework":[]}'); + homework = require("Storage").readJSON("homework.txt", "r"); + E.showMenu(mainMenu); + + }else{ + E.showMenu(mainMenu); + } + }); + }, +}; + +function checkUnfinishedHomeworkAssembler() { + homework = require("Storage").readJSON("homework.txt", "r"); + var hwcount = Object.keys(homework.homework).length; + mainCheckHomeworkMenu = { + '': { + 'title': 'Unfinished HW:' + } + }; + // This code snippet gets the unfinished HW and puts it in mainCheckHomeworkMenu + // btw mainCheckHomeworkMenu displays all the homework, when tapping on it you get more details with checkPreciseHomework function + for (var i = 0; i < hwcount; ++i) { + if (homework.homework[i].done === false) { + var currentsubject = i; //attempting to pass i + mainCheckHomeworkMenu[homework.homework[i].subject] = checkPreciseHomework.bind(null, currentsubject); + } + + } + mainCheckHomeworkMenu["See Archived HW"] = function() { + checkFinishedHomeworkAssembler(); + }; + mainCheckHomeworkMenu["Back to Main Menu"] = function() { + mode = "mainmenu"; + E.showMenu(mainMenu); + }; + console.log(mainCheckHomeworkMenu); + E.showMenu(mainCheckHomeworkMenu); +} + +function checkFinishedHomeworkAssembler() { + homework = require("Storage").readJSON("homework.txt", "r"); + var hwcount = Object.keys(homework.homework).length; + mainCheckHomeworkMenu = { + '': { + 'title': 'Archived HW:' + } + }; + + // This code snippet gets the unfinished HW and puts it in mainCheckHomeworkMenu + // btw mainCheckHomeworkMenu displays all the homework, when tapping on it you get more details with checkPreciseHomework function (currently being written) + for (var i = 0; i < hwcount; ++i) { + if (homework.homework[i].done === true) { + var currentsubject = i; //attempting to pass i + mainCheckHomeworkMenu[homework.homework[i].subject] = checkPreciseHomework.bind(null, currentsubject); + } + + } + mainCheckHomeworkMenu["Back"] = function() { + mode = "mainmenu"; + E.showMenu(mainMenu); + }; + E.showMenu(mainCheckHomeworkMenu); +} + +function checkPreciseHomework(subjectnum) { // P A I N + homework = require("Storage").read("homework.txt", "r"); + homework = JSON.parse(homework); + var subject = homework.homework[subjectnum].subject; + var task = homework.homework[subjectnum].task; + var taskmsg = "Task: " + homework.homework[subjectnum].task; + if (homework.homework[subjectnum].done === false) { + statusmsg = "Status: Unfinished"; + } else { + statusmsg = "Status: Finished"; + } + var datetimerecieved = homework.homework[subjectnum].datetimerecievehw; + var datetimerecievedmsg = "Recieved: " + homework.homework[subjectnum].datetimerecievehw; + var checkPreciseHomeworkMenu = { + '': { + 'title': subject + }, + }; + checkPreciseHomeworkMenu[subject] = function() {}, + checkPreciseHomeworkMenu[taskmsg] = function() {}, + checkPreciseHomeworkMenu[statusmsg] = function() { + status = "Status: Finished"; + var d = new Date(); + var currenttime = require("locale").time(d, 1); + var currentdate = require("locale").date(d); + var datetime = (currenttime + " " + currentdate); + delete homework.homework[subjectnum]; + homework.homework.push({ + subject: subject, + task: task, + done: true, + datetimerecievehw: datetimerecieved, + datetimehwdone: datetime + }); + require("Storage").write("homework.txt", JSON.stringify(homework)); + checkUnfinishedHomeworkAssembler(); + }, + checkPreciseHomeworkMenu[datetimerecievedmsg] = function() {}, + checkPreciseHomeworkMenu["Back"] = function() { + checkUnfinishedHomeworkAssembler(); + }, + + E.showMenu(checkPreciseHomeworkMenu); + + +} + +function pushHomework(subject, status, datetimehwdone) { + homework = require("Storage").readJSON("homework.txt", "r"); + +} + +function addNewHomework(subject) { // Pass subject + console.log(subject); + require("textinput").input().then(result => { + if (result === "") { + mode = "newhomework"; + newHomeworkMenu(); + } else { + var d = new Date(); + // update time and date + var currenttime = require("locale").time(d, 1); + var currentdate = require("locale").date(d); + var datetime = (currenttime + " " + currentdate); + homework.homework.push({ + subject: subject, + task: result, + done: false, + datetimerecievehw: datetime + }); // TODO: when HW is done, add datetimeendhw !!! + console.log("subject is" + subject); + + //homework.homework[subject] = result; + require("Storage").write("homework.txt", JSON.stringify(homework)); + E.showMenu(mainMenu); + + } + }); + +} + +function main() { // why does this still exist + if (mode === "mainmenu") { + E.showMenu(mainMenu); + + } else if (mode === "newhomework") { + newHomeworkMenu() + + } +} +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +main(); +//loop = setInterval(main, 1); diff --git a/apps/homework/app.png b/apps/homework/app.png new file mode 100644 index 000000000..e6174d46b Binary files /dev/null and b/apps/homework/app.png differ diff --git a/apps/homework/metadata.json b/apps/homework/metadata.json new file mode 100644 index 000000000..2ba1e918f --- /dev/null +++ b/apps/homework/metadata.json @@ -0,0 +1,16 @@ + +{ "id": "homework", + "name": "Homework", + "shortName":"Homework", + "version":"0.1", + "description": "A simple app to manage homework", + "icon": "app.png", + "tags": "tool", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "custom": "subjects.html", + "storage": [ + {"name":"homework.app.js","url":"app.js"}, + {"name":"homework.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/homework/subjects.html b/apps/homework/subjects.html new file mode 100644 index 000000000..d3bf7a400 --- /dev/null +++ b/apps/homework/subjects.html @@ -0,0 +1,30 @@ + + + + + + +

Subjects:

+

Click

+ + + + + + diff --git a/apps/kbmulti/ChangeLog b/apps/kbmulti/ChangeLog index 04b2430bb..709aa3203 100644 --- a/apps/kbmulti/ChangeLog +++ b/apps/kbmulti/ChangeLog @@ -1 +1,2 @@ 0.01: New keyboard +0.02: Introduce setting "Show help button?". Make setting firstLaunch invisible by removing corresponding code from settings.js. Add marker that shows when character selection timeout has run out. Display opened text on launch when editing existing text string. Perfect horizontal alignment of buttons. Tweak help message letter casing. diff --git a/apps/kbmulti/README.md b/apps/kbmulti/README.md index d40b2dd07..4c83d378e 100644 --- a/apps/kbmulti/README.md +++ b/apps/kbmulti/README.md @@ -10,7 +10,8 @@ Uses the multitap keypad logic originally from here: http://www.espruino.com/Mor ![](screenshot_1.png) ![](screenshot_2.png) +![](screenshot_3.png) Written by: [Sir Indy](https://github.com/sir-indy) and [Thyttan](https://github.com/thyttan) -For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) \ No newline at end of file +For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 79c2d861a..5ccab4204 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -8,6 +8,7 @@ exports.input = function(options) { var settings = require('Storage').readJSON("kbmulti.settings.json", true) || {}; if (settings.firstLaunch===undefined) { settings.firstLaunch = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } + if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } var fontSize = "6x15"; var Layout = require("Layout"); @@ -16,26 +17,30 @@ exports.input = function(options) { "4":"GHI4","5":"JKL5","6":"MNO6", "7":"PQRS7","8":"TUV80","9":"WXYZ9", }; - var helpMessage = 'swipe:\nRight: Space\nLeft:Backspace\nUp/Down: Caps lock\n'; + var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp/Down: Caps lock\n'; var charTimeout; // timeout after a key is pressed var charCurrent; // current character (index in letters) var charIndex; // index in letters[charCurrent] var caps = true; var layout; + var btnWidth = g.getWidth()/3 - function displayText() { + function displayText(hideMarker) { layout.clear(layout.text); - layout.text.label = text.slice(-12); + layout.text.label = text.slice(settings.showHelpBtn ? -11 : -13) + (hideMarker ? " " : "_"); layout.render(layout.text); } - function backspace() { - // remove the timeout if we had one + function deactivateTimeout(charTimeout) { if (charTimeout!==undefined) { clearTimeout(charTimeout); charTimeout = undefined; } + } + + function backspace() { + deactivateTimeout(charTimeout); text = text.slice(0, -1); newCharacter(); } @@ -55,11 +60,7 @@ exports.input = function(options) { } function onKeyPad(key) { - // remove the timeout if we had one - if (charTimeout!==undefined) { - clearTimeout(charTimeout); - charTimeout = undefined; - } + deactivateTimeout(charTimeout); // work out which char was pressed if (key==charCurrent) { charIndex = (charIndex+1) % letters[charCurrent].length; @@ -69,12 +70,12 @@ exports.input = function(options) { } var newLetter = letters[charCurrent][charIndex]; text += (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()); - displayText(); // set a timeout charTimeout = setTimeout(function() { charTimeout = undefined; newCharacter(); }, settings.charTimeout); + displayText(charTimeout); } function onSwipe(dirLeftRight, dirUpDown) { @@ -104,25 +105,26 @@ exports.input = function(options) { type:"v", c: [ {type:"h", c: [ {type:"txt", font:"12x20", label:text.slice(-12), id:"text", fillx:1}, - {type:"btn", font:'6x8', label:'?', cb: l=>onHelp(resolve,reject), filly:1 }, + (settings.showHelpBtn ? {type:"btn", font:'6x8', label:'?', cb: l=>onHelp(resolve,reject), filly:1 } : {}), ]}, {type:"h", c: [ - {type:"btn", font:fontSize, label:letters[1], cb: l=>onKeyPad(1), id:'1', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[2], cb: l=>onKeyPad(2), id:'2', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[3], cb: l=>onKeyPad(3), id:'3', fillx:1, filly:1 }, + {type:"btn", font:fontSize, label:letters[1], cb: l=>onKeyPad(1), id:'1', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[2], cb: l=>onKeyPad(2), id:'2', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[3], cb: l=>onKeyPad(3), id:'3', width:btnWidth, filly:1 }, ]}, {type:"h", filly:1, c: [ - {type:"btn", font:fontSize, label:letters[4], cb: l=>onKeyPad(4), id:'4', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[5], cb: l=>onKeyPad(5), id:'5', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[6], cb: l=>onKeyPad(6), id:'6', fillx:1, filly:1 }, + {type:"btn", font:fontSize, label:letters[4], cb: l=>onKeyPad(4), id:'4', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[5], cb: l=>onKeyPad(5), id:'5', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[6], cb: l=>onKeyPad(6), id:'6', width:btnWidth, filly:1 }, ]}, {type:"h", filly:1, c: [ - {type:"btn", font:fontSize, label:letters[7], cb: l=>onKeyPad(7), id:'7', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[8], cb: l=>onKeyPad(8), id:'8', fillx:1, filly:1 }, - {type:"btn", font:fontSize, label:letters[9], cb: l=>onKeyPad(9), id:'9', fillx:1, filly:1 }, + {type:"btn", font:fontSize, label:letters[7], cb: l=>onKeyPad(7), id:'7', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[8], cb: l=>onKeyPad(8), id:'8', width:btnWidth, filly:1 }, + {type:"btn", font:fontSize, label:letters[9], cb: l=>onKeyPad(9), id:'9', width:btnWidth, filly:1 }, ]}, ] },{back: ()=>{ + deactivateTimeout(charTimeout); Bangle.setUI(); Bangle.removeListener("swipe", onSwipe); g.clearRect(Bangle.appRect); @@ -132,12 +134,13 @@ exports.input = function(options) { return new Promise((resolve,reject) => { g.clearRect(Bangle.appRect); - if (settings.firstLaunch) { - onHelp(resolve,reject); + if (settings.firstLaunch) { + onHelp(resolve,reject); settings.firstLaunch = false; require('Storage').writeJSON("kbmulti.settings.json", settings); } else { generateLayout(resolve,reject); + displayText(false); Bangle.on('swipe', onSwipe); layout.render(); } diff --git a/apps/kbmulti/metadata.json b/apps/kbmulti/metadata.json index 6c813e321..1efdb8847 100644 --- a/apps/kbmulti/metadata.json +++ b/apps/kbmulti/metadata.json @@ -1,6 +1,6 @@ { "id": "kbmulti", "name": "Multitap keyboard", - "version":"0.01", + "version":"0.02", "description": "A library for text input via multitap/T9 style keypad", "icon": "app.png", "type":"textinput", diff --git a/apps/kbmulti/screenshot_3.png b/apps/kbmulti/screenshot_3.png new file mode 100644 index 000000000..882ea7386 Binary files /dev/null and b/apps/kbmulti/screenshot_3.png differ diff --git a/apps/kbmulti/settings.js b/apps/kbmulti/settings.js index d3148eca7..8a66cd8f0 100644 --- a/apps/kbmulti/settings.js +++ b/apps/kbmulti/settings.js @@ -1,7 +1,7 @@ (function(back) { function settings() { var settings = require('Storage').readJSON("kbmulti.settings.json", true) || {}; - if (settings.firstLaunch===undefined) { settings.firstLaunch = true; } + if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } return settings; } @@ -21,11 +21,11 @@ format: v => v, onchange: v => updateSetting("charTimeout", v), }, - /*LANG*/'Show help on first launch': { - value: !!settings().firstLaunch, + /*LANG*/'Show help button?': { + value: !!settings().showHelpBtn, format: v => v?"Yes":"No", - onchange: v => updateSetting("firstLaunch", v) + onchange: v => updateSetting("showHelpBtn", v) } }; E.showMenu(mainmenu); - }) \ No newline at end of file + }) diff --git a/apps/multitimer/ChangeLog b/apps/multitimer/ChangeLog new file mode 100644 index 000000000..624f1b0fb --- /dev/null +++ b/apps/multitimer/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version \ No newline at end of file diff --git a/apps/multitimer/README.md b/apps/multitimer/README.md new file mode 100644 index 000000000..f1e2eb281 --- /dev/null +++ b/apps/multitimer/README.md @@ -0,0 +1,10 @@ +# Multi Timer +With this app, you can set timers and chronographs (stopwatches) and watch them count down/up in real time. You can also set alarms - swipe left or right to switch between the three functions. + +"Hard mode" is also available for timers and alarms. It will double the number of buzz counts and you will have to swipe the screen five to eight times correctly - make a mistake, and you will need to start over. + +## WARNING +* Editing timers in another app (such as the default Alarm app) is not recommended. Editing alarms should not be a problem (in theory). +* This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched). +* To avoid potential conflicts with other apps that uses sched (especially ones that make use of the data and js field), this app only lists timers and alarms that it created - any made outside the app will be ignored. GB alarms are currently an exception as they do not make use of the data and js field. +* A keyboard app is only used for adding messages to timers and is therefore not strictly needed. diff --git a/apps/multitimer/alarm.js b/apps/multitimer/alarm.js new file mode 100644 index 000000000..fc0195455 --- /dev/null +++ b/apps/multitimer/alarm.js @@ -0,0 +1,148 @@ +//sched.js, modified +// Chances are boot0.js got run already and scheduled *another* +// 'load(sched.js)' - so let's remove it first! +if (Bangle.SCHED) { + clearInterval(Bangle.SCHED); + delete Bangle.SCHED; +} + +function hardMode(tries, max) { + var R = Bangle.appRect; + + function adv() { + tries++; + hardMode(tries, max); + } + + if (tries < max) { + g.clear(); + g.reset(); + g.setClipRect(R.x,R.y,R.x2,R.y2); + var code = Math.abs(E.hwRand()%4); + if (code == 0) dir = "up"; + else if (code == 1) dir = "right"; + else if (code == 2) dir = "down"; + else dir = "left"; + g.setFont("6x8:2").setFontAlign(0,0).drawString(tries+"/"+max+"\nSwipe "+dir, (R.x2-R.x)/2, (R.y2-R.y)/2); + var drag; + Bangle.setUI({ + mode : "custom", + drag : e=>{ + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + //horizontal swipes + if (Math.abs(dx)>Math.abs(dy)+10) { + //left + if (dx<0 && code == 3) adv(); + //right + else if (dx>0 && code == 1) adv(); + //wrong swipe - reset + else startHM(); + } + //vertical swipes + else if (Math.abs(dy)>Math.abs(dx)+10) { + //up + if (dy<0 && code == 0) adv(); + //down + else if (dy>0 && code == 2) adv(); + //wrong swipe - reset + else startHM(); + } + } + } + }); + } + else { + if (!active[0].timer) active[0].last = (new Date()).getDate(); + if (!active[0].rp) active[0].on = false; + if (active[0].timer) active[0].timer = active[0].data.ot; + require("sched").setAlarms(alarms); + load(); + } +} + +function startHM() { + //between 5-8 random swipes + hardMode(0, Math.abs(E.hwRand()%4)+5); +} + +function showAlarm(alarm) { + const settings = require("sched").getSettings(); + + let msg = ""; + msg += require("sched").formatTime(alarm.timer); + if (alarm.msg) { + msg += "\n"+alarm.msg; + } + else msg = atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==")+" "+msg; + + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + let buzzCount = settings.buzzCount; + + if (alarm.data.hm && alarm.data.hm == true) { + //hard mode extends auto-snooze time + buzzCount = buzzCount * 2; + startHM(); + } + + else { + E.showPrompt(msg,{ + title: "TIMER!", + buttons : {"Snooze":true,"Ok":false} // default is sleep so it'll come back in 10 mins + }).then(function(sleep) { + buzzCount = 0; + if (sleep) { + if(alarm.ot===undefined) alarm.ot = alarm.t; + alarm.t += settings.defaultSnoozeMillis; + } else { + if (!alarm.timer) alarm.last = (new Date()).getDate(); + if (alarm.ot!==undefined) { + alarm.t = alarm.ot; + delete alarm.ot; + } + if (!alarm.rp) alarm.on = false; + } + //reset timer value + if (alarm.timer) alarm.timer = alarm.data.ot; + // alarm is still a member of 'alarms', so writing to array writes changes back directly + require("sched").setAlarms(alarms); + load(); + }); + } + + function buzz() { + if (settings.unlockAtBuzz) { + Bangle.setLocked(false); + } + + require("buzz").pattern(alarm.vibrate === undefined ? ".." : alarm.vibrate).then(() => { + if (buzzCount--) { + setTimeout(buzz, settings.buzzIntervalMillis); + } else if (alarm.as) { // auto-snooze + buzzCount = settings.buzzCount; + setTimeout(buzz, settings.defaultSnoozeMillis); + } + }); + } + + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) + return; + + buzz(); +} + +// Check for alarms +let alarms = require("sched").getAlarms(); +let active = require("sched").getActiveAlarms(alarms); +if (active.length) { + // if there's an alarm, show it + showAlarm(active[0]); +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/multitimer/app-icon.js b/apps/multitimer/app-icon.js new file mode 100644 index 000000000..6eac4ab31 --- /dev/null +++ b/apps/multitimer/app-icon.js @@ -0,0 +1 @@ +atob("MDCBAYAAAAAAfwAAAAAAPz//////Pz//////Pz//////Pz//////Pz//////PwAAAAAAPwAAAAAAPz//////Pz//////Pz//////Pz//////Pz//////Pz48AAAfPz4YAAAPPz//////Pz//////Pz//////Pz48AAAfPz4YAAAPPz//////Pz//////Pz//////Pz4YAAAPPz4YAAAfPz//////vz///////z///////z4YAAH4Pz48AAP4Dz////95hz////594z////x/8T////zP+T////nH+D////nj/AAAADnw/AAAABn4/P////n4/P////n//P////n/+f////z/+f////x/8/////4/4/////8Ph/////+AH//////gfw==") \ No newline at end of file diff --git a/apps/multitimer/app.js b/apps/multitimer/app.js new file mode 100644 index 000000000..becaf6169 --- /dev/null +++ b/apps/multitimer/app.js @@ -0,0 +1,680 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var R = Bangle.appRect; +var layer; +var drag; +var timerInt1 = []; +var timerInt2 = []; + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function decodeTime(t) { + let hrs = 0 | Math.floor(t / 3600000); + let mins = 0 | Math.floor(t / 60000 % 60); + let secs = 0 | Math.floor(t / 1000 % 60); + return { hrs: hrs, mins: mins, secs: secs }; +} + +function encodeTime(o) { + return o.hrs * 3600000 + o.mins * 60000 + o.secs * 1000; +} + +function formatTime(t) { + let o = decodeTime(t); + return o.hrs + ":" + ("0" + o.mins).substr(-2) + ":" + ("0" + o.secs).substr(-2); +} + +function decodeTimeDecis(t) { + let hrs = 0 | Math.floor(t / 3600000); + let mins = 0 | Math.floor(t / 60000 % 60); + let secs = 0 | Math.floor(t / 1000 % 60); + let decis = 0 | Math.floor(t / 100 % 100); + return { hrs: hrs, mins: mins, secs: secs, decis: decis }; +} + +function formatTimeDecis(t) { + let o = decodeTimeDecis(t); + return o.hrs + ":" + ("0" + o.mins).substr(-2) + ":" + ("0" + o.secs).substr(-2) + "." + ("0" + o.decis).substr(-1); +} + +function clearInt() { + for (let i = 0; i < timerInt1.length; i++) { + if (timerInt1[i]) clearTimeout(timerInt1[i]); + } + for (let i = 0; i < timerInt2.length; i++) { + if (timerInt2[i]) clearInterval(timerInt2[i]); + } + timerInt1 = []; + timerInt2 = []; +} + +function drawTimers() { + layer = 0; + var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer"); + var alarms = require("sched").getAlarms(); + + function updateTimers(idx) { + if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() { + s.drawItem(idx+1); + if (!timerInt2[idx]) timerInt2[idx] = setInterval(function(){ + s.drawItem(idx+1); + }, 1000); + }, 1000 - (timers[idx].t % 1000)); + } + + var s = E.showScroller({ + h : 40, c : timers.length+2, + back : function() {load();}, + draw : (idx, r) => { + function drawMenuItem(a) { + g.setClipRect(R.x,R.y,R.x2,R.y2); + if (idx > 0 && timers[idx-1].msg) msg = "\n"+(timers[idx-1].msg.length > 10 ? + timers[idx-1].msg.substring(0, 10)+"..." : timers[idx-1].msg); + else msg = ""; + return g.setColor(g.theme.bg2).fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg2).setFont("6x8:2").setFontAlign(-1,0).drawString(a+msg,r.x+12,r.y+(r.h/2)); + } + + if (idx == 0) { + drawMenuItem("+ New Timer"); + } + if (idx == timers.length+1) { + g.setColor(g.theme.bg).fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg).setFont("6x8:2").setFontAlign(0,0).drawString("< Swipe >",r.x+(r.w/2),r.y+(r.h/2)); + } + else if (idx > 0 && idx < timers.length+1) { + if (timers[idx-1].on == true) { + drawMenuItem(formatTime(timers[idx-1].t-getCurrentTime())); + updateTimers(idx-1); + } + else drawMenuItem(formatTime(timers[idx-1].timer)); + } + }, + select : (idx) => { + clearInt(); + if (idx == 0) editTimer(-1); + else if (idx > 0 && idx < timers.length+1) timerMenu(idx-1); + } + }); +} + +function timerMenu(idx) { + layer = -1; + var timers = require("sched").getAlarms(); + var timerIdx = []; + var j = 0; + for (let i = 0; i < timers.length; i++) { + if (timers[i].timer && timers[i].appid == "multitimer") { + a = i; + timerIdx.push(a); + j++; + } + } + var a = timers[timerIdx[idx]]; + var msg = ""; + + function updateTimer() { + if (timerInt1[0] == undefined) timerInt1[0] = setTimeout(function() { + s.drawItem(0); + if (timerInt2[0] == undefined) timerInt2[0] = setInterval(function(){ + s.drawItem(0); + }, 1000); + }, 1000 - (a.t % 1000)); + } + + var s = E.showScroller({ + h : 40, c : 5, + back : function() { + clearInt(); + drawTimers(); + }, + draw : (i, r) => { + + function drawMenuItem(b) { + return g.setClipRect(R.x,R.y,R.x2,R.y2).setColor(g.theme.bg2) + .fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg2).setFont("6x8:2").setFontAlign(-1,0).drawString(b,r.x+12,r.y+(r.h/2)); + } + + if (i == 0) { + if (a.msg) msg = "\n"+(a.msg.length > 10 ? a.msg.substring(0, 10)+"..." : a.msg); + if (a.on == true) { + drawMenuItem(formatTime(a.t-getCurrentTime())+msg); + updateTimer(); + } + else { + clearInt(); + drawMenuItem(formatTime(a.timer)+msg); + } + } + if (i == 1) { + if (a.on == true) drawMenuItem("Pause"); + else drawMenuItem("Start"); + } + if (i == 2) drawMenuItem("Reset"); + if (i == 3) drawMenuItem("Edit"); + if (i == 4) drawMenuItem("Delete"); + }, + select : (i) => { + + function saveAndReload() { + require("sched").setAlarms(timers); + require("sched").reload(); + s.draw(); + } + + //pause/start + if (i == 1) { + if (a.on == true) { + clearInt(); + a.timer = a.t-getCurrentTime(); + a.on = false; + timers[timerIdx[idx]] = a; + saveAndReload(); + } + else { + a.t = a.timer+getCurrentTime(); + a.on = true; + timers[timerIdx[idx]] = a; + saveAndReload(); + } + } + //reset + if (i == 2) { + clearInt(); + a.timer = a.data.ot; + if (a.on == true) a.on = false; + saveAndReload(); + } + //edit + if (i == 3) { + clearInt(); + editTimer(idx); + } + //delete + if (i == 4) { + clearInt(); + timers.splice(timerIdx[idx], 1); + saveAndReload(); + drawTimers(); + } + } + }); +} + +function editTimer(idx, a) { + layer = -1; + var timers = require("sched").getAlarms().filter(a => a.timer && a.appid == "multitimer"); + var alarms = require("sched").getAlarms(); + var timerIdx = []; + var j = 0; + for (let i = 0; i < alarms.length; i++) { + if (alarms[i].timer && alarms[i].appid == "multitimer") { + b = i; + timerIdx.push(b); + j++; + } + } + if (!a) { + if (idx < 0) a = require("sched").newDefaultTimer(); + else a = timers[idx]; + } + if (!a.data) { + a.data = {}; + a.data.hm = false; + } + var t = decodeTime(a.timer); + + function editMsg(idx, a) { + g.clear(); + idx < 0 ? msg = "" : msg = a.msg; + require("textinput").input({text:msg}).then(result => { + if (result != "") { + a.msg = result; + } + else delete a.msg; + editTimer(idx, a); + }); + } + + function kbAlert() { + E.showAlert("Must install keyboard app").then(function() { + editTimer(idx, a); + }); + } + + var menu = { + "": { "title": "Timer" }, + "< Back": () => { + a.t = getCurrentTime() + a.timer; + a.last = 0; + a.data.ot = a.timer; + a.appid = "multitimer"; + a.js = "load('multitimer.alarm.js')"; + if (idx < 0) alarms.push(a); + else alarms[timerIdx[idx]] = a; + require("sched").setAlarms(alarms); + require("sched").reload(); + drawTimers(); + }, + "Enabled": { + value: a.on, + format: v => v ? "On" : "Off", + onchange: v => a.on = v + }, + "Hours": { + value: t.hrs, min: 0, max: 23, wrap: true, + onchange: v => { + t.hrs = v; + a.timer = encodeTime(t); + } + }, + "Minutes": { + value: t.mins, min: 0, max: 59, wrap: true, + onchange: v => { + t.mins = v; + a.timer = encodeTime(t); + } + }, + "Seconds": { + value: t.secs, min: 0, max: 59, wrap: true, + onchange: v => { + t.secs = v; + a.timer = encodeTime(t); + } + }, + "Hard Mode": { + value: a.data.hm, + format: v => v ? "On" : "Off", + onchange: v => a.data.hm = v + }, + "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), + "Msg": { + value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg, + //menu glitch? setTimeout required here + onchange: () => { + var kbapp = require("Storage").read("textinput"); + if (kbapp != undefined) setTimeout(editMsg, 0, idx, a); + else setTimeout(kbAlert, 0); + } + }, + "Cancel": () => { + if (idx >= 0) timerMenu(idx); + else drawTimers(); + }, + }; + + E.showMenu(menu); +} + +function drawSw() { + layer = 1; + var sw = require("Storage").readJSON("multitimer.json", true) || []; + + function updateTimers(idx) { + if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() { + s.drawItem(idx+1); + if (!timerInt2[idx]) timerInt2[idx] = setInterval(function(){ + s.drawItem(idx+1); + }, 1000); + }, 1000 - (sw[idx].t % 1000)); + } + + var s = E.showScroller({ + h : 40, c : sw.length+2, + back : function() {load();}, + draw : (idx, r) => { + + function drawMenuItem(a) { + g.setClipRect(R.x,R.y,R.x2,R.y2); + if (idx > 0 && sw[idx-1].msg) msg = "\n"+(sw[idx-1].msg.length > 10 ? + sw[idx-1].msg.substring(0, 10)+"..." : sw[idx-1].msg); + else msg = ""; + return g.setColor(g.theme.bg2).fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg2).setFont("6x8:2").setFontAlign(-1,0).drawString(a+msg,r.x+12,r.y+(r.h/2)); + } + + if (idx == 0) { + drawMenuItem("+ New Chrono"); + } + if (idx == sw.length+1) { + g.setColor(g.theme.bg).fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg).setFont("6x8:2").setFontAlign(0,0).drawString("< Swipe >",r.x+(r.w/2),r.y+(r.h/2)); + } + else if (idx > 0 && idx < sw.length+1) { + if (sw[idx-1].on == true) { + drawMenuItem(formatTime(Date.now()-sw[idx-1].t)); + updateTimers(idx-1); + } + else drawMenuItem(formatTime(sw[idx-1].t)); + } + }, + select : (idx) => { + clearInt(); + if (idx == 0) swMenu(sw.length); + else if (idx > 0 && idx < sw.length+1) swMenu(idx-1); + } + }); +} + +function swMenu(idx, a) { + layer = -1; + var sw = require("Storage").readJSON("multitimer.json", true) || []; + if (sw[idx]) a = sw[idx]; + else { + a = {"t" : 0, "on" : false, "msg" : ""}; + sw[idx] = a; + require("Storage").writeJSON("multitimer.json", sw); + } + + function updateTimer() { + if (timerInt1[0] == undefined) timerInt1[0] = setTimeout(function() { + s.drawItem(0); + if (timerInt2[0] == undefined) timerInt2[0] = setInterval(function(){ + s.drawItem(0); + }, 100); + }, 100 - (a.t % 100)); + } + + function editMsg(idx, a) { + g.clear(); + msg = a.msg; + require("textinput").input({text:msg}).then(result => { + if (result != "") { + a.msg = result; + } + else delete a.msg; + sw[idx] = a; + require("Storage").writeJSON("multitimer.json", sw); + swMenu(idx, a); + }); + } + + function kbAlert() { + E.showAlert("Must install keyboard app").then(function() { + swMenu(idx, a); + }); + } + + var s = E.showScroller({ + h : 40, c : 5, + back : function() { + clearInt(); + drawSw(); + }, + draw : (i, r) => { + + function drawMenuItem(b) { + return g.setClipRect(R.x,R.y,R.x2,R.y2).setColor(g.theme.bg2) + .fillRect({x:r.x+4,y:r.y+2,w:r.w-8, h:r.h-4, r:5}) + .setColor(g.theme.fg2).setFont("6x8:2").setFontAlign(-1,0).drawString(b,r.x+12,r.y+(r.h/2)); + } + + if (i == 0) { + if (a.msg) msg = "\n"+(a.msg.length > 10 ? a.msg.substring(0, 10)+"..." : a.msg); + else msg = ""; + if (a.on == true) { + drawMenuItem(formatTimeDecis(Date.now()-a.t)+msg); + updateTimer(); + } + else { + clearInt(); + drawMenuItem(formatTimeDecis(a.t)+msg); + } + } + if (i == 1) { + if (a.on == true) drawMenuItem("Pause"); + else drawMenuItem("Start"); + } + if (i == 2) drawMenuItem("Reset"); + if (i == 3) drawMenuItem("Msg"); + if (i == 4) drawMenuItem("Delete"); + }, + select : (i) => { + + function saveAndReload() { + require("Storage").writeJSON("multitimer.json", sw); + s.draw(); + } + + //pause/start + if (i == 1) { + if (a.on == true) { + clearInt(); + a.t = Date.now()-a.t; + a.on = false; + sw[idx] = a; + saveAndReload(); + } + else { + a.t == 0 ? a.t = Date.now() : a.t = Date.now()-a.t; + a.on = true; + sw[idx] = a; + saveAndReload(); + } + } + //reset + if (i == 2) { + clearInt(); + a.t = 0; + if (a.on == true) a.on = false; + saveAndReload(); + } + //edit message + if (i == 3) { + clearInt(); + var kbapp = require("Storage").read("textinput"); + if (kbapp != undefined) editMsg(idx, a); + else kbAlert(); + } + //delete + if (i == 4) { + clearInt(); + sw.splice(idx, 1); + saveAndReload(); + drawSw(); + } + } + }); +} + +function drawAlarms() { + layer = 2; + var alarms = require("sched").getAlarms().filter(a => !a.timer); + + var s = E.showScroller({ + h : 40, c : alarms.length+2, + back : function() {load();}, + draw : (idx, r) => { + + function drawMenuItem(a) { + g.setClipRect(R.x,R.y,R.x2,R.y2); + var on = ""; + var dow = ""; + if (idx > 0 && alarms[idx-1].on == true) on = " - on"; + else if (idx > 0 && alarms[idx-1].on == false) on = " - off"; + if (idx > 0 && idx < alarms.length+1) dow = "\n"+"SMTWTFS".split("").map((d,n)=>alarms[idx-1].dow&(1<",r.x+(r.w/2),r.y+(r.h/2)); + } + else if (idx > 0 && idx < alarms.length+1){ + var str = formatTime(alarms[idx-1].t); + drawMenuItem(str.slice(0, -3)); + } + }, + select : (idx) => { + clearInt(); + if (idx == 0) editAlarm(-1); + else if (idx > 0 && idx < alarms.length+1) editAlarm(idx-1); + } + }); +} + +function editDOW(dow, onchange) { + const menu = { + '': { 'title': 'Days of Week' }, + '< Back' : () => onchange(dow) + }; + for (var i = 0; i < 7; i++) (i => { + var dayOfWeek = require("locale").dow({ getDay: () => i }); + menu[dayOfWeek] = { + value: !!(dow&(1< v ? "Yes" : "No", + onchange: v => v ? dow |= 1<= 0) a = alarms[alarmIdx[idx]]; + else a = require("sched").newDefaultAlarm(); + } + if (!a.data) { + a.data = {}; + a.data.hm = false; + } + var t = decodeTime(a.t); + + function editMsg(idx, a) { + g.clear(); + idx < 0 ? msg = "" : msg = a.msg; + require("textinput").input({text:msg}).then(result => { + if (result != "") { + a.msg = result; + } + else delete a.msg; + editAlarm(idx, a); + }); + } + + function kbAlert() { + E.showAlert("Must install keyboard app").then(function() { + editAlarm(idx, a); + }); + } + + var menu = { + "": { "title": "Alarm" }, + "< Back": () => { + if (a.data.hm == true) a.js = "load('multitimer.alarm.js')"; + if (a.data.hm == false && a.js) delete a.js; + if (idx >= 0) alarms[alarmIdx[idx]] = a; + else alarms.push(a); + require("sched").setAlarms(alarms); + require("sched").reload(); + drawAlarms(); + }, + "Enabled": { + value: a.on, + format: v => v ? "On" : "Off", + onchange: v => a.on = v + }, + "Hours": { + value: t.hrs, min: 0, max: 23, wrap: true, + onchange: v => { + t.hrs = v; + a.t = encodeTime(t); + } + }, + "Minutes": { + value: t.mins, min: 0, max: 59, wrap: true, + onchange: v => { + t.mins = v; + a.t = encodeTime(t); + } + }, + "Repeat": { + value: a.rp, + format: v => v ? "Yes" : "No", + onchange: v => a.rp = v + }, + "Days": { + value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d=>{a.dow=d;editAlarm(idx,a);}) + }, + "Hard Mode": { + value: a.data.hm, + format: v => v ? "On" : "Off", + onchange: v => a.data.hm = v + }, + "Vibrate": require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v), + "Auto Snooze": { + value: a.as, + format: v => v ? "Yes" : "No", + onchange: v => a.as = v + }, + "Msg": { + value: !a.msg ? "" : a.msg.length > 6 ? a.msg.substring(0, 6)+"..." : a.msg, + //menu glitch? setTimeout required here + onchange: () => { + var kbapp = require("Storage").read("textinput"); + if (kbapp != undefined) setTimeout(editMsg, 0, idx, a); + else setTimeout(kbAlert, 0); + } + }, + "Delete": () => { + if (idx >= 0) { + alarms.splice(alarmIdx[idx], 1); + require("sched").setAlarms(alarms); + require("sched").reload(); + } + drawAlarms(); + }, + }; + + E.showMenu(menu); +} + +drawTimers(); + +Bangle.on("drag", e=>{ + if (layer < 0) return; + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } + else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (dx == 0) return; + //horizontal swipes + if (Math.abs(dx)>Math.abs(dy)+10) { + //swipe left + if (dx<0) layer == 2 ? layer = 0 : layer++; + //swipe right + if (dx>0) layer == 0 ? layer = 2 : layer--; + clearInt(); + if (layer == 0) drawTimers(); + else if (layer == 1) drawSw(); + else if (layer == 2) drawAlarms(); + } + } +}); diff --git a/apps/multitimer/app.png b/apps/multitimer/app.png new file mode 100644 index 000000000..3006b0a26 Binary files /dev/null and b/apps/multitimer/app.png differ diff --git a/apps/multitimer/metadata.json b/apps/multitimer/metadata.json new file mode 100644 index 000000000..6e53e2c8c --- /dev/null +++ b/apps/multitimer/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "multitimer", + "name": "Multi Timer", + "version": "0.01", + "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": [ + {"url":"screenshot1.png"}, + {"url":"screenshot2.png"}, + {"url":"screenshot3.png"} + ], + "tags": "tool,alarm", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"multitimer.app.js","url":"app.js"}, + {"name":"multitimer.alarm.js","url":"alarm.js"}, + {"name":"multitimer.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"multitimer.json"}], + "dependencies": {"scheduler":"type"} +} \ No newline at end of file diff --git a/apps/multitimer/screenshot1.png b/apps/multitimer/screenshot1.png new file mode 100644 index 000000000..0226ce495 Binary files /dev/null and b/apps/multitimer/screenshot1.png differ diff --git a/apps/multitimer/screenshot2.png b/apps/multitimer/screenshot2.png new file mode 100644 index 000000000..23a0d4c22 Binary files /dev/null and b/apps/multitimer/screenshot2.png differ diff --git a/apps/multitimer/screenshot3.png b/apps/multitimer/screenshot3.png new file mode 100644 index 000000000..6dc2fdf15 Binary files /dev/null and b/apps/multitimer/screenshot3.png differ diff --git a/apps/pokeclk/ChangeLog b/apps/pokeclk/ChangeLog new file mode 100644 index 000000000..09ae62ed3 --- /dev/null +++ b/apps/pokeclk/ChangeLog @@ -0,0 +1 @@ +0.01: New face :) diff --git a/apps/pokeclk/README.md b/apps/pokeclk/README.md new file mode 100644 index 000000000..6a76623c9 --- /dev/null +++ b/apps/pokeclk/README.md @@ -0,0 +1,7 @@ +# About this Watchface + +Based on the electronic device from the Pokemon game. + +# Features + +Has a dark mode diff --git a/apps/pokeclk/app-icon.js b/apps/pokeclk/app-icon.js new file mode 100644 index 000000000..4b948799c --- /dev/null +++ b/apps/pokeclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIWTgfAAocH8AFDh/wAp08AoM8AoN+AoN+AoP/AoP/AoWP+IFDAAQFHv4EB/wFBn4FB/gFBj4FB/A6CAoI8CApUHDYIfB8AKB/AfB+ACB+fPBAZ3BApP774FBDoopB/xPBRYJBQLIxlFOIqDLSoyhEVoq5FaKLpGeooAP")) diff --git a/apps/pokeclk/app.js b/apps/pokeclk/app.js new file mode 100644 index 000000000..8c1a00556 --- /dev/null +++ b/apps/pokeclk/app.js @@ -0,0 +1,92 @@ +Modules.addCached("Font4x5",function(){exports.add=function(a){a.prototype.setFont4x5=function(){this.setFontCustom(atob("AAAAdBgGAfV8CfyBIiQKrcAMAA6IARcAFXVARxAAwABCEAAIAAGTAPx+BHwAvXoK1+DhPg7W4P1uCEPg/X4O1+ACgACoAIqIBSlAIqIIVQC9VAfR4P1UB0VA/FwP1qD9KAdGYPk+AHwAEHwPk2D4Qg+j4PweB0XA/RAHTeD9FgTWQIfgD4fg8HwPi+DZNgwfAJ1yD8QAwQYI/ABEEACEIIIAB9Hg/VQHRUD8XA/WoP0oB0Zg+T4AfAAQfA+TYPhCD6Pg/B4HRcD9EAdN4P0WBNZAh+APh+DwfA+L4Nk2DB8AnXICfiAGwAj8gIYQAA=="),32,4,5)}}}); + +const offset = 25; +const width = g.getWidth(); +const height = g.getHeight(); +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==")) +}; + +var night= { + width : 89, height : 76, bpp : 4, + 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 + // work out how to display the current time + const d = new Date(); + const h = d.getHours(), + m = d.getMinutes(); + const time = h + ":" + ("0" + m).substr(-2); + const day = Date.now(); + const mo = d.getMonth()+1; + 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); + + // date + require("Font4x5").add(Graphics); + g.setFontAlign(1,1); + //g.setFont("4x6",2); + g.setFont("4x5",3); + g.drawString(dayMonth, width/2+60, height/2+40); + +} + +function isDark(){ + if (g.theme.dark==true){ + g.setColor(0xFFFF); + } + else { + g.setColor(0x0000); + } +} + +function draw() { //poketch background + if (g.theme.dark==true){ + g.drawImage(night, 0, 25, {scale:2}); //poketch is life + } + else { + g.drawImage(img, 0, 0); //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 +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 1000); + draw(); // draw immediately + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/pokeclk/app.png b/apps/pokeclk/app.png new file mode 100644 index 000000000..56789ab22 Binary files /dev/null and b/apps/pokeclk/app.png differ diff --git a/apps/pokeclk/metadata.json b/apps/pokeclk/metadata.json new file mode 100644 index 000000000..b7fca3273 --- /dev/null +++ b/apps/pokeclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "pokeclk", + "name": "Poketch Clock", + "shortName":"Poketch Clock", + "version": "0.01", + "description": "A clock based on the Poketch electronic device found in Sinnoh", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", + "storage": [ + {"name":"pokeclk.app.js","url":"app.js"}, + {"name":"pokeclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index b80dfef94..5dd28453e 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -2,4 +2,6 @@ 0.02: Fix typo to Purple 0.03: Added dependancy on Pedometer Widget 0.04: Fixed icon and png to 48x48 pixels -0.05: added charging icon \ No newline at end of file +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 diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index b26fb6a27..e1f18ea55 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,7 +2,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.05", + "version": "0.07", "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", diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index 7c7d57939..bdc7ed1a3 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -1,8 +1,10 @@ var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); const SETTINGS_FILE = "rebble.json"; const LOCATION_FILE = "mylocation.json"; +const GLOBAL_SETTINGS = "setting.json"; let settings; let location; +let is12Hour; Graphics.prototype.setFontLECO1976Regular22 = function(scale) { // Actual height 22 (21 - 0) @@ -33,12 +35,26 @@ function loadLocation() { } function loadSettings() { - settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green'}; + settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'autoCycle': true}; + is12Hour = (require("Storage").readJSON(GLOBAL_SETTINGS, 1) || {})["12hour"] || false; +} + +const zeroPad = (num, places) => String(num).padStart(places, '0') + +function formatHours(h) { + if (is12Hour) { + if (h == 0) { + h = 12; + } else if (h > 12) { + h -= 12; + } + } + return zeroPad(h, 2); } function extractTime(d){ var h = d.getHours(), m = d.getMinutes(); - return(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2)); + return(formatHours(h) + ":" + zeroPad(m, 2)); } function updateSunRiseSunSet(lat, lon){ @@ -78,9 +94,12 @@ const wb = 40; // battery width function draw() { log_debug("draw()"); let date = new Date(); - let da = date.toString().split(" "); - let hh = da[4].substr(0,2); - let mm = da[4].substr(3,2); + let hh = date.getHours(); + let mm = date.getMinutes(); + + hh = formatHours(hh); + mm = zeroPad(mm,2); + //const t = 6; if (drawCount % 60 == 0) @@ -117,8 +136,11 @@ function draw() { function drawSideBar1() { let date = new Date(); - let da = date.toString().split(" "); + let dy=require("date_utils").dow(date.getDay(),1).toUpperCase(); + let dd=date.getDate(); + let mm=require("date_utils").month(date.getMonth()+1,1).toUpperCase(); + drawBattery(w2 + (w-w2-wb)/2, h/10, wb, 17); setTextColor(); @@ -126,7 +148,7 @@ function drawSideBar1() { g.setFontAlign(0, -1); g.drawString(E.getBattery() + '%', w3, (h/10) + 17 + 7); - drawDateAndCalendar(w3, h/2, da[0], da[2], da[1]); + drawDateAndCalendar(w3, h/2, dy, dd, mm); } function drawSideBar2() { @@ -260,7 +282,9 @@ function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; - nextSidebar(); + if (!settings.autoCycle) { + nextSidebar(); + } draw(); }, 60000 - (Date.now() % 60000)); } diff --git a/apps/rebble/rebble.settings.js b/apps/rebble/rebble.settings.js index db3bab878..91142d72d 100644 --- a/apps/rebble/rebble.settings.js +++ b/apps/rebble/rebble.settings.js @@ -2,19 +2,19 @@ const SETTINGS_FILE = "rebble.json"; // initialize with default settings... - let s = {'bg': '#0f0', 'color': 'Green'} + let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true} // ...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) || s; + let settings = storage.readJSON(SETTINGS_FILE, 1) || localSettings; const saved = settings || {} for (const key in saved) { - s[key] = saved[key] + localSettings[key] = saved[key] } function save() { - settings = s + settings = localSettings storage.write(SETTINGS_FILE, settings) } @@ -25,14 +25,22 @@ '': { 'title': 'Rebble Clock' }, '< Back': back, 'Colour': { - value: 0 | color_options.indexOf(s.color), + value: 0 | color_options.indexOf(localSettings.color), min: 0, max: 5, format: v => color_options[v], onchange: v => { - s.color = color_options[v]; - s.bg = bg_code[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(); + } } }); }) diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 914bd7002..9c2f68348 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -7,3 +7,5 @@ 0.07: Update settings Correct `decodeTime(t)` to return a more likely expected time 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 diff --git a/apps/sched/boot.js b/apps/sched/boot.js index dbdf02593..06dc4f308 100644 --- a/apps/sched/boot.js +++ b/apps/sched/boot.js @@ -24,7 +24,7 @@ will then clearInterval() to get rid of this call so it can proceed normally. If active[0].js is defined, just run that code as-is and not alarm.js */ - Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t); + Bangle.SCHED = setTimeout(require("Storage").read(active[0].js)!==undefined ? 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 ff35c94cb..063402e3d 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -61,12 +61,12 @@ exports.reload = function() { exports.newDefaultAlarm = function () { const settings = exports.getSettings(); - let alarm = { + var alarm = { t: 12 * 3600000, // Default to 12:00 on: true, rp: settings.defaultRepeat, as: settings.defaultAutoSnooze, - dow: 0b1111111, + dow: settings.defaultRepeat ? 0b1111111 : 0b0000000, last: 0, vibrate: settings.defaultAlarmPattern, }; @@ -79,7 +79,7 @@ exports.newDefaultAlarm = function () { exports.newDefaultTimer = function () { const settings = exports.getSettings(); - let timer = { + var timer = { timer: 5 * 60 * 1000, // 5 minutes on: true, rp: false, @@ -113,20 +113,3 @@ exports.getSettings = function () { exports.setSettings = function(settings) { require("Storage").writeJSON("sched.settings.json", settings); }; - -// time in ms -> { hrs, mins } -exports.decodeTime = function(t) { - t = Math.ceil(t / 60000); // sanitise to full minutes - let hrs = 0 | (t / 60); - return { hrs: hrs, mins: t - hrs * 60 }; -} - -// time in { hrs, mins } -> ms -exports.encodeTime = function(o) { - return o.hrs * 3600000 + o.mins * 60000; -} - -exports.formatTime = function(t) { - let o = exports.decodeTime(t); - return o.hrs + ":" + ("0" + o.mins).substr(-2); -} diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json index 089fffe31..c3fc7ca8a 100644 --- a/apps/sched/metadata.json +++ b/apps/sched/metadata.json @@ -1,7 +1,7 @@ { "id": "sched", "name": "Scheduler", - "version": "0.08", + "version": "0.10", "description": "Scheduling library for alarms and timers", "icon": "app.png", "type": "scheduler", diff --git a/apps/sched/sched.js b/apps/sched/sched.js index 7c97600d9..f4d1bc9ad 100644 --- a/apps/sched/sched.js +++ b/apps/sched/sched.js @@ -9,7 +9,7 @@ function showAlarm(alarm) { const settings = require("sched").getSettings(); let msg = ""; - msg += alarm.timer ? require("sched").formatTime(alarm.timer) : require("sched").formatTime(alarm.t); + msg += require("time_utils").formatTime(alarm.timer ? alarm.timer : alarm.t); if (alarm.msg) { msg += "\n"+alarm.msg; } else { @@ -26,7 +26,7 @@ function showAlarm(alarm) { E.showPrompt(msg,{ title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!", - buttons : {/*LANG*/"Snooze":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins + buttons : {/*LANG*/"Snooze":true,/*LANG*/"Stop":false} // default is sleep so it'll come back in 10 mins }).then(function(sleep) { buzzCount = 0; if (sleep) { diff --git a/apps/sleeplog/ChangeLog b/apps/sleeplog/ChangeLog index d12c565ac..8a3da6362 100644 --- a/apps/sleeplog/ChangeLog +++ b/apps/sleeplog/ChangeLog @@ -2,3 +2,5 @@ 0.02: Fix crash on start #1423 0.03: Added power saving mode, move all read/write log actions into lib/module 0.04: Fix #1445, display loading info, add icons to display service states +0.05: Fix LOW_MEMORY,MEMORY error on to big log size +0.06: Reduced log size further to 750 entries diff --git a/apps/sleeplog/README.md b/apps/sleeplog/README.md index 4b10438ef..ebbcdde54 100644 --- a/apps/sleeplog/README.md +++ b/apps/sleeplog/README.md @@ -21,7 +21,8 @@ also provides a power saving mode using the built in movement calculation. The i * __Logging__ To minimize the log size only a changed state is logged. The logged timestamp is matching the beginning of its measurement period. When not on power saving mode a movement is detected nearly instantaneous and the detection of a no movement period is delayed by the minimal no movement duration. To match the beginning of the measurement period a cached timestamp (_sleeplog.firstnomodate_) is logged. - On power saving mode the measurement period is fixed to 10 minutes and all logged timestamps are also set back 10 minutes. + On power saving mode the measurement period is fixed to 10 minutes and all logged timestamps are also set back 10 minutes. + To prevent a LOW_MEMORY,MEMORY error the log size is limited to 750 entries, older entries will be overwritten. --- ### Control diff --git a/apps/sleeplog/lib.js b/apps/sleeplog/lib.js index 7b35d8a85..752139e27 100644 --- a/apps/sleeplog/lib.js +++ b/apps/sleeplog/lib.js @@ -98,6 +98,9 @@ exports = { input = log; } + // check and if neccessary reduce logsize to prevent low mem + if (input.length > 750) input = input.slice(-750); + // simple check for log plausibility if (input[0].length > 1 && input[0][0] * 1 > 9E11) { // write log to storage diff --git a/apps/sleeplog/metadata.json b/apps/sleeplog/metadata.json index 8cf6979d6..c4dbe8631 100644 --- a/apps/sleeplog/metadata.json +++ b/apps/sleeplog/metadata.json @@ -2,7 +2,7 @@ "id":"sleeplog", "name":"Sleep Log", "shortName": "SleepLog", - "version": "0.04", + "version": "0.06", "description": "Log and view your sleeping habits. This app derived from SleepPhaseAlarm and uses also the principe of Estimation of Stationary Sleep-segments (ESS). It also provides a power saving mode using the built in movement calculation.", "icon": "app.png", "type": "app", diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index ec3fe3a23..208058472 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -6,3 +6,4 @@ 0.06: Add logging use Layout library and display ETA 0.07: Add check for day of week +0.08: Update to new time_utils module diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index 8ccd43eb2..febc8a259 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -46,8 +46,8 @@ function calc_ess(acc_magn) { var nextAlarm; active.forEach(alarm => { const now = new Date(); - const t = require("sched").decodeTime(alarm.t); - var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), t.hrs, t.mins); + const time = require("time_utils").decodeTime(alarm.t); + var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), time.h, time.m); if (dateAlarm < now) { // dateAlarm in the past, add 24h dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000)); } diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index d74590704..c74a617ab 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,7 +2,7 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.07", + "version": "0.08", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", diff --git a/apps/tabanchi/ChangeLog b/apps/tabanchi/ChangeLog index 23b44dd5d..3889ade8e 100644 --- a/apps/tabanchi/ChangeLog +++ b/apps/tabanchi/ChangeLog @@ -1 +1,2 @@ -0.0.1: Initial implementation +0.01: Initial implementation +0.02: Fix app icon diff --git a/apps/tabanchi/app-icon.js b/apps/tabanchi/app-icon.js index 95796eb16..126bbb1a9 100644 --- a/apps/tabanchi/app-icon.js +++ b/apps/tabanchi/app-icon.js @@ -1 +1 @@ -atob("MDDBAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gAAAAD//gAAAAD//gAAAAf//8AAAAf//8AAAAf//8AAAD/////gAD/////gAD/////gAf/////8Af/////8Af/////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8D//////8f//////gf//////gf//////gD/////gAD/////gAD/////gAAf///8AAAf///8AAAf///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") +atob("MDCI/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v///wAAAP///////////////////wAAAP////////////7+/v7+/v7+/v7+/v7+/v///wAAAP///////////////////wAAAP////////////7+/v7+/v7+/v7+/v7+/v///wAAAP///////////////////wAAAP////////////7+/v7+/v7+/v7+/v///wAAAP///////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v///wAAAP///////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v///wAAAP///////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///////////////////wAAAP///////////////wAAAP////7+/v7+/v///wAAAP///////////////////wAAAP///////////////wAAAP////7+/v7+/v///wAAAP///////////////////wAAAP///////////////wAAAP////7+/v7+/v///wAAAP///wAAAP///////////wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///wAAAP///////////wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///wAAAP///////////wAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///////////////////////wAAAP///////////wAAAP////7+/v7+/v///wAAAP///////////////////////wAAAP///////////wAAAP////7+/v7+/v///wAAAP///////////////////////wAAAP///////////wAAAP////7+/v7+/v///wAAAP///////////////////////wAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///////////////////////wAAAAAAAAAAAAAAAAAAAP////7+/v7+/v///wAAAP///////////////////////wAAAAAAAAAAAAAAAAAAAP////7+/v///wAAAP///////////////////////////////////wAAAP////////7+/v7+/v///wAAAP///////////////////////////////////wAAAP////////7+/v7+/v///wAAAP///////////////////////////////////wAAAP////////7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////7+/v7+/v7+/v7+/v7+/v7+/v////////////////////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v////////////////////////////////////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/g==") diff --git a/apps/tabanchi/metadata.json b/apps/tabanchi/metadata.json index 23de01869..335dd0326 100644 --- a/apps/tabanchi/metadata.json +++ b/apps/tabanchi/metadata.json @@ -2,12 +2,12 @@ "id": "tabanchi", "name": "Tabanchi", "shortName": "Tabanchi", - "version": "0.0.1", + "version": "0.02", "type": "app", "description": "Tamagotchi WatchApp", "icon": "app.png", "allow_emulator": true, - "tags": "watch, pet", + "tags": "clock, watch, virtual pet", "supports": [ "BANGLEJS2" ], diff --git a/apps/tinydraw/ChangeLog b/apps/tinydraw/ChangeLog index 2ee16e6b5..4bae1b9f8 100644 --- a/apps/tinydraw/ChangeLog +++ b/apps/tinydraw/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial release 0.02: Don't start drawing with white colour on white canvas +0.03: Fix segmented line glitch when drawing, optimize screen lock detection diff --git a/apps/tinydraw/README.md b/apps/tinydraw/README.md index a4acd9a72..f250af920 100644 --- a/apps/tinydraw/README.md +++ b/apps/tinydraw/README.md @@ -1,14 +1,13 @@ TinyDraw ======== -This is a simple drawing application to make sketches -using different brushes and colors for your BangleJS2 watch! +This is a simple drawing application to make sketches using different +brushes and colors for your BangleJS2 watch! * Brush types: dot, brush, circle, square -It is my first BangleJS application, I plan -to continue improving this app over time, but -if you want to contribute or provide feedback +It is my first BangleJS application, I plan to continue improving +this app over time, but if you want to contribute or provide feedback don't hesitate to contact me! --pancake diff --git a/apps/tinydraw/app.js b/apps/tinydraw/app.js index b0b3ef15b..52460bc79 100644 --- a/apps/tinydraw/app.js +++ b/apps/tinydraw/app.js @@ -1,10 +1,10 @@ (function () { - var pen = 'circle'; - var discard = null; - var kule = [0, 255, 255]; // R, G, B - var oldLock = false; + let pen = 'circle'; + let discard = null; + const kule = [0, 255, 255]; // R, G, B + let oldLock = false; - setInterval(() => { +Bangle.on("lock", function() { if (Bangle.isLocked()) { if (oldLock) { return; @@ -19,8 +19,7 @@ oldLock = false; drawUtil(); } - }, 1000); - +}); function nextColor () { kule[0] = Math.random(); kule[1] = Math.random(); @@ -35,10 +34,33 @@ case 'square': pen = 'circle'; break; default: pen = 'pixel'; break; } - console.log('set time'); drawUtil(); - discard = setTimeout(function () { console.log('timeout'); discard = null; }, 500); + discard = setTimeout(function () { oldX = -1; oldY = -1; console.log('timeout'); discard = null; }, 500); + } + + var oldX = -1; + var oldY = -1; + + function drawBrushIcon () { + const w = g.getWidth(); + switch (pen) { + case 'circle': + g.fillCircle(w - 10, 10, 5); + break; + case 'square': + g.fillRect(w - 5, 5, w - 15, 15); + break; + case 'pixel': + g.setPixel(10, 10); + g.fillCircle(w - 10, 10, 2); + break; + case 'crayon': + g.drawLine(w - 10, 5, w - 10, 15); + g.drawLine(w - 14, 6, w - 10, 12); + g.drawLine(w - 6, 6, w - 10, 12); + break; + } } function drawUtil () { @@ -58,35 +80,32 @@ g.setColor('#fff'); g.fillCircle(g.getWidth() - 10, 10, 8); g.setColor('#000'); - - var w = g.getWidth(); - switch (pen) { - case 'circle': - g.fillCircle(w - 10, 10, 5); - break; - case 'square': - g.fillRect(w - 5, 5, w - 15, 15); - break; - case 'pixel': - g.setPixel(10, 10); - g.fillCircle(w - 10, 10, 2); - break; - case 'crayon': - var tap = { x: 10, y: 15, dy: -5, dx: 5 }; - g.drawLine(w - tap.x, tap.y, w - tap.x + tap.dx, tap.y + tap.dy); - g.drawLine(w - tap.x + 1, tap.y + 2, w - tap.x + tap.dx, tap.y + tap.dy - 2); - g.drawLine(w - tap.x + 2, tap.y + 2, w - tap.x + tap.dx, tap.y + tap.dy + 2); - break; - } + drawBrushIcon(); } - var tapTimer = null; + + let tapTimer = null; + let dragTimer = null; Bangle.on('drag', function (tap) { + let from = { x: tap.x, y: tap.y }; + const to = { x: tap.x + tap.dx, y: tap.y + tap.dy }; + if (oldX != -1) { + from = { x: oldX, y: oldY }; + } if (tap.b === 0) { if (tapTimer !== null) { clearTimeout(tapTimer); tapTimer = null; } } + if (dragTimer != null) { + clearTimeout(dragTimer); + dragTimer = null; + } + dragTimer = setTimeout(function () { + oldX = -1; + oldY = -1; + }, 100); + // tap and hold the clear button if (tap.x < 32 && tap.y < 32) { if (tap.b === 1) { @@ -110,6 +129,8 @@ tapTimer = setTimeout(function () { g.clear(); drawUtil(); + oldX = -1; oldY = -1; + tapTimer = null; }, 800); } @@ -127,28 +148,34 @@ drawUtil(); return; } - + oldX = to.x; + oldY = to.y; g.setColor(kule[0], kule[1], kule[2]); switch (pen) { case 'pixel': - g.setPixel(tap.x, tap.y); - g.drawLine(tap.x, tap.y, tap.x + tap.dx, tap.y + tap.dy); + g.drawLine(from.x, from.y, to.x, to.y); break; case 'crayon': - g.drawLine(tap.x, tap.y, tap.x + tap.dx, tap.y + tap.dy); - g.drawLine(tap.x + 1, tap.y + 2, tap.x + tap.dx, tap.y + tap.dy - 2); - g.drawLine(tap.x + 2, tap.y + 2, tap.x + tap.dx, tap.y + tap.dy + 2); + g.drawLine(from.x, from.y, to.x, to.y); + g.drawLine(from.x + 1, from.y + 2, to.x, to.y - 2); + g.drawLine(from.x + 2, from.y + 2, to.x, to.y + 2); break; case 'circle': - var XS = tap.dx / 10; - var YS = tap.dy / 10; - for (i = 0; i < 10; i++) { - g.fillCircle(tap.x + (i * XS), tap.y + (i * YS), 4, 4); + var XS = (to.x - from.x) / 32; + var YS = (to.y - from.y) / 32; + for (i = 0; i < 32; i++) { + g.fillCircle(from.x + (i * XS), from.y + (i * YS), 4, 4); } break; case 'square': - g.fillRect(tap.x - 10, tap.y - 10, tap.x + 10, tap.y + 10); + var XS = (to.x - from.x) / 32; + var YS = (to.y - from.y) / 32; + for (i = 0; i < 32; i++) { + const posX = from.x + (i * XS); + const posY = from.y + (i * YS); + g.fillRect(posX - 10, posY - 10, posX + 10, posY + 10); + } break; } drawUtil(); @@ -157,3 +184,4 @@ g.clear(); drawUtil(); })(); + diff --git a/apps/tinydraw/metadata.json b/apps/tinydraw/metadata.json index 357fcc1d0..35d994ec3 100644 --- a/apps/tinydraw/metadata.json +++ b/apps/tinydraw/metadata.json @@ -1,7 +1,7 @@ { "id": "tinydraw", "name": "TinyDraw", "shortName":"TinyDraw", - "version":"0.02", + "version":"0.03", "type": "app", "description": "Draw stuff in your wrist", "icon": "app.png", diff --git a/apps/widbt_notify/ChangeLog b/apps/widbt_notify/ChangeLog index b5a50210e..3708089c1 100644 --- a/apps/widbt_notify/ChangeLog +++ b/apps/widbt_notify/ChangeLog @@ -8,3 +8,4 @@ 0.09: Vibrate on connection loss 0.10: Bug fix 0.11: Avoid too many notifications. Change disconnected colour to red. +0.12: Prevent repeated execution of `draw()` from the current app. diff --git a/apps/widbt_notify/metadata.json b/apps/widbt_notify/metadata.json index 0b795c2c8..0a144ade1 100644 --- a/apps/widbt_notify/metadata.json +++ b/apps/widbt_notify/metadata.json @@ -1,7 +1,7 @@ { "id": "widbt_notify", "name": "Bluetooth Widget with Notification", - "version": "0.11", + "version": "0.12", "description": "Show the current Bluetooth connection status in the top right of the clock and vibrate when disconnected.", "icon": "widget.png", "type": "widget", diff --git a/apps/widbt_notify/widget.js b/apps/widbt_notify/widget.js index 47765f3d0..fd088c670 100644 --- a/apps/widbt_notify/widget.js +++ b/apps/widbt_notify/widget.js @@ -28,7 +28,7 @@ WIDGETS.bluetooth_notify = { disconnect: function() { if(WIDGETS.bluetooth_notify.warningEnabled == 1){ E.showMessage(/*LANG*/'Connection\nlost.', 'Bluetooth'); - setInterval(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. + setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'. WIDGETS.bluetooth_notify.warningEnabled = 0; setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds. diff --git a/apps/widbthide/metadata.json b/apps/widbthide/metadata.json new file mode 100644 index 000000000..59b13adb4 --- /dev/null +++ b/apps/widbthide/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widbthide", + "name": "Bluetooth Widget (hides when no connection)", + "version": "0.01", + "description": "Shows Bluetooth icon (when connected) in the top right of the clock", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbthide.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widbthide/widget.js b/apps/widbthide/widget.js new file mode 100644 index 000000000..620811b36 --- /dev/null +++ b/apps/widbthide/widget.js @@ -0,0 +1,13 @@ +WIDGETS["bluetooth"]={area:"tr",draw:function() { + if (WIDGETS.bluetooth.width==0) + return; + g.reset(); + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS.bluetooth.width = NRF.getSecurityStatus().connected?15:0; + Bangle.drawWidgets(); +},width:NRF.getSecurityStatus().connected?15:0 +}; +NRF.on('connect',WIDGETS.bluetooth.changed); +NRF.on('disconnect',WIDGETS.bluetooth.changed); diff --git a/apps/widbthide/widget.png b/apps/widbthide/widget.png new file mode 100644 index 000000000..1a884a62c Binary files /dev/null and b/apps/widbthide/widget.png differ diff --git a/core b/core index 32d01b5b3..147892754 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 32d01b5b3d8e013ca0364671e2352b7b0dd48bb4 +Subproject commit 147892754eaf50c8581ebfb4d8651b9ec24aa44e diff --git a/index.html b/index.html index de7facd5a..b141cffc9 100644 --- a/index.html +++ b/index.html @@ -147,6 +147,10 @@ Always update time when we connect +