diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index 06f94854e..a5458d2da 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -31,3 +31,4 @@ clkinfo.addInteractive that would cause ReferenceError. 0.29: use setItem of clockInfoMenu to change the active item 0.30: Use widget_utils 0.31: Use clock_info module as an app +0.32: Make the border of the clock_info box extend all the way to the right of the screen. diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index 770c053c2..359baebae 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -135,7 +135,7 @@ let clockInfoMenu = clock_info.addInteractive(clockInfoItems, { app: "bwclk", x : 0, y: 135, - w: W, + w: W+1, h: H-135, draw : (itm, info, options) => { var hideClkInfo = info.text == null; diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index 430f466b2..6b8230555 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.31", + "version": "0.32", "description": "A very minimalistic clock.", "readme": "README.md", "icon": "app.png", diff --git a/apps/bwclklite/ChangeLog b/apps/bwclklite/ChangeLog index c728997da..eca453be0 100644 --- a/apps/bwclklite/ChangeLog +++ b/apps/bwclklite/ChangeLog @@ -34,3 +34,4 @@ clkinfo.addInteractive that would cause ReferenceError. 0.32: Diverge from BW Clock. Change out the custom font for a standard bitmap one to speed up loading times. Remove invertion of theme as this doesn'twork very well with fastloading. Do an quick inital fillRect on theclock info area. +0.33: Make the border of the clock_info box extend all the way to the right of the screen. diff --git a/apps/bwclklite/app.js b/apps/bwclklite/app.js index 1008eae9c..697776ea0 100644 --- a/apps/bwclklite/app.js +++ b/apps/bwclklite/app.js @@ -95,7 +95,7 @@ let clockInfoMenu = clock_info.addInteractive(clockInfoItems, { app: "bwclklite", x : 0, y: 135, - w: W, + w: W+1, h: H-135, draw : (itm, info, options) => { let hideClkInfo = info.text == null; diff --git a/apps/bwclklite/metadata.json b/apps/bwclklite/metadata.json index bab852623..8932274d4 100644 --- a/apps/bwclklite/metadata.json +++ b/apps/bwclklite/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclklite", "name": "BW Clock Lite", - "version": "0.32", + "version": "0.33", "description": "A very minimalistic clock. This version of BW Clock is quicker at the cost of the custom font.", "readme": "README.md", "icon": "app.png", diff --git a/apps/clkinfostopw/clkinfo.ts b/apps/clkinfostopw/clkinfo.ts index 93eebe4af..5a2fd7bd0 100644 --- a/apps/clkinfostopw/clkinfo.ts +++ b/apps/clkinfostopw/clkinfo.ts @@ -1,4 +1,4 @@ -((): ClockInfo.Menu => { +(() => { let durationOnPause = "---"; let redrawInterval: number | undefined; let startTime: number | undefined; @@ -80,4 +80,4 @@ } ] }; -}) +}) satisfies ClockInfoFunc diff --git a/apps/clkinfostopw/settings.ts b/apps/clkinfostopw/settings.ts new file mode 100644 index 000000000..a9a35f245 --- /dev/null +++ b/apps/clkinfostopw/settings.ts @@ -0,0 +1,34 @@ +const enum StopWatchFormat { + HMS, + Colon, +} +type StopWatchSettings = { + format: StopWatchFormat, +}; + +(back => { + const SETTINGS_FILE = "clkinfostopw.setting.json"; + + const storage = require("Storage"); + const settings: StopWatchSettings = storage.readJSON(SETTINGS_FILE, true) || {}; + settings.format ??= StopWatchFormat.HMS; + + const save = () => { + storage.writeJSON(SETTINGS_FILE, settings) + }; + + E.showMenu({ + "": { "title": "stopwatch" }, + "< Back": back, + "Format": { + value: settings.format, + min: StopWatchFormat.HMS, + max: StopWatchFormat.Colon, + format: v => v === StopWatchFormat.HMS ? "12m34s" : "12:34", + onchange: v => { + settings.format = v; + save(); + }, + }, + }); +}) satisfies SettingsFunc diff --git a/apps/drained/ChangeLog b/apps/drained/ChangeLog new file mode 100644 index 000000000..1a3bc1757 --- /dev/null +++ b/apps/drained/ChangeLog @@ -0,0 +1 @@ +0.01: New app! diff --git a/apps/drained/README.md b/apps/drained/README.md new file mode 100644 index 000000000..7bef74d81 --- /dev/null +++ b/apps/drained/README.md @@ -0,0 +1,33 @@ +# Drained + +With this app installed, your Bangle will automatically switch into low power mode when the battery reaches 5% battery (or a preconfigured percentage), displaying a simple clock. When the battery is then charged above 20% (also configurable), normal operation is restored. + +Low power mode can also be exited manually, by tapping the primary watch button (an initial tap may be required to unlock the watch). + +# Features + +## Persistence +- [x] Restore normal operation with sufficient charge +- [x] Reactivate on watch startup + +## Time +- [x] Show simple date/time +- [ ] Disable alarms - with a setting? +- [ ] Smarter/backoff interval for checking battery percentage + +## No backlight (#2502) +- [x] LCD brightness +- [ ] LCD timeout? + +## Peripherals +- [x] Disable auto heart rate measurement in health app (#2502) +- [x] Overwrite setGPSPower() function (#2502) +- [x] Turn off already-running GPS / HRM + +## Features +- [x] Wake on twist -> off (#2502) +- [x] Emit `"drained"` event + +# Creator + +- [bobrippling](https://github.com/bobrippling/) diff --git a/apps/drained/app.js b/apps/drained/app.js new file mode 100644 index 000000000..f9ae04605 --- /dev/null +++ b/apps/drained/app.js @@ -0,0 +1,102 @@ +var app = "drained"; +if (typeof drainedInterval !== "undefined") + drainedInterval = clearInterval(drainedInterval); +Bangle.setLCDBrightness(0); +var powerNoop = function () { return false; }; +var forceOff = function (name) { + var _a; + if ((_a = Bangle._PWR) === null || _a === void 0 ? void 0 : _a[name]) + Bangle._PWR[name] = []; + Bangle["set".concat(name, "Power")](0, app); + Bangle["set".concat(name, "Power")] = powerNoop; +}; +forceOff("GPS"); +forceOff("HRM"); +try { + NRF.disconnect(); + NRF.sleep(); +} +catch (e) { + console.log("couldn't disable ble: ".concat(e)); +} +Bangle.removeAllListeners(); +clearWatch(); +Bangle.setOptions({ + wakeOnFaceUp: 0, + wakeOnTouch: 0, + wakeOnTwist: 0, +}); +var nextDraw; +var draw = function () { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2 - 48; + var date = new Date(); + var timeStr = require("locale").time(date, 1); + var dateStr = require("locale").date(date, 0).toUpperCase() + + "\n" + + require("locale").dow(date, 0).toUpperCase(); + g.reset() + .clearRect(Bangle.appRect) + .setFont("Vector", 55) + .setFontAlign(0, 0) + .drawString(timeStr, x, y) + .setFont("Vector", 24) + .drawString(dateStr, x, y + 56) + .drawString("".concat(E.getBattery(), "%"), x, y + 104); + if (nextDraw) + clearTimeout(nextDraw); + nextDraw = setTimeout(function () { + nextDraw = undefined; + draw(); + }, 60000 - (date.getTime() % 60000)); +}; +var reload = function () { + Bangle.setUI({ + mode: "custom", + remove: function () { + if (nextDraw) + clearTimeout(nextDraw); + nextDraw = undefined; + }, + btn: function () { + E.showPrompt("Restore watch to full power?").then(function (v) { + if (v) { + drainedRestore(); + } + else { + reload(); + } + }); + } + }); + Bangle.CLOCK = 1; + g.clear(); + draw(); +}; +reload(); +Bangle.emit("drained", E.getBattery()); +var _a = require("Storage").readJSON("".concat(app, ".setting.json"), true) || {}, _b = _a.disableBoot, disableBoot = _b === void 0 ? false : _b, _c = _a.restore, restore = _c === void 0 ? 20 : _c; +function drainedRestore() { + if (disableBoot) { + try { + eval(require('Storage').read('bootupdate.js')); + } + catch (e) { + console.log("error restoring bootupdate:" + e); + } + } + load(); +} +if (disableBoot) { + var checkCharge_1 = function () { + if (E.getBattery() < restore) + return; + drainedRestore(); + }; + if (Bangle.isCharging()) + checkCharge_1(); + Bangle.on("charging", function (charging) { + if (charging) + checkCharge_1(); + }); +} diff --git a/apps/drained/app.ts b/apps/drained/app.ts new file mode 100644 index 000000000..9fc2665ee --- /dev/null +++ b/apps/drained/app.ts @@ -0,0 +1,127 @@ +const app = "drained"; + +// from boot.js +declare var drainedInterval: number | undefined; +if(typeof drainedInterval !== "undefined") + drainedInterval = clearInterval(drainedInterval) as undefined; + +// backlight +Bangle.setLCDBrightness(0); + +// peripherals +const powerNoop = () => false; + +const forceOff = (name: "GPS" | "HRM" | "Compass" /*| "Barom"*/) => { + if ((Bangle as any)._PWR?.[name]) + (Bangle as any)._PWR[name] = []; + + // if(name === "Barom"){ setBarometerPower(...) } + // ^^^^ + Bangle[`set${name}Power`](0, app); + Bangle[`set${name}Power`] = powerNoop; +}; +forceOff("GPS"); +forceOff("HRM"); +try{ + NRF.disconnect(); + NRF.sleep(); +}catch(e){ + console.log(`couldn't disable ble: ${e}`); +} + +// events +Bangle.removeAllListeners(); +clearWatch(); + +// UI +Bangle.setOptions({ + wakeOnFaceUp: 0, + wakeOnTouch: 0, + wakeOnTwist: 0, +}); + +// clock +let nextDraw: number | undefined; +const draw = () => { + const x = g.getWidth() / 2; + const y = g.getHeight() / 2 - 48; + + const date = new Date(); + + const timeStr = require("locale").time(date, 1); + const dateStr = require("locale").date(date, 0).toUpperCase() + + "\n" + + require("locale").dow(date, 0).toUpperCase(); + + g.reset() + .clearRect(Bangle.appRect) + .setFont("Vector", 55) + .setFontAlign(0, 0) + .drawString(timeStr, x, y) + .setFont("Vector", 24) + .drawString(dateStr, x, y + 56) + .drawString(`${E.getBattery()}%`, x, y + 104); + + if(nextDraw) clearTimeout(nextDraw); + nextDraw = setTimeout(() => { + nextDraw = undefined; + draw(); + }, 60000 - (date.getTime() % 60000)); +}; + +const reload = () => { + Bangle.setUI({ + mode: "custom", + remove: () => { + if (nextDraw) clearTimeout(nextDraw); + nextDraw = undefined; + }, + btn: () => { + E.showPrompt("Restore watch to full power?").then(v => { + if(v){ + drainedRestore(); + }else{ + reload(); + } + }) + } + }); + Bangle.CLOCK=1; + + g.clear(); + draw(); +}; +reload(); + +// permit other apps to put themselves into low-power mode +Bangle.emit("drained", E.getBattery()); + +// restore normal boot on charge +const { disableBoot = false, restore = 20 }: DrainedSettings + = require("Storage").readJSON(`${app}.setting.json`, true) || {}; + +// re-enable normal boot code when we're above a threshold: +function drainedRestore() { // "public", to allow users to call + if(disableBoot){ + try{ + eval(require('Storage').read('bootupdate.js')); + }catch(e){ + console.log("error restoring bootupdate:" + e); + } + } + load(); // necessary after updating boot.0 +} + +if(disableBoot){ + const checkCharge = () => { + if(E.getBattery() < restore) return; + drainedRestore(); + }; + + if (Bangle.isCharging()) + checkCharge(); + + Bangle.on("charging", charging => { + if(charging) checkCharge(); + }); +} diff --git a/apps/drained/boot.js b/apps/drained/boot.js new file mode 100644 index 000000000..97f405123 --- /dev/null +++ b/apps/drained/boot.js @@ -0,0 +1,13 @@ +{ + var _a = require("Storage").readJSON("drained.setting.json", true) || {}, _b = _a.battery, threshold_1 = _b === void 0 ? 5 : _b, _c = _a.interval, interval = _c === void 0 ? 10 : _c, _d = _a.disableBoot, disableBoot_1 = _d === void 0 ? false : _d; + drainedInterval = setInterval(function () { + if (Bangle.isCharging()) + return; + if (E.getBattery() > threshold_1) + return; + var app = "drained.app.js"; + if (disableBoot_1) + require("Storage").write(".boot0", "if(typeof __FILE__ === \"undefined\" || __FILE__ !== \"".concat(app, "\") setTimeout(load, 100, \"").concat(app, "\");")); + load(app); + }, interval * 60 * 1000); +} diff --git a/apps/drained/boot.ts b/apps/drained/boot.ts new file mode 100644 index 000000000..4dc885c20 --- /dev/null +++ b/apps/drained/boot.ts @@ -0,0 +1,21 @@ +{ +const { battery: threshold = 5, interval = 10, disableBoot = false }: DrainedSettings + = require("Storage").readJSON(`drained.setting.json`, true) || {}; + +drainedInterval = setInterval(() => { + if(Bangle.isCharging()) + return; + if(E.getBattery() > threshold) + return; + + const app = "drained.app.js"; + + if(disableBoot) + require("Storage").write( + ".boot0", + `if(typeof __FILE__ === "undefined" || __FILE__ !== "${app}") setTimeout(load, 100, "${app}");` + ); + + load(app); +}, interval * 60 * 1000); +} diff --git a/apps/drained/icon.png b/apps/drained/icon.png new file mode 100644 index 000000000..33311cf2c Binary files /dev/null and b/apps/drained/icon.png differ diff --git a/apps/drained/metadata.json b/apps/drained/metadata.json new file mode 100644 index 000000000..b665311a4 --- /dev/null +++ b/apps/drained/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "drained", + "name": "Drained", + "version": "0.01", + "description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals", + "readme": "README.md", + "icon": "icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"drained.boot.js","url":"boot.js"}, + {"name":"drained.app.js","url":"app.js"}, + {"name":"drained.settings.js","url":"settings.js"} + ] +} diff --git a/apps/drained/settings.js b/apps/drained/settings.js new file mode 100644 index 000000000..fe4ab9c4b --- /dev/null +++ b/apps/drained/settings.js @@ -0,0 +1,58 @@ +(function (back) { + var _a, _b, _c, _d; + var SETTINGS_FILE = "drained.setting.json"; + var storage = require("Storage"); + var settings = storage.readJSON(SETTINGS_FILE, true) || {}; + (_a = settings.battery) !== null && _a !== void 0 ? _a : (settings.battery = 5); + (_b = settings.restore) !== null && _b !== void 0 ? _b : (settings.restore = 20); + (_c = settings.interval) !== null && _c !== void 0 ? _c : (settings.interval = 10); + (_d = settings.disableBoot) !== null && _d !== void 0 ? _d : (settings.disableBoot = false); + var save = function () { + storage.writeJSON(SETTINGS_FILE, settings); + }; + E.showMenu({ + "": { "title": "Drained" }, + "< Back": back, + "Keep startup code": { + value: settings.disableBoot, + format: function () { return settings.disableBoot ? "No" : "Yes"; }, + onchange: function () { + settings.disableBoot = !settings.disableBoot; + save(); + }, + }, + "Trigger at batt%": { + value: settings.battery, + min: 0, + max: 95, + step: 5, + format: function (v) { return "".concat(v, "%"); }, + onchange: function (v) { + settings.battery = v; + save(); + }, + }, + "Poll interval": { + value: settings.interval, + min: 1, + max: 60 * 2, + step: 5, + format: function (v) { return "".concat(v, " mins"); }, + onchange: function (v) { + settings.interval = v; + save(); + }, + }, + "Restore watch at %": { + value: settings.restore, + min: 0, + max: 95, + step: 5, + format: function (v) { return "".concat(v, "%"); }, + onchange: function (v) { + settings.restore = v; + save(); + }, + }, + }); +}); diff --git a/apps/drained/settings.ts b/apps/drained/settings.ts new file mode 100644 index 000000000..c79e6605c --- /dev/null +++ b/apps/drained/settings.ts @@ -0,0 +1,67 @@ +type DrainedSettings = { + battery?: number, + restore?: number, + interval?: number, + disableBoot?: ShortBoolean, +}; + +(back => { + const SETTINGS_FILE = "drained.setting.json"; + + const storage = require("Storage") + const settings: DrainedSettings = storage.readJSON(SETTINGS_FILE, true) || {}; + settings.battery ??= 5; + settings.restore ??= 20; + settings.interval ??= 10; + settings.disableBoot ??= false; + + const save = () => { + storage.writeJSON(SETTINGS_FILE, settings) + }; + + E.showMenu({ + "": { "title": "Drained" }, + "< Back": back, + "Keep startup code": { + value: settings.disableBoot, + format: () => settings.disableBoot ? "No" : "Yes", + onchange: () => { + settings.disableBoot = !settings.disableBoot; + save(); + }, + }, + "Trigger at batt%": { + value: settings.battery, + min: 0, + max: 95, + step: 5, + format: (v: number) => `${v}%`, + onchange: (v: number) => { + settings.battery = v; + save(); + }, + }, + "Poll interval": { + value: settings.interval, + min: 1, + max: 60 * 2, + step: 5, + format: (v: number) => `${v} mins`, + onchange: (v: number) => { + settings.interval = v; + save(); + }, + }, + "Restore watch at %": { + value: settings.restore, + min: 0, + max: 95, + step: 5, + format: (v: number) => `${v}%`, + onchange: (v: number) => { + settings.restore = v; + save(); + }, + }, + }); +}) satisfies SettingsFunc diff --git a/apps/glbasic/ChangeLog b/apps/glbasic/ChangeLog index d97fd44d5..1f3a1b643 100644 --- a/apps/glbasic/ChangeLog +++ b/apps/glbasic/ChangeLog @@ -1,3 +1,3 @@ 0.20: New App! 0.21: Tell clock widgets to hide. - +0.22: Changed font so 5 and 6 are not similar diff --git a/apps/glbasic/glbasic.app.js b/apps/glbasic/glbasic.app.js index c1f82f74c..4f6c9ffb3 100644 --- a/apps/glbasic/glbasic.app.js +++ b/apps/glbasic/glbasic.app.js @@ -1,13 +1,36 @@ -Graphics.prototype.setFontLECO1976Regular42 = function (scale) { + +Graphics.prototype.setFontLECO1976Regular5fix42 = function(scale) { // Actual height 42 (41 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAA/AAAAAAAAH/AAAAAAAA//AAAAAAAP//AAAAAAB///AAAAAAP///AAAAAB////AAAAAf////AAAAD////4AAAAf////AAAAH////4AAAA////+AAAAA////wAAAAA///+AAAAAA///gAAAAAA//8AAAAAAA//gAAAAAAA/4AAAAAAAA/AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////gD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4B/gH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAH+AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ERkmHyYmJiYmJCYmEQ=="), 60 + (scale << 8) + (1 << 16)); + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('ADMD/gHFv/AAwkHB3QAtngGFj47Fh5KFh//BwkH/4OEgf/BwgGCBwcBAwIOEAwQODAwX/wB7CCos/Awo/BAAPgDgvwJwgGEBwX4LoplFAw0P/yCF/4GFh6YGKgQAhNAZGDAwZ4CB3ibCg4ODZoYO/BwyV/BxIA7YX7C/YRRZCAAZZDB2AAgNAMHO4v4O42PB3P4AIL+EwABBQwQO/BwgABBwgGBB34A0h/wAYMDSogDBSogGBUgoOOd/4O2AAbgEAAIO+AGY7C/AHDIIWAB3wQCBwjiDB34OGf1gOdAGbgDgZKFwF/JQn4g4O3/ABBBwmAB34OLcAgOBd4oO6AGY5CJQoADd4gO5f2wOdf1IOdAEgqBA4v//AOGwAO5AwqGCB34OJAAbRCAwbgDB3QAzO/4OL/ABBg4ODwABBv4O/BwyV/BxIAzHYX4gZKFSogOCSowOxf2gOdf1YOdAGkH/EAgY7DSgMASoSWCCIIO3ADg='))), + 46, + atob("ERkmICYmJiYmJCYmEQ=="), + 60+(scale<<8)+(1<<16) + ); + return this; }; -Graphics.prototype.setFontLECO1976Regular22 = function (scale) { +Graphics.prototype.setFontLECO1976Regular5fix22 = function(scale) { // Actual height 22 (21 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22 + (scale << 8) + (1 << 16)); + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AAs8AYV8AaQjOgP8AYMPAYV/AYMH/4DBn///EA///4ADB/wSB//gAYQlCCIIABCIIAFDYIjBAaYjBLYIDTF64AH+CDCGdLLV/i7C/wfCAZ/4/BPCAaTiBAaaHBABaPIIaxPMcbxbBAapgCAahPhVYLDTUbA7CAZ/wv5PKN6xPzAof+AaTuXdcCbuJ8H4ngDCE4QDOJ+8PgBPBh+AE4IDPAA4'))), + 46, + atob("CQ0UERQUFBQUExQUCQ=="), + 32+(scale<<8)+(1<<16) + ); + return this; }; +Graphics.prototype.setFontLECO1976Regular5fix11 = function(scale) { + // Actual height 11 (10 - 0) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AAMBwEDgEGgECgF8g/4v/w/+B+ARBg//h/+j/8mEYsEw//h//D/+AgEMg0Yhk/DofggHAFwMAh9+j38nv4scw41h/nD/OH+YdC5kxzAODxgsBw47CIIM/wF/gAGBhkAjBKFCAN/mH+FgUZw0zhl+jH8mP4CAJZEBwVmBwdj+HwgPggfAQoIxBFgJoDSwUDJQhZDO4QsB4CVB+cP80fNAiVGgaWDmAA=='))), + 46, + atob("BAYJCAkJCQkJCQkJBA=="), + 15+(scale<<8)+(1<<16) + ); + return this; +}; require("Font7x11Numeric7Seg").add(Graphics); @@ -60,7 +83,7 @@ function drawCal() { const CAL_Y = g.getHeight() - 44; // Bangle.appRect.y+this.DATE_FONT_SIZE()+10+this.TIME_FONT_SIZE()+3; const CAL_AREA_H = 44; // g.getHeight()-CAL_Y+24; //+24: top widgtes only const CELL_W = g.getWidth() / 7; //cell width - const CELL_H = (CAL_AREA_H - DAY_NAME_FONT_SIZE) / 3; //cell heigth + const CELL_H = 1+parseInt((CAL_AREA_H - DAY_NAME_FONT_SIZE) / 3); //cell heigth const DAY_NUM_FONT_SIZE = Math.min(CELL_H + 3, 15); //size down, max 15 const wdStrt = 1; @@ -73,11 +96,14 @@ function drawCal() { const tdyNumClr = 0; // today fg g.setFont("Vector", DAY_NAME_FONT_SIZE + 3); + + + g.setColor(nrgb[1]); g.setFontAlign(-1, -1); // g.clearRect(Bangle.appRect.x, CAL_Y, Bangle.appRect.x2, CAL_Y+CAL_AREA_H); - //draw grid & Headline + // == draw grid & Headline == const dNames = ABR_DAY.map((a) => a.length <= 2 ? a : a.substr(0, 2)); //force shrt 2 for (var dNo = 0; dNo < dNames.length; dNo++) { const dIdx = wdStrt >= 0 ? ((wdStrt + dNo) % 7) : ((dNo + d.getDay() + 4) % 7); @@ -103,9 +129,10 @@ function drawCal() { // horizontal lines // for(i=0; i<3; i++){ const y=nextY+i*CELL_H; g.drawLine(Bangle.appRect.x, y, Bangle.appRect.x2, y); } - g.setFont("Vector", DAY_NUM_FONT_SIZE); + // g.setFont("Vector", DAY_NUM_FONT_SIZE); - g.setFont("7x11Numeric7Seg", 1); + // g.setFont("7x11Numeric7Seg", 1); + g.setFontLECO1976Regular5fix11(); //write days const tdyDate = d.getDate(); @@ -113,16 +140,38 @@ function drawCal() { var rD = new Date(d.getTime()); rD.setDate(rD.getDate() - days); var rDate = rD.getDate(); + + // == today background rectangle == for (var y = 0; y < 3; y++) { for (var x = 0; x < dNames.length; x++) { if (rDate === tdyDate) { //today g.setColor(nrgb[tdyMrkClr]); //today marker color or fg color // rectangle - g.fillRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1); - g.setColor(nrgb[tdyNumClr]); //today color or fg color + var frm=3; // fame pixels + g.drawRect(x * CELL_W-frm, nextY + CELL_H - 1-frm, x * CELL_W + CELL_W+frm, nextY + CELL_H + CELL_H - 1+frm); + } + rD.setDate(rDate + 1); + rDate = rD.getDate(); + } + } + + // == individual days == + rD = new Date(d.getTime()); + rD.setDate(rD.getDate() - days); + rDate = rD.getDate(); + for (var y = 0; y < 3; y++) { + for (var x = 0; x < dNames.length; x++) { + if (rDate === tdyDate) { //today + g.setColor(nrgb[tdyMrkClr]); //today marker color or fg color + + // rectangle + // g.fillRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1); + // g.setColor(nrgb[tdyNumClr]); //today color or fg color + // g.drawRect(x * CELL_W, nextY + CELL_H - 1, x * CELL_W + CELL_W, nextY + CELL_H + CELL_H - 1); // simulate "bold" + // g.setColor(nrgb[tdyNumClr]); //today color or fg color g.drawString(rDate, 1 + x * CELL_W + ((CELL_W - g.stringWidth(rDate)) / 2) + 2, nextY + ((CELL_H - DAY_NUM_FONT_SIZE + 2) / 2) + (CELL_H * y)); } else if (IS_SUNDAY[rD.getDay()]) { //sundays @@ -153,7 +202,7 @@ function draw() { // g.setFont('Vector', 30); // g.setFont("7x11Numeric7Seg", 5); - g.setFontLECO1976Regular42(); + g.setFontLECO1976Regular5fix42(); g.setFontAlign(0, -1); g.drawString(timeString(h, m), g.getWidth() / 2, 28); g.drawString(dayString(d), g.getWidth() * 3 / 4, 88); @@ -162,7 +211,7 @@ function draw() { g.reset(); // Steps - g.setFontLECO1976Regular22(); + g.setFontLECO1976Regular5fix22(); g.setFontAlign(-1, -1); g.drawString(getSteps(), 8, 88); @@ -200,4 +249,4 @@ Bangle.on('lcdPower', on => { }); -Bangle.drawWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/glbasic/metadata.json b/apps/glbasic/metadata.json index 6d4c562a3..2636deefa 100644 --- a/apps/glbasic/metadata.json +++ b/apps/glbasic/metadata.json @@ -2,7 +2,7 @@ "id": "glbasic", "name": "GLBasic Clock", "shortName": "GLBasic", - "version": "0.21", + "version": "0.22", "description": "A clock with large numbers", "dependencies": {"widpedom":"app"}, "icon": "icon48.png", diff --git a/apps/xxlmessage/ChangeLog b/apps/xxlmessage/ChangeLog new file mode 100644 index 000000000..4c587db52 --- /dev/null +++ b/apps/xxlmessage/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Display icon of the message origin's app \ No newline at end of file diff --git a/apps/xxlmessage/README.md b/apps/xxlmessage/README.md new file mode 100644 index 000000000..40e62fdd2 --- /dev/null +++ b/apps/xxlmessage/README.md @@ -0,0 +1,13 @@ +# XXL Message app +This app displays an incomming message with a very large +font and scrolls the text. For people who can't read the +menu font without glasses, this might be an alternative +to the default messages UI app. + +When arrived, new messages are displayed instantly on the screen. +When the message fully scrolled two times or +if you touch the screen or press the button, +the default clock is loaded. + +Nothing more, nothing less. + diff --git a/apps/xxlmessage/app-icon.js b/apps/xxlmessage/app-icon.js new file mode 100644 index 000000000..ee6642f11 --- /dev/null +++ b/apps/xxlmessage/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcA/4A/AH4A/AH2SpMkAQ1/CI3+CI+fB4v5AQQRFk4JB84REBAXkCIgLFEAYsCIgo+BCIwLEC4oRHF4w7CCJAIRGQaDECLQIRLKB9QUKDFRdKB3GQYwAFBwpKFAAhEFAQokHAH4A/AH4A/ABA")) \ No newline at end of file diff --git a/apps/xxlmessage/app.js b/apps/xxlmessage/app.js new file mode 100644 index 000000000..207221fc6 --- /dev/null +++ b/apps/xxlmessage/app.js @@ -0,0 +1,42 @@ + + +if (require("Storage").read("messagegui")){ // "messagegui" module is installed + require("messages").openGUI(); + console.log("Opened Messages UI"); + Bangle.load("messagegui"); +} + + +function stop() { + g.setBgColor(0, 1, 1); + g.clear(); + g.reset(); + load(); +} +var txt = 'No Messages'; +try{ + console.log("try delete messages"); + var MESSAGES = require("messages").getMessages(); + MESSAGES = []; + txt = 'Deleted all messages'; + console.log("worked"); +}catch(e){} +g.setBgColor('#ffff00'); +g.setColor('#000000'); +g.clear(); + +g.setFont('6x8:3'); +g.setFontAlign(0, 0); +g.setColor('#000000'); +g.drawString(g.wrapString(txt, g.getWidth()).join("\n"), g.getWidth()/2, g.getHeight()/2); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +//E.showMessage(txt,{ +// title:"XXL Messages", +// img:atob("FBQBAfgAf+Af/4P//D+fx/n+f5/v+f//n//5//+f//n////3//5/n+P//D//wf/4B/4AH4A=") // (i) +//}) + +setTimeout(stop, 4000); + diff --git a/apps/xxlmessage/app.png b/apps/xxlmessage/app.png new file mode 100644 index 000000000..88dae4c66 Binary files /dev/null and b/apps/xxlmessage/app.png differ diff --git a/apps/xxlmessage/boot.js b/apps/xxlmessage/boot.js new file mode 100644 index 000000000..62053a0b9 --- /dev/null +++ b/apps/xxlmessage/boot.js @@ -0,0 +1 @@ +Bangle.on("message", (type, msg) => require("xxlmessage.lib.js").listener(type, msg)); \ No newline at end of file diff --git a/apps/xxlmessage/lib-unbuffered.js b/apps/xxlmessage/lib-unbuffered.js new file mode 100644 index 000000000..68fb2a7a2 --- /dev/null +++ b/apps/xxlmessage/lib-unbuffered.js @@ -0,0 +1,133 @@ +// GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) +var xxl = { +// private: + msg: [], + drawTimeout: undefined, + xpos : 0, + loopCount : 0, + txt:'', + wtot:0, + img:undefined, + imgcol:'#ffffff', + + setFont: function(){ + g.setFont('6x8:5x9'); // TODO this is a bottleneck. How to prepare the font once? + }, + +//public: + show: function(theMessage){ + console.log("theMessage is:"); + console.log(theMessage); + xxl.msg = theMessage; + // prepare string and metrics + xxl.txt = xxl.msg.src + ": " + xxl.msg.body; + xxl.setFont(); + xxl.wtot = g.stringMetrics(xxl.txt).width; + xxl.xpos = 2 * g.getWidth(); + + // get icon + xxl.img = require("messageicons").getImage(xxl.msg); + xxl.imgcol = require("messageicons").getColor(xxl.msg, '#ffffff'); + + Bangle.loadWidgets(); + + Bangle.on('touch', function (b, xy) { + xxl.stop(); + }); + setWatch(xxl.stop, BTN1); + Bangle.buzz(500,1); + + xxl.draw(); + }, + +//private: + // schedule a draw for 30 FPS + queueDraw: function() { + if (xxl.drawTimeout) { return; } // clearTimeout(xxl.drawTimeout); } + xxl.drawTimeout = setTimeout(function () { + xxl.drawTimeout = undefined; + xxl.draw(); + }, 33 - (Date.now() % 33)); + }, + + + stop:function() { + console.log("stop"); + if (xxl.drawTimeout) { clearTimeout(xxl.drawTimeout); } + xxl.drawTimeout = undefined; + g.reset(); + g.setBgColor('#ffff00'); + g.clear(); + + // Bangle.setLCDPower(0); // light off + // Bangle.setLocked(true); // disable touch + + setTimeout(load, 100); + }, + + draw: function() { + wh = 24; // widgets height + var gw = g.getWidth(); + var h = (g.getHeight() - wh)/2; // height of drawing area per stripe + + Bangle.setLCDPower(1); // light on + Bangle.setLocked(false); // keep the touch input active + g.setBgColor('#000000'); + g.clear(); + + if (xxl.img) { // 24x24 + g.setColor(xxl.imgcol); + g.drawImage(xxl.img + , gw/2, wh+h // center point + ,{rotate:0,scale:2} + ); + } + + xxl.setFont(); + g.setFontAlign(-1, -1); + + // draw both lines + g.setBgColor('#000000'); + g.setColor('#ffffff'); + g.drawString(xxl.txt, xxl.xpos, wh); + g.drawString(xxl.txt, xxl.xpos - gw - 32, h + wh); + + g.reset(); + // widget redraw + Bangle.drawWidgets(); + + // scroll + xxl.xpos -= 25; + if (xxl.xpos < -xxl.wtot - gw * 2) { + ++xxl.loopCount; + if (xxl.loopCount > 2) { + xxl.stop(); + return; + } + xxl.xpos = 3 * gw; + } + // loop drawing + xxl.queueDraw(); + } +}; + + +// for IDE +// var exports={}; + +exports.listener = function (type, msg) { + // msg = {t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool} + if (!msg) return; + if (type === 'text' && msg.t !== 'remove') { + msg.handled = true; // don't do anything else with the message + xxl.show(msg); + } +}; + +// debug +// Bangle.on("message", (type, msg) => exports.listener(type, msg)); + + + + + diff --git a/apps/xxlmessage/lib.js b/apps/xxlmessage/lib.js new file mode 100644 index 000000000..9aa85462d --- /dev/null +++ b/apps/xxlmessage/lib.js @@ -0,0 +1,194 @@ +// GB({t:"notify",id:1680248072,src:"SMS Messenger",title:"Fabia",body:"Nein"}) +// msg = {"t":"add","id":1680248072,"src":"SMS Messenger","title":"Fabia","body":"Nein","new":true,"handled":true} +var xxl = { +// private: + msg: [], + drawTimeout: undefined, + xpos : 0, + loopCount : 0, + txt:'', + wtot:0, + img:undefined, + imgcol:'#ffffff', + + // gfx buffer + bufimg:undefined, + bufpal4color:undefined, + buffnt:'6x15', // font to use. Built-in: 4x6, 6x8,12x20,6x15,Vector + bufw:0, // width of buffer for all lines + bufh:0, // height of buffer + buflin:0, // number of lines to print + bufscale:0, // scale factor for buffer to screen + +// public: + show: function(theMessage){ + // console.log("theMessage is:"); + // console.log(theMessage); + xxl.msg = theMessage; + + // get icon + try{ + xxl.img = require("messageicons").getImage(xxl.msg); + xxl.imgcol = (require("messageicons").getColor(xxl.msg, '#ffffff')||'#00ffff'); + }catch(e){} + + Bangle.loadWidgets(); + + Bangle.on('touch', function (b, xy) { + xxl.stop(); + }); + setWatch(xxl.stop, BTN1); + Bangle.buzz(500,1); + + + // offscreen gfx buffer + // screen is 176x176 + // font should be scaled 5x9=30x72px + // built in fonts are 4x6, 6x8,12x20,6x15,Vector + xxl.bufpal4color = new Uint16Array([0x0000,0xFFFF,0x7BEF,0xAFE5],0,2); // b,w,grey,greenyellow + g.setFont(xxl.buffnt); + var hfont = g.getFontHeight(); + xxl.bufscale=parseInt((g.getHeight() - 24/*widgets*/)/2) / hfont; + xxl.buflin=2; // number of lines + xxl.bufw=(g.getWidth() * xxl.buflin) / xxl.bufscale; // 6x15 font scaled by 5 on 176 screen width + xxl.bufh=hfont; + + xxl.bufimg = Graphics.createArrayBuffer(xxl.bufw,xxl.bufh,2,{msb:true}); + + // prepare string and metrics + xxl.txt = (xxl.msg.title||(xxl.msg.src||"MSG")) + ": " + (xxl.msg.body||"-x-"); + g.setFont(xxl.buffnt); + xxl.wtot = g.stringMetrics(xxl.txt).width; + xxl.xpos = xxl.bufw; // g.getWidth(); + + xxl.draw(); + }, + +//private: + // schedule a draw for 60 FPS + queueDraw: function() { + if (xxl.drawTimeout) { return; } // clearTimeout(xxl.drawTimeout); } + xxl.drawTimeout = setTimeout(function () { + xxl.drawTimeout = undefined; + xxl.draw(); + }, 16 - (Date.now() % 16)); + }, + + + stop:function() { + // console.log("stop"); + if (xxl.drawTimeout) { clearTimeout(xxl.drawTimeout); } + xxl.drawTimeout = undefined; + g.reset(); + g.setBgColor('#ffff00'); + g.clear(); + + // Bangle.setLCDPower(0); // light off + // Bangle.setLocked(true); // disable touch + + setTimeout(function(){Bangle.showClock();}, 100); + }, + + // this is even slower than the scaled printing :( + // megaPrintBufferd: function(txt, x, y){ + // xxl.bufimg.setFont(xxl.buffnt); + // xxl.bufimg.setFontAlign(-1, -1); + // xxl.bufimg.setColor(1); // index in palette + // xxl.bufimg.clear(); + // xxl.bufimg.drawString(txt, x, 0); + // for(var i = 0; i 2) { + xxl.stop(); + return; + } + xxl.xpos = (3*xxl.bufw)/2; + } + // loop drawing + xxl.queueDraw(); + } +}; + + +// for IDE +// var exports={}; + +exports.listener = function (type, msg) { + // msg = {t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool} + if (!msg) return; + if (type === 'text' && msg.t !== 'remove') { + msg.handled = true; // don't do anything else with the message + xxl.show(msg); + } +}; + +// debug +// var msg = {t:"add",id:12341, src:"SMS",title:undefined,subject:undefined,body:"yes",sender:"phoo",tel:undefined, important:false, new:true}; +// exports.listener('text', msg); + + + + + + + diff --git a/apps/xxlmessage/metadata.json b/apps/xxlmessage/metadata.json new file mode 100644 index 000000000..f8150f0e7 --- /dev/null +++ b/apps/xxlmessage/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "xxlmessage", + "name": "XXL Message", + "version": "0.02", + "shortName": "XXL Msg", + "description": "App to display large notifications from iOS and Gadgetbridge/Android", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : {"messages":"module", "messageicons":"module" }, + "readme": "README.md", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"xxlmessage.app.js","url":"app.js"}, + {"name":"xxlmessage.lib.js","url":"lib.js"}, + {"name":"xxlmessage.boot.js","url":"boot.js"}, + {"name":"xxlmessage.img","url":"app-icon.js","evaluate":true} + ] +} \ No newline at end of file diff --git a/apps/xxlmessage/screenshot.png b/apps/xxlmessage/screenshot.png new file mode 100644 index 000000000..5d9fbcf55 Binary files /dev/null and b/apps/xxlmessage/screenshot.png differ diff --git a/loader.js b/loader.js index 414aa99d1..1fcae8da6 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v16"; +var RECOMMENDED_VERSION = "2v17"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles diff --git a/typescript/types/funcs.d.ts b/typescript/types/funcs.d.ts new file mode 100644 index 000000000..81c4646ea --- /dev/null +++ b/typescript/types/funcs.d.ts @@ -0,0 +1,2 @@ +type SettingsFunc = (back: () => void) => void; +type ClockInfoFunc = () => ClockInfo.Menu; diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts index 944d55326..29118b7c6 100644 --- a/typescript/types/main.d.ts +++ b/typescript/types/main.d.ts @@ -90,6 +90,8 @@ type Widget = { }; declare const WIDGETS: { [key: string]: Widget }; +type ShortBoolean = boolean | 0 | 1; + type AccelData = { x: number; y: number; @@ -153,13 +155,13 @@ type LCDMode = | "120x120" | "80x80" -type BangleOptions = { - wakeOnBTN1: boolean; - wakeOnBTN2: boolean; - wakeOnBTN3: boolean; - wakeOnFaceUp: boolean; - wakeOnTouch: boolean; - wakeOnTwist: boolean; +type BangleOptions = { + wakeOnBTN1: Boolean; + wakeOnBTN2: Boolean; + wakeOnBTN3: Boolean; + wakeOnFaceUp: Boolean; + wakeOnTouch: Boolean; + wakeOnTwist: Boolean; twistThreshold: number; twistMaxY: number; twistTimeout: number; @@ -174,6 +176,12 @@ type BangleOptions = { btnLoadTimeout: number; }; +type SetUIArg = Mode | { + mode: Mode, + back?: () => void, + remove?: () => void, +}; + type NRFFilters = { services?: string[]; name?: string; @@ -296,6 +304,12 @@ type VariableSizeInformation = { more?: VariableSizeInformation; }; +type PipeOptions = { + chunkSize?: number, + end?: boolean, + complete?: () => void, +}; + // CLASSES @@ -794,6 +808,29 @@ declare class NRF { */ static on(event: "security", callback: (status: any) => void): void; + /** + * Called when Bluetooth advertising starts or stops on Espruino + * @param {string} event - The event to listen to. + * @param {(isAdvertising: boolean) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `isAdvertising` Whether we are advertising or not + * @url http://www.espruino.com/Reference#l_NRF_advertising + */ + static on(event: "advertising", callback: (isAdvertising: boolean) => void): void; + + /** + * Called during the bonding process to update on status + * `status` is one of: + * * `"request"` - Bonding has been requested in code via `NRF.startBonding` + * * `"start"` - The bonding procedure has started + * * `"success"` - The bonding procedure has succeeded (`NRF.startBonding`'s promise resolves) + * * `"fail"` - The bonding procedure has failed (`NRF.startBonding`'s promise rejects) + * @param {string} event - The event to listen to. + * @param {(status: any) => void} callback - A function that is executed when the event occurs. Its arguments are: + * * `status` One of `'request'/'start'/'success'/'fail'` + * @url http://www.espruino.com/Reference#l_NRF_bond + */ + static on(event: "bond", callback: (status: any) => void): void; + /** * Called with a single byte value when Espruino is set up as a HID device and the * computer it is connected to sends a HID report back to Espruino. This is usually @@ -985,15 +1022,17 @@ declare class NRF { * `options` is an object, which can contain: * ``` * { - * name: "Hello" // The name of the device - * showName: true/false // include full name, or nothing - * discoverable: true/false // general discoverable, or limited - default is limited - * connectable: true/false // whether device is connectable - default is true - * scannable : true/false // whether device can be scanned for scan response packets - default is true - * interval: 600 // Advertising interval in msec, between 20 and 10000 (default is 375ms) - * manufacturer: 0x0590 // IF sending manufacturer data, this is the manufacturer ID - * manufacturerData: [...] // IF sending manufacturer data, this is an array of data - * phy: "1mbps/2mbps/coded" // (NRF52840 only) use the long-range coded phy for transmission (1mbps default) + * name: "Hello" // The name of the device + * showName: true/false // include full name, or nothing + * discoverable: true/false // general discoverable, or limited - default is limited + * connectable: true/false // whether device is connectable - default is true + * scannable : true/false // whether device can be scanned for scan response packets - default is true + * whenConnected : true/false // keep advertising when connected (nRF52 only) + * // switches to advertising as non-connectable when it is connected + * interval: 600 // Advertising interval in msec, between 20 and 10000 (default is 375ms) + * manufacturer: 0x0590 // IF sending manufacturer data, this is the manufacturer ID + * manufacturerData: [...] // IF sending manufacturer data, this is an array of data + * phy: "1mbps/2mbps/coded" // (NRF52840 only) use the long-range coded phy for transmission (1mbps default) * } * ``` * Setting `connectable` and `scannable` to false gives the lowest power @@ -2274,7 +2313,7 @@ declare class Socket { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_Socket_pipe */ - pipe(destination: any, options?: any): void; + pipe(destination: any, options?: PipeOptions): void /** * This function writes the `data` argument as a string. Data that is passed in @@ -2469,7 +2508,7 @@ declare class httpSRq { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_httpSRq_pipe */ - pipe(destination: any, options?: any): void; + pipe(dest: any, options?: PipeOptions): void } /** @@ -2692,7 +2731,7 @@ declare class httpCRs { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_httpCRs_pipe */ - pipe(destination: any, options?: any): void; + pipe(destination: any, options?: PipeOptions): void } /** @@ -2906,20 +2945,11 @@ declare class Microbit { } -/** - * This is the File object - it allows you to stream data to and from files (As - * opposed to the `require('fs').readFile(..)` style functions that read an entire - * file). - * To create a File object, you must type ```var fd = - * E.openFile('filepath','mode')``` - see [E.openFile](#l_E_openFile) for more - * information. - * **Note:** If you want to remove an SD card after you have started using it, you - * *must* call `E.unmountSD()` or you may cause damage to the card. - * @url http://www.espruino.com/Reference#File - */ -declare class File { +interface FileConstructor { +} +interface File { /** * Close an open file. * @url http://www.espruino.com/Reference#l_File_close @@ -2976,9 +3006,22 @@ declare class File { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_File_pipe */ - pipe(destination: any, options?: any): void; + pipe(destination: any, options?: PipeOptions): void } +/** + * This is the File object - it allows you to stream data to and from files (As + * opposed to the `require('fs').readFile(..)` style functions that read an entire + * file). + * To create a File object, you must type ```var fd = + * E.openFile('filepath','mode')``` - see [E.openFile](#l_E_openFile) for more + * information. + * **Note:** If you want to remove an SD card after you have started using it, you + * *must* call `E.unmountSD()` or you may cause damage to the card. + * @url http://www.espruino.com/Reference#File + */ +declare const File: FileConstructor + /** * Class containing [Puck.js's](http://www.puck-js.com) utility functions. * @url http://www.espruino.com/Reference#Puck @@ -3519,7 +3562,7 @@ declare class Bangle { * @param {string} event - The event to listen to. * @param {(button: number, xy: any) => void} callback - A function that is executed when the event occurs. Its arguments are: * * `button` `1` for left, `2` for right - * * `xy` Object of form `{x,y}` containing touch coordinates (if the device supports full touch). Clipped to 0..175 (LCD pixel coordinates) on firmware 2v13 and later. + * * `xy` Object of form `{x,y,type}` containing touch coordinates (if the device supports full touch). Clipped to 0..175 (LCD pixel coordinates) on firmware 2v13 and later.`type` is only available on Bangle.js 2 and is an integer, either 0 for swift touches or 2 for longer ones. * @url http://www.espruino.com/Reference#l_Bangle_touch */ static on(event: "touch", callback: TouchCallback): void; @@ -3783,7 +3826,7 @@ declare class Bangle { * @param {any} options * @url http://www.espruino.com/Reference#l_Bangle_setOptions */ - static setOptions(options: { [key in keyof BangleOptions]?: BangleOptions[key] }): void; + static setOptions(options: { [key in keyof BangleOptions]?: BangleOptions[key] }): void; /** * Return the current state of options as set by `Bangle.setOptions` @@ -3844,7 +3887,7 @@ declare class Bangle { * @returns {boolean} Is HRM on? * @url http://www.espruino.com/Reference#l_Bangle_setHRMPower */ - static setHRMPower(isOn: boolean, appID: string): boolean; + static setHRMPower(isOn: ShortBoolean, appID: string): boolean; /** * Is the Heart rate monitor powered? @@ -3868,7 +3911,7 @@ declare class Bangle { * @returns {boolean} Is the GPS on? * @url http://www.espruino.com/Reference#l_Bangle_setGPSPower */ - static setGPSPower(isOn: boolean, appID: string): boolean; + static setGPSPower(isOn: ShortBoolean, appID: string): boolean; /** * Is the GPS powered? @@ -3900,7 +3943,7 @@ declare class Bangle { * @returns {boolean} Is the Compass on? * @url http://www.espruino.com/Reference#l_Bangle_setCompassPower */ - static setCompassPower(isOn: boolean, appID: string): boolean; + static setCompassPower(isOn: ShortBoolean, appID: string): boolean; /** * Is the compass powered? @@ -3928,7 +3971,7 @@ declare class Bangle { * @returns {boolean} Is the Barometer on? * @url http://www.espruino.com/Reference#l_Bangle_setBarometerPower */ - static setBarometerPower(isOn: boolean, appID: string): boolean; + static setBarometerPower(isOn: ShortBoolean, appID: string): boolean; /** * Is the Barometer powered? @@ -4294,7 +4337,11 @@ declare class Bangle { * @param {any} callback - A function with one argument which is the direction * @url http://www.espruino.com/Reference#l_Bangle_setUI */ - static setUI(type?: "updown" | "leftright" | "clock" | "clockupdown" | { mode: "custom"; back?: () => void; touch?: TouchCallback; swipe?: SwipeCallback; drag?: DragCallback; btn?: (n: number) => void, remove?: () => void, clock?: boolean }, callback?: (direction?: -1 | 1) => void): void; + static setUI(type?: undefined): void; + static setUI(type: SetUIArg<"updown" | "leftright">, callback: (direction?: -1 | 1) => void): void; + static setUI(type: SetUIArg<"clock">): void; + static setUI(type: SetUIArg<"clockupdown">, callback?: (direction: -1 | 1) => void): void; + static setUI(type: SetUIArg<"custom"> & { touch?: TouchCallback; swipe?: SwipeCallback; drag?: DragCallback; btn?: (n: 1 | 2 | 3) => void; clock?: boolean | 0 | 1 }): void; /** * @url http://www.espruino.com/Reference#l_Bangle_setUI @@ -4316,7 +4363,7 @@ declare class Bangle { */ static appRect: { x: number, y: number, w: number, h: number, x2: number, y2: number }; - static CLOCK: boolean; + static CLOCK: ShortBoolean; static strokes: undefined | { [key: string]: Unistroke }; } @@ -7972,7 +8019,7 @@ declare class E { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_E_pipe */ - static pipe(source: any, destination: any, options?: { chunkSize?: number, end?: boolean, complete?: () => void }): void + static pipe(source: any, destination: any, options?: PipeOptions): void /** * Create an ArrayBuffer from the given string. This is done via a reference, not a @@ -8609,6 +8656,12 @@ declare class E { /** * This class provides a software-defined OneWire master. It is designed to be * similar to Arduino's OneWire library. + * **Note:** OneWire commands are very timing-sensitive, and on nRF52 devices + * (Bluetooth LE Espruino boards) the bluetooth stack can get in the way. Before + * version 2v18 of Espruino OneWire could be unreliable, but as of firmware 2v18 + * Espruino now schedules OneWire accesses with the bluetooth stack to ensure it doesn't interfere. + * OneWire is now reliable but some functions such as `OneWire.search` can now take + * a while to execute (around 1 second). * @url http://www.espruino.com/Reference#OneWire */ declare class OneWire { @@ -8946,10 +8999,10 @@ interface Object { * ``` * For more information see `Object.on` * - * @param {any} event - The name of the event, for instance `'data'`. If not specified *all* listeners are removed. + * @param {any} [event] - [optional] The name of the event, for instance `'data'`. If not specified *all* listeners are removed. * @url http://www.espruino.com/Reference#l_Object_removeAllListeners */ - removeAllListeners(event: any): void; + removeAllListeners(event?: any): void; } /** @@ -9668,6 +9721,19 @@ declare class StorageFile { * @url http://www.espruino.com/Reference#l_StorageFile_erase */ erase(): void; + + /** + * Pipe this file to a stream (an object with a 'write' method) + * + * @param {any} destination - The destination file/stream that will receive content from the source. + * @param {any} [options] + * [optional] An object `{ chunkSize : int=32, end : bool=true, complete : function }` + * chunkSize : The amount of data to pipe from source to destination at a time + * complete : a function to call when the pipe activity is complete + * end : call the 'end' function on the destination when the source is finished + * @url http://www.espruino.com/Reference#l_StorageFile_pipe + */ + pipe(destination: any, options?: PipeOptions): void } interface processConstructor { @@ -10002,7 +10068,7 @@ declare class Serial { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_Serial_pipe */ - pipe(destination: any, options?: any): void; + pipe(destination: any, options?: PipeOptions): void } interface StringConstructor { @@ -11437,10 +11503,11 @@ declare function setWatch(func: ((arg: { state: boolean, time: number, lastTime: * Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. * To avoid accidentally deleting all Watches, if a parameter is supplied but is `undefined` then an Exception will be thrown. * - * @param {any} id - The id returned by a previous call to setWatch. **Only one argument is allowed.** + * @param {any} id - The id returned by a previous call to setWatch. **Only one argument is allowed.** (or pass nothing to clear all watches) * @url http://www.espruino.com/Reference#l__global_clearWatch */ declare function clearWatch(id: number): void; +declare function clearWatch(): void; /** * A variable containing the arguments given to the function: @@ -11991,6 +12058,10 @@ declare module "neopixel" { * white). These are still supported but the array of data supplied must still be a * multiple of 3 bytes long. Just round the size up - it won't cause any problems. * * On some platforms like STM32, pins capable of hardware SPI MOSI are required. + * * On STM32, `neopixel.write` chooses a hardware SPI device to output the signal on + * and uses that. However in order to avoid spikes in the output, if that hardware device is *already + * initialised* it will not be re-initialised. This means that if the SPI device was already in use, + * you may have to use `SPIx.setup({baud:3200000, mosi:the_pin})` to force it to be re-setup on the pin. * * Espruino devices tend to have 3.3v IO, while WS2812/etc run off of 5v. Many * WS2812 will only register a logic '1' at 70% of their input voltage - so if * powering them off 5v you will not be able to send them data reliably. You can @@ -12915,7 +12986,7 @@ declare module "fs" { * end : call the 'end' function on the destination when the source is finished * @url http://www.espruino.com/Reference#l_fs_pipe */ - function pipe(source: any, destination: any, options?: any): void; + function pipe(destination: any, options?: PipeOptions): void } /** @@ -13272,7 +13343,7 @@ declare module "Storage" { /** * The Flash Storage system is journaling. To make the most of the limited write - * cycles of Flash memory, Espruino marks deleted/replaced files as garbage and + * cycles of Flash memory, Espruino marks deleted/replaced files as garbage/trash files and * moves on to a fresh part of flash memory. Espruino only fully erases those files * when it is running low on flash, or when `compact` is called. * `compact` may fail if there isn't enough RAM free on the stack to use as swap @@ -13311,7 +13382,7 @@ declare module "Storage" { * fileBytes // How many bytes of allocated files do we have? * fileCount // How many allocated files do we have? * trashBytes // How many bytes of trash files do we have? - * trashCount // How many trash files do we have? + * trashCount // How many trash files do we have? (can be cleared with .compact) * } * ``` * @returns {any} An object containing info about the current Storage system