diff --git a/apps/ashadyclock/0.bin b/apps/ashadyclock/0.bin new file mode 100644 index 000000000..0be5abd15 Binary files /dev/null and b/apps/ashadyclock/0.bin differ diff --git a/apps/ashadyclock/1.bin b/apps/ashadyclock/1.bin new file mode 100644 index 000000000..54fe59905 Binary files /dev/null and b/apps/ashadyclock/1.bin differ diff --git a/apps/ashadyclock/2.bin b/apps/ashadyclock/2.bin new file mode 100644 index 000000000..a95c74b20 Binary files /dev/null and b/apps/ashadyclock/2.bin differ diff --git a/apps/ashadyclock/3.bin b/apps/ashadyclock/3.bin new file mode 100644 index 000000000..db3821284 Binary files /dev/null and b/apps/ashadyclock/3.bin differ diff --git a/apps/ashadyclock/4.bin b/apps/ashadyclock/4.bin new file mode 100644 index 000000000..b59e41a0d Binary files /dev/null and b/apps/ashadyclock/4.bin differ diff --git a/apps/ashadyclock/5.bin b/apps/ashadyclock/5.bin new file mode 100644 index 000000000..10985534a Binary files /dev/null and b/apps/ashadyclock/5.bin differ diff --git a/apps/ashadyclock/6.bin b/apps/ashadyclock/6.bin new file mode 100644 index 000000000..d45f451b6 Binary files /dev/null and b/apps/ashadyclock/6.bin differ diff --git a/apps/ashadyclock/7.bin b/apps/ashadyclock/7.bin new file mode 100644 index 000000000..f847816ab Binary files /dev/null and b/apps/ashadyclock/7.bin differ diff --git a/apps/ashadyclock/8.bin b/apps/ashadyclock/8.bin new file mode 100644 index 000000000..deb47c3c0 Binary files /dev/null and b/apps/ashadyclock/8.bin differ diff --git a/apps/ashadyclock/9.bin b/apps/ashadyclock/9.bin new file mode 100644 index 000000000..c806c2012 Binary files /dev/null and b/apps/ashadyclock/9.bin differ diff --git a/apps/ashadyclock/app-icon.js b/apps/ashadyclock/app-icon.js new file mode 100644 index 000000000..3e45d1754 --- /dev/null +++ b/apps/ashadyclock/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDADAAAAHA4HHAAAHA4HA4HA4HAAAAA4//////AAH//////6///AAAA///6//9AAV/////////XAAAA//////6AAv//X//////4AAAA//////4AA////X/////4AAAA//////4AA/////6////4AAAA//////4AA///6vX///v4AAAAAA////4AA///4A//6//AAAAAAF///64AAHA4CFX////AAAAAAH//X/oAAAAAAH6///4AAAAAAH////AAAAAAAH////QAAAAAAH////AAAAAAA/////AAAAAAAH////AAAAAAC////4AAAAAAAH////AAAAAAH////4AAAAAAA6v///AAAAAA//X/9QAAAAAAA////VAAAAAA////4AAAAAAAA////4AAAAAH////4AAAAAAAA////4AAAAA/6///AAAAAAAAA////4AAAAA////6AAAAAAAAF////4AAAAH/X/94AAAAAAAAH/X//oAAAAX////AAAAAAAAAH////AAAAA/////AAAAAAAAAH////AEQAB+///4AwEAAAAAAH////AigAH////4EUGEAAAAAX////A0GA///3/AmEUigAAAAjGMYxEw0AxxGOIGg0w0igAAGikUwmGmmgwEUwmmGmk0wwAAmmmmmmmGmmgimmmmmmmGmgAGmmmGgGmmmmAmmmmiE0w00wAE0000wE000wwmmmmiA0000wAE00U0AGmmmmA0000wE0U00wAAAAAAAmmmmmAAAAAAGmmmmAAAAAAAE0000wAAAAAE00U00AAAAAACk0000QAAAAAmmmmmgAAAAAAGmmmmigAAAAA00000AAAAAAA00000UAAAAAmmmmmEAAAAAA000w00AAAAAGmmmmmAAAAAAE00000QAAAAE0U000wAAAAAA00000wAAAAA000mmiAAAAAAmmmmmiAAAAAmimmmkQAAAAAGmmmmmAAAAAE00000QAAAAAA00000wmEwmAmmmmmmEwmEwAE0000w00000wmmmmmmimmmgAGmmmmmmmmmGEw00mmmmmmmgAGmmmmmmmmmGE0w00000000AA0000U000mmmA000000000wwAGmmmmmmmmmEU0GmmmmmmmmAAAAiAEACgAECAAgCEAAiAEAAA")) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js new file mode 100644 index 000000000..8c86ca8d2 --- /dev/null +++ b/apps/ashadyclock/app.js @@ -0,0 +1,107 @@ +var settings = Object.assign({ + // default values + showWidgets: false, + alternativeColor: false, +}, require('Storage').readJSON("ashadyclock.json", true) || {}); + +let drawTimeout; +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +let palBottom; +if (settings.alternativeColor) { + palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#000"), + g.toColor("#000"), + g.toColor("#0FF"), + g.toColor("#0FF"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#00F"), + g.toColor("#000") + ]).buffer))); +} else { + palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#000"), + g.toColor("#000"), + g.toColor("#F00"), + g.toColor("#FF0"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#FF0"), + g.toColor("#000") + ]).buffer))); +} + +let palTop = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#FFF"), + g.toColor("#000"), + g.toColor("#FFF"), + g.toColor("#FFF"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#FFF"), + g.toColor("#000"), + ]).buffer))); + +let xOffset = (g.getWidth() - 176) / 2; +let yOffset = (g.getHeight() - 176) / 2; + +function drawTop(d0, d1) { + if (settings.showWidgets && g.getHeight()<=176) { + drawNumber(d1, 82 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + drawNumber(d0, 13 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + } else { + drawNumber(d1, 80, 0, palTop); + drawNumber(d0, -1, 0, palTop); + } +} + +function drawBottom(d0, d1) { + if (settings.showWidgets && g.getHeight()<=176) { + drawNumber(d1, 82 + xOffset, 92 + yOffset, palBottom, {scale: 0.825}); + drawNumber(d0, 13 + xOffset, 92 + yOffset, palBottom, {scale: 0.825}); + } else { + drawNumber(d1, 80, 75, palBottom); + drawNumber(d0, -1, 75, palBottom); + } +} + +function drawNumber(number, x, y, palette, options) { + let image = + { + width : 98, height : 100, bpp : 3, + transparent: 4, + buffer : require("Storage").read("ashadyclock." + number +".bin") + }; + image.palette = palette; + g.drawImage(image, x, y, options); +} + +function draw() { + let d = new Date(); + g.clearRect(0, settings.showWidgets ? 24 : 0, g.getWidth(),g.getHeight()); + + drawBottom(Math.floor(d.getMinutes()/10), d.getMinutes() % 10); + drawTop(Math.floor(d.getHours()/10), d.getHours() % 10); + + queueDraw(); +} + +g.clear(); +// draw immediately at first +draw(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +if(settings.showWidgets) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} diff --git a/apps/ashadyclock/app.png b/apps/ashadyclock/app.png new file mode 100644 index 000000000..e01fd0746 Binary files /dev/null and b/apps/ashadyclock/app.png differ diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json new file mode 100644 index 000000000..1b92ecabc --- /dev/null +++ b/apps/ashadyclock/metadata.json @@ -0,0 +1,27 @@ +{ "id": "ashadyclock", + "name": "A Shady Clock", + "shortName":"Shady Clk", + "icon": "app.png", + "version":"0.01", + "description": "A nice clock with drop shadow. Hours and minutes. Configure color and widgets in settings. Create any color combination with the existing images by changing only the app color values.", + "type": "clock", + "tags": "clock", + "screenshots": [{"url":"screenshot-1.png"},{"url":"screenshot.png"}], + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"ashadyclock.app.js","url":"app.js"}, + {"name":"ashadyclock.img","url":"app-icon.js","evaluate":true}, + {"name":"ashadyclock.0.bin","url":"0.bin"}, + {"name":"ashadyclock.1.bin","url":"1.bin"}, + {"name":"ashadyclock.2.bin","url":"2.bin"}, + {"name":"ashadyclock.3.bin","url":"3.bin"}, + {"name":"ashadyclock.4.bin","url":"4.bin"}, + {"name":"ashadyclock.5.bin","url":"5.bin"}, + {"name":"ashadyclock.6.bin","url":"6.bin"}, + {"name":"ashadyclock.7.bin","url":"7.bin"}, + {"name":"ashadyclock.8.bin","url":"8.bin"}, + {"name":"ashadyclock.9.bin","url":"9.bin"}, + {"name":"ashadyclock.settings.js","url":"settings.js"} + ], + "data": [{"name":"ashadyclock.json"}] +} diff --git a/apps/ashadyclock/screenshot-1.png b/apps/ashadyclock/screenshot-1.png new file mode 100644 index 000000000..a0c7ea3be Binary files /dev/null and b/apps/ashadyclock/screenshot-1.png differ diff --git a/apps/ashadyclock/screenshot.png b/apps/ashadyclock/screenshot.png new file mode 100644 index 000000000..731055915 Binary files /dev/null and b/apps/ashadyclock/screenshot.png differ diff --git a/apps/ashadyclock/settings.js b/apps/ashadyclock/settings.js new file mode 100644 index 000000000..eef57e519 --- /dev/null +++ b/apps/ashadyclock/settings.js @@ -0,0 +1,32 @@ +(function(back) { + var FILE = "ashadyclock.json"; + // Load settings + var settings = Object.assign({ + showWidgets: false, + alternativeColor: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Shady Clck" }, + "< Back" : () => back(), + 'Show Widgets': { + value: !!settings.showWidgets, // !! converts undefined to false + onchange: v => { + settings.showWidgets = v; + writeSettings(); + } + }, + 'Blue Color': { + value: !!settings.alternativeColor, // !! converts undefined to false + onchange: v => { + settings.alternativeColor = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/delaylock/ChangeLog b/apps/delaylock/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/delaylock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/delaylock/README.md b/apps/delaylock/README.md new file mode 100644 index 000000000..da2ef3cda --- /dev/null +++ b/apps/delaylock/README.md @@ -0,0 +1,23 @@ +# Delayed Locking + +Delay the locking of the touchscreen to 5 seconds after the backlight turns off. Giving you the chance to interact with the watch without having to press the hardware button again. + +## Usage + +Just install and the behavior is tweaked at boot time. + +## Features + +- respects the LCD Timeout and Brightness as configured in the settings app. + +## Requests + +Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions. + +## Creator + +thyttan + +## Acknowledgements + +Inspired by the conversation between Gordon Williams and user156427 linked here: https://forum.espruino.com/conversations/392219/ diff --git a/apps/delaylock/app.png b/apps/delaylock/app.png new file mode 100644 index 000000000..7bdce945d Binary files /dev/null and b/apps/delaylock/app.png differ diff --git a/apps/delaylock/boot.js b/apps/delaylock/boot.js new file mode 100644 index 000000000..87dcbf186 --- /dev/null +++ b/apps/delaylock/boot.js @@ -0,0 +1,21 @@ +{ + let backlightTimeout = Bangle.getOptions().backlightTimeout; + let brightness = require("Storage").readJSON("setting.json", true); + brightness = brightness?brightness.brightness:1; + + Bangle.setOptions({ + backlightTimeout: backlightTimeout, + lockTimeout: backlightTimeout+5000 + }); + + let turnLightsOn = (_,numOrObj)=>{ + if (!Bangle.isBacklightOn()) { + Bangle.setLCDPower(brightness); + if (typeof numOrObj !== "number") E.stopEventPropagation(); // Touches will not be passed on to other listeners, but swipes will. + } + }; + + setWatch(turnLightsOn, BTN1, { repeat: true, edge: 'rising' }); + Bangle.prependListener("swipe", turnLightsOn); + Bangle.prependListener("touch", turnLightsOn); +} diff --git a/apps/delaylock/metadata.json b/apps/delaylock/metadata.json new file mode 100644 index 000000000..dff4d9219 --- /dev/null +++ b/apps/delaylock/metadata.json @@ -0,0 +1,13 @@ +{ "id": "delaylock", + "name": "Delayed Locking", + "version":"0.01", + "description": "Delay the locking of the screen to 5 seconds after the backlight turns off.", + "icon": "app.png", + "tags": "settings, configuration, backlight, touchscreen, screen", + "type": "bootloader", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"delaylock.boot.js","url":"boot.js"} + ] +} diff --git a/apps/ffcniftyapp/ChangeLog b/apps/ffcniftyapp/ChangeLog new file mode 100644 index 000000000..30dcec467 --- /dev/null +++ b/apps/ffcniftyapp/ChangeLog @@ -0,0 +1,3 @@ +0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock + + diff --git a/apps/ffcniftyapp/README.md b/apps/ffcniftyapp/README.md new file mode 100644 index 000000000..d6795840e --- /dev/null +++ b/apps/ffcniftyapp/README.md @@ -0,0 +1,13 @@ +# Nifty-A ++ Clock + +This is the clock: + +![](screenshot_niftyapp.png) + +The week number (ISO8601) can be turned off in settings (default is `On`) +Weather and Steps can be also turned off in settings. + +![](screenshot_settings_niftyapp.png) + +Based on the # Nifty-A Clock by @alessandrococco + diff --git a/apps/ffcniftyapp/app-icon.js b/apps/ffcniftyapp/app-icon.js new file mode 100644 index 000000000..f0a2393b1 --- /dev/null +++ b/apps/ffcniftyapp/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file diff --git a/apps/ffcniftyapp/app.js b/apps/ffcniftyapp/app.js new file mode 100644 index 000000000..840dd72ff --- /dev/null +++ b/apps/ffcniftyapp/app.js @@ -0,0 +1,174 @@ +const w = require("weather"); +//const locale = require("locale"); + +// Weather icons from https://icons8.com/icon/set/weather/color +function getSun() { + return require("heatshrink").decompress(atob("kEggILIgOAAZkDAYPAgeBwPAgIFBBgPhw4TBp/yAYMcnADBnEcAYMwhgDBsEGgE/AYP8AYYLDCYgbDEYYrD8fHIwI7CIYZLDL54AHA==")); +} +function getPartSun() { + return require("heatshrink").decompress(atob("kcjwIVSgOAAgUwAYUGAYVgBoQHBkAIBocIDIX4CIcOAYMYg/wgECgODgE8oFAmEDxEYgYZBgQLBGYNAg/ggcYgANBAIIxBsPAG4MYsAIBoQ3ChAQCgI4BHYUEBgUADIIPBh///4GBv//8Cda")); +} +//function getPartRain() { +// return require("heatshrink").decompress(atob("kEggIHEmADJjEwsEAjkw8EAh0B4EAg35wEAgP+CYMDwv8AYMDBAP2g8HgH+g0DBYMMgPwAYX8gOMEwMG3kAg8OvgSBjg2BgcYGQIcBAY5CBg0Av//HAM///4MYgNBEIMOCoUMDoUAnBwGkEA")); +//} +function getCloud() { + return require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA==")); +} +function getSnow() { + return require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAjADCj+AgOAj/gAYMIuEHwEAjEPAYQVChk4AYQhCAYcYBYQTDnEPgEB+EH///IAQACE4IAB8EICIPghwDB4EeBYNAjgDBg8EAYQYCg4bCgZuFA==")); +} +function getRain() { + return require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA==")); +} +function getStorm() { + return require("heatshrink").decompress(atob("kcjwIROgfwAYMB44ICsEwAYMYgYQCgAICoEHCwMYgFDwEHCYfgEAMA4AIBmAXCgUGFIVAwADBhEQFIQtCGwNggPgjAVBngCBv8Oj+AgfjwYpCGAIABn4kBgOBBAVwjBHBD4IdBgYNBGwUAkCdbA=")); +} +// err icon - https://icons8.com/icons/set/error +function getErr() { + return require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A==")); +} +//function getDummy() { +// return require("heatshrink").decompress(atob("gMBwMAwA")); +//} + + + + +/** +Choose weather icon to display based on condition. +Based on function from the Bangle weather app so it should handle all of the conditions +sent from gadget bridge. +*/ +function chooseIcon(condition) { + condition = condition.toLowerCase(); + if (condition.includes("thunderstorm") || + condition.includes("squalls") || + condition.includes("tornado")) return getStorm; + else if (condition.includes("freezing") || condition.includes("snow") || + condition.includes("sleet")) { + return getSnow; + } + else if (condition.includes("drizzle") || + condition.includes("shower") || + condition.includes("rain")) return getRain; + else if (condition.includes("clear")) return getSun; + else if (condition.includes("clouds")) return getCloud; + else if (condition.includes("few clouds") || + condition.includes("scattered clouds") || + condition.includes("mist") || + condition.includes("smoke") || + condition.includes("haze") || + condition.includes("sand") || + condition.includes("dust") || + condition.includes("fog") || + condition.includes("overcast") || + condition.includes("partly cloudy") || + condition.includes("ash")) { + return getPartSun; + } else return getErr; +} +/*function condenseWeather(condition) { + condition = condition.toLowerCase(); + if (condition.includes("thunderstorm") || + condition.includes("squalls") || + condition.includes("tornado")) return "storm"; + if (condition.includes("freezing") || condition.includes("snow") || + condition.includes("sleet")) { + return "snow"; + } + if (condition.includes("drizzle") || + condition.includes("shower") || + condition.includes("rain")) return "rain"; + if (condition.includes("clear")) return "clear"; + if (condition.includes("clouds")) return "clouds"; + if (condition.includes("few clouds") || + condition.includes("scattered clouds") || + condition.includes("mist") || + condition.includes("smoke") || + condition.includes("haze") || + condition.includes("sand") || + condition.includes("dust") || + condition.includes("fog") || + condition.includes("overcast") || + condition.includes("partly cloudy") || + condition.includes("ash")) { + return "scattered"; + } else { return "N/A"; } + return "N/A"; +} +*/ +// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 +function ISO8601_week_no(date) { + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + return 1 + Math.ceil((firstThursday - tdt) / 604800000); +} + +function format(value) { + return ("0" + value).substr(-2); +} + +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; + + this.viewport = { + width: appRect.w, + height: appRect.h + }; + + this.center = { + x: this.viewport.width / 2, + y: Math.round((this.viewport.height / 2) + appRect.y) + }; + + this.scale = g.getWidth() / this.viewport.width; + this.centerTimeScaleX = this.center.x + 32 * this.scale; + this.centerDatesScaleX = this.center.x + 40 * this.scale; + }, + draw: function (date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; + // const monthName = require("date_utils").month(month, 1); + // const dayName = require("date_utils").dow(date.getDay(), 1); + let steps = Bangle.getHealthStatus("day").steps; + let curr = (w.get() === undefined ? "no data" : w.get()); // Get weather from weather app. + //let cWea =(curr === "no data" ? "no data" : curr.txt); + let cTemp= (curr === "no data" ? 273 : curr.temp); + // const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/); + let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt ); + //let w_icon = chooseIcon(curr.txt); + + g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); + g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); + g.drawString(format(date.getMinutes()), this.centerTimeScaleX, this.center.y + 46 * this.scale); + + g.fillRect(this.center.x + 30 * this.scale, this.center.y - 72 * this.scale, this.center.x + 32 * this.scale, this.center.y + 74 * this.scale); + + g.setFontAlign(-1, 0).setFont("Vector", 16 * this.scale); + g.drawString(format(date.getDate()), this.centerDatesScaleX, this.center.y - 62 * this.scale); //26 + g.drawString("." + format(month) + ".", this.centerDatesScaleX + 20, this.center.y - 62 * this.scale); //44 + g.drawString(date.getFullYear(date), this.centerDatesScaleX, this.center.y - 44 * this.scale); //62 + if (this.showWeekNum) + g.drawString("CW" + format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + -26 * this.scale); //15 + // print(w_icon()); + if (this.showWeather) { + g.drawImage(w_icon(), this.centerDatesScaleX, this.center.y - 8 * this.scale); + // g.drawString(condenseWeather(curr.txt), this.centerDatesScaleX, this.center.y + 24 * this.scale); + g.drawString((cTemp === undefined ? 273 : cTemp ) - 273 + "°C", this.centerDatesScaleX, this.center.y + 44 * this.scale); //48 + + } + if (this.showSteps) + g.drawString(steps, this.centerDatesScaleX, this.center.y + 66 * this.scale); + + }, + settingsFile: "ffcniftyapp.json" +}); +clock.start(); diff --git a/apps/ffcniftyapp/app.png b/apps/ffcniftyapp/app.png new file mode 100644 index 000000000..1cd8a49b7 Binary files /dev/null and b/apps/ffcniftyapp/app.png differ diff --git a/apps/ffcniftyapp/metadata.json b/apps/ffcniftyapp/metadata.json new file mode 100644 index 000000000..bbe8e7e69 --- /dev/null +++ b/apps/ffcniftyapp/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "ffcniftyapp", + "name": "Nifty-A Clock ++", + "version": "0.01", + "description": "A nifty clock with time and date and more", + "dependencies": {"weather":"app"}, + "icon": "app.png", + "screenshots": [{"url":"screenshot_niftyapp.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"ffcniftyapp.app.js","url":"app.js"}, + {"name":"ffcniftyapp.img","url":"app-icon.js","evaluate":true}, + {"name":"ffcniftyapp.settings.js","url":"settings.js"} + ], + "data": [{"name":"ffcniftyapp.json"}] +} diff --git a/apps/ffcniftyapp/screenshot_niftyapp.png b/apps/ffcniftyapp/screenshot_niftyapp.png new file mode 100644 index 000000000..2428523d0 Binary files /dev/null and b/apps/ffcniftyapp/screenshot_niftyapp.png differ diff --git a/apps/ffcniftyapp/screenshot_settings_niftyapp.png b/apps/ffcniftyapp/screenshot_settings_niftyapp.png new file mode 100644 index 000000000..0bc9cc72c Binary files /dev/null and b/apps/ffcniftyapp/screenshot_settings_niftyapp.png differ diff --git a/apps/ffcniftyapp/settings.js b/apps/ffcniftyapp/settings.js new file mode 100644 index 000000000..a1f09e454 --- /dev/null +++ b/apps/ffcniftyapp/settings.js @@ -0,0 +1,35 @@ + +(function (back) { + var DEFAULTS = { + 'showWeekNum': false, + 'showWeather': false, + 'showSteps': false, + }; + let settings = require('Storage').readJSON("ffcniftyapp.json", 1) || DEFAULTS; + E.showMenu({ + + "": { "title": "Nifty-A Clock ++" }, + "< Back": () => back(), + /*LANG*/"Show Week Number": { + value: settings.showWeekNum, + onchange: v => { + settings.showWeekNum = v; + require("Storage").writeJSON("ffcniftyapp.json", settings); + } + }, + /*LANG*/"Show Weather": { + value: settings.showWeather, + onchange: w => { + settings.showWeather = w; + require("Storage").writeJSON("ffcniftyapp.json", settings); + } + }, + /*LANG*/"Show Steps": { + value: settings.showSteps, + onchange: z => { + settings.showSteps = z; + require("Storage").writeJSON("ffcniftyapp.json", settings); + } + } + }); +}) diff --git a/apps/reply/ChangeLog b/apps/reply/ChangeLog new file mode 100644 index 000000000..f3c7b0d2c --- /dev/null +++ b/apps/reply/ChangeLog @@ -0,0 +1 @@ +0.01: New Library! \ No newline at end of file diff --git a/apps/reply/README.md b/apps/reply/README.md new file mode 100644 index 000000000..dc874d183 --- /dev/null +++ b/apps/reply/README.md @@ -0,0 +1,23 @@ +# Canned Replies Library + +A library that handles replying to messages received from Gadgetbridge/Messages apps. + +## Replying to a message +The user can define a set of canned responses via the customise page after installing the app, or alternatively if they have a keyboard installed, they can type a response back. The requesting app will receive either an object containing the full reply for GadgetBridge, or a string with the response from the user, depending on how they wish to handle the response. + +## Integrating in your app +To use this in your app, simply call + +```js +require("reply").reply(/*options*/{...}).then(result => ...); +``` + +The ```options``` object can contain the following: + +- ```msg```: A message object containing a field ```id```, the ID to respond to. If this is included in options, the result of the promise will be an object as follows: ```{t: "notify", id: msg.id, n: "REPLY", msg: "USER REPLY"}```. If not included, the result of the promise will be an object, ```{msg: "USER REPLY"}``` +- ```shouldReply```: Whether or not the library should send the response over Bluetooth with ```Bluetooth.println(...```. Useful if the calling app wants to handle the response a different way. Default is true. +- ```title```: The title to show at the top of the menu. Defaults to ```"Reply with:"```. +- ```fileOverride```: An override file to read canned responses from, which is an array of objects each with a ```text``` property. Default is ```replies.json```. Useful for apps which might want to make use of custom canned responses. + +## Known Issues +Emojis are currently not supported. \ No newline at end of file diff --git a/apps/reply/app.png b/apps/reply/app.png new file mode 100644 index 000000000..bef8338cf Binary files /dev/null and b/apps/reply/app.png differ diff --git a/apps/reply/interface.html b/apps/reply/interface.html new file mode 100644 index 000000000..ddad4ef35 --- /dev/null +++ b/apps/reply/interface.html @@ -0,0 +1,122 @@ + + + + + + + + +
+ + + +
+
+
+ +
+
+
+
+
+
+

Loading

+

Syncing custom replies with your watch

+
+
+
+
+
+ +
+

No custom replies

+

Use the field above to add a custom reply

+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/apps/reply/lib.js b/apps/reply/lib.js new file mode 100644 index 000000000..4a040c557 --- /dev/null +++ b/apps/reply/lib.js @@ -0,0 +1,69 @@ +exports.reply = function (options) { + var keyboard = "textinput"; + try { + keyboard = require(keyboard); + } catch (e) { + keyboard = null; + } + + function constructReply(msg, replyText, resolve) { + var responseMessage = {msg: replyText}; + if (msg.id) { + responseMessage = { t: "notify", id: msg.id, n: "REPLY", msg: replyText }; + } + E.showMenu(); + if (options.sendReply == null || options.sendReply) { + Bluetooth.println(JSON.stringify(responseMessage)); + } + resolve(responseMessage); + } + + return new Promise((resolve, reject) => { + var menu = { + "": { + title: options.title || /*LANG*/ "Reply with:", + back: function () { + E.showMenu(); + reject("User pressed back"); + }, + }, // options + /*LANG*/ "Compose": function () { + keyboard.input().then((result) => { + constructReply(options.msg ?? {}, result, resolve); + }); + }, + }; + var replies = + require("Storage").readJSON( + options.fileOverride || "replies.json", + true + ) || []; + replies.forEach((reply) => { + menu = Object.defineProperty(menu, reply.text, { + value: () => constructReply(options.msg ?? {}, reply.text, resolve), + }); + }); + if (!keyboard) delete menu[/*LANG*/ "Compose"]; + + if (replies.length == 0) { + if (!keyboard) { + E.showPrompt( + /*LANG*/ "Please install a keyboard app, or set a custom reply via the app loader!", + { + buttons: { Ok: true }, + remove: function () { + reject( + "Please install a keyboard app, or set a custom reply via the app loader!" + ); + }, + } + ); + } else { + keyboard.input().then((result) => { + constructReply(options.msg.id, result, resolve); + }); + } + } + E.showMenu(menu); + }); +}; diff --git a/apps/reply/metadata.json b/apps/reply/metadata.json new file mode 100644 index 000000000..34843edd4 --- /dev/null +++ b/apps/reply/metadata.json @@ -0,0 +1,16 @@ +{ "id": "reply", + "name": "Reply Library", + "version": "0.01", + "description": "A library for replying to text messages via predefined responses or keyboard", + "icon": "app.png", + "type": "module", + "provides_modules" : ["reply"], + "tags": "", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"reply","url":"lib.js"} + ], + "data": [{"name":"replies.json"}] +} \ No newline at end of file diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 5993656b0..3be7db96a 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -81,3 +81,4 @@ of 'Select Clock' 0.70: Fix load() typo 0.71: Minor code improvements 0.72: Add setting for configuring BLE privacy +0.73: Fix `const` bug / work with fastload diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index a50aba27e..44774dc08 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.72", + "version": "0.73", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 3a3f53469..b348e00d5 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,3 +1,4 @@ +{ Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -984,3 +985,4 @@ function showTouchscreenCalibration() { } showMainMenu(); +} diff --git a/apps/setuichange/ChangeLog b/apps/setuichange/ChangeLog new file mode 100644 index 000000000..397e4f509 --- /dev/null +++ b/apps/setuichange/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Fix case where we tried to push to Bangle.btnWatches but it wasn't + defined. diff --git a/apps/setuichange/README.md b/apps/setuichange/README.md new file mode 100644 index 000000000..7803171e6 --- /dev/null +++ b/apps/setuichange/README.md @@ -0,0 +1,24 @@ +# setUI Proposals Preview + +Try out changes to setUI that may or may not eventually en up in the Bangle.js firmware. + +## Usage + +Just install it and it modifies setUI at boot time. + +## Features + +- Add custom handlers on top of the standard modes as well. Previously this was only possible for mode == "custom". + - The goal here is to make it possible to move all input handling inside `setUI` where today some apps add on extra handlers outside of `setUI` calls. +- Change the default behaviour of the hardware button to act immediately on press down. Previously it has been acting on button release. + - This makes the interaction slightly snappier. + - In addition to the existing `btn` key a new `btnRelease` key can now be specified. `btnRelease` will let you listen to the rising edge of the hardware button. + +## Requests + +Please report your experience and thoughts on this issue: +[ Discussion: HW buttons should act on 'rising' edge #3435 ](https://github.com/espruino/BangleApps/issues/3435) or on the related forum conversation (Link will be added when the conversation has opened). + +## Creator + +The changes done here were done by thyttan with help from Gordon Williams. diff --git a/apps/setuichange/app.png b/apps/setuichange/app.png new file mode 100644 index 000000000..78bea6c3b Binary files /dev/null and b/apps/setuichange/app.png differ diff --git a/apps/setuichange/boot.js b/apps/setuichange/boot.js new file mode 100644 index 000000000..c9f7aa898 --- /dev/null +++ b/apps/setuichange/boot.js @@ -0,0 +1,152 @@ +Bangle.setUI = (function(mode, cb) { + var options = {}; + if ("object"==typeof mode) { + options = mode; + mode = options.mode; + if (!mode) throw new Error("Missing mode in setUI({...})"); + } + var redraw = true; + if (global.WIDGETS && WIDGETS.back) { + redraw = false; + WIDGETS.back.remove(mode && options.back); + } + if (Bangle.btnWatches) { + Bangle.btnWatches.forEach(clearWatch); + delete Bangle.btnWatches; + } + if (Bangle.swipeHandler) { + Bangle.removeListener("swipe", Bangle.swipeHandler); + delete Bangle.swipeHandler; + } + if (Bangle.dragHandler) { + Bangle.removeListener("drag", Bangle.dragHandler); + delete Bangle.dragHandler; + } + if (Bangle.touchHandler) { + Bangle.removeListener("touch", Bangle.touchHandler); + delete Bangle.touchHandler; + } + delete Bangle.uiRedraw; + delete Bangle.CLOCK; + if (Bangle.uiRemove) { + let r = Bangle.uiRemove; + delete Bangle.uiRemove; // stop recursion if setUI is called inside uiRemove + r(); + } + g.reset();// reset graphics state, just in case + if (!mode) return; + function b() { + try{Bangle.buzz(30);}catch(e){} + } + if (mode=="updown") { + var dy = 0; + Bangle.dragHandler = e=>{ + dy += e.dy; + if (!e.b) dy=0; + while (Math.abs(dy)>32) { + if (dy>0) { dy-=32; cb(1) } + else { dy+=32; cb(-1) } + Bangle.buzz(20); + } + }; + Bangle.on('drag',Bangle.dragHandler); + Bangle.touchHandler = d => {b();cb();}; + Bangle.btnWatches = [ + setWatch(function() { b();cb(); }, BTN1, {repeat:1, edge:"rising"}), + ]; + } else if (mode=="leftright") { + var dx = 0; + Bangle.dragHandler = e=>{ + dx += e.dx; + if (!e.b) dx=0; + while (Math.abs(dx)>32) { + if (dx>0) { dx-=32; cb(1) } + else { dx+=32; cb(-1) } + Bangle.buzz(20); + } + }; + Bangle.on('drag',Bangle.dragHandler); + Bangle.touchHandler = d => {b();cb();}; + Bangle.btnWatches = [ + setWatch(function() { b();cb(); }, BTN1, {repeat:1, edge:"rising"}), + ]; + } else if (mode=="clock") { + Bangle.CLOCK=1; + Bangle.btnWatches = [ + setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"}) + ]; + } else if (mode=="clockupdown") { + Bangle.CLOCK=1; + Bangle.touchHandler = (d,e) => { + if (e.x < 120) return; + b();cb((e.y > 88) ? 1 : -1); + }; + Bangle.btnWatches = [ + setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"}) + ]; + } else if (mode=="custom") { + if (options.clock) { + Bangle.btnWatches = [ + setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"rising"}) + ]; + } + } else + throw new Error("Unknown UI mode "+E.toJS(mode)); + if (options.clock) Bangle.CLOCK=1; + if (options.touch) + Bangle.touchHandler = options.touch; + if (options.drag) { + Bangle.dragHandler = options.drag; + Bangle.on("drag", Bangle.dragHandler); + } + if (options.swipe) { + Bangle.swipeHandler = options.swipe; + Bangle.on("swipe", Bangle.swipeHandler); + } + if ((options.btn || options.btnRelease) && !Bangle.btnWatches) Bangle.btnWatches = []; + if (options.btn) Bangle.btnWatches.push(setWatch(options.btn.bind(options), BTN1, {repeat:1,edge:"rising"})) + if (options.btnRelease) Bangle.btnWatches.push(setWatch(options.btnRelease.bind(options), BTN1, {repeat:1,edge:"falling"})) + if (options.remove) // handler for removing the UI (intervals/etc) + Bangle.uiRemove = options.remove; + if (options.redraw) // handler for redrawing the UI + Bangle.uiRedraw = options.redraw; + if (options.back) { + var touchHandler = (_,e) => { + if (e.y<36 && e.x<48) { + e.handled = true; + E.stopEventPropagation(); + options.back(); + } + }; + Bangle.on("touch", touchHandler); + // If a touch handler was needed for setUI, add it - but ignore touches if they've already gone to the 'back' handler + if (Bangle.touchHandler) { + var uiTouchHandler = Bangle.touchHandler; + Bangle.touchHandler = (_,e) => { + if (!e.handled) uiTouchHandler(_,e); + }; + Bangle.on("touch", Bangle.touchHandler); + } + var btnWatch; + if (Bangle.btnWatches===undefined) // only add back button handler if there's no existing watch on BTN1 + btnWatch = setWatch(function() { + btnWatch = undefined; + options.back(); + }, BTN1, {edge:"rising"}); + WIDGETS = Object.assign({back:{ + area:"tl", width:24, + draw:e=>g.reset().setColor("#f00").drawImage(atob("GBiBAAAYAAH/gAf/4A//8B//+D///D///H/P/n+H/n8P/n4f/vwAP/wAP34f/n8P/n+H/n/P/j///D///B//+A//8Af/4AH/gAAYAA=="),e.x,e.y), + remove:(noclear)=>{ + if (btnWatch) clearWatch(btnWatch); + Bangle.removeListener("touch", touchHandler); + if (!noclear) g.reset().clearRect({x:WIDGETS.back.x, y:WIDGETS.back.y, w:24,h:24}); + delete WIDGETS.back; + if (!noclear) Bangle.drawWidgets(); + } + }},global.WIDGETS); + if (redraw) Bangle.drawWidgets(); + } else { // If a touch handler was needed for setUI, add it + if (Bangle.touchHandler) + Bangle.on("touch", Bangle.touchHandler); + } +}) diff --git a/apps/setuichange/metadata.json b/apps/setuichange/metadata.json new file mode 100644 index 000000000..2d6cafc81 --- /dev/null +++ b/apps/setuichange/metadata.json @@ -0,0 +1,13 @@ +{ "id": "setuichange", + "name": "SetUI Proposals preview", + "version":"0.02", + "description": "Try out potential future changes to `Bangle.setUI`. Makes hardware button interaction snappier. Makes it possible to set custom event handlers on any type/mode, not just `\"custom\"`. Please provide feedback - see `Read more...` below.", + "icon": "app.png", + "tags": "", + "type": "bootloader", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"setuichange.0.boot.js","url":"boot.js"} + ] +} diff --git a/apps/slpquiet/ChangeLog b/apps/slpquiet/ChangeLog index 5560f00bc..3a4fdc392 100644 --- a/apps/slpquiet/ChangeLog +++ b/apps/slpquiet/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: added check if settings file exists and create if missing diff --git a/apps/slpquiet/README.md b/apps/slpquiet/README.md index a1f2e94e1..673deef5d 100644 --- a/apps/slpquiet/README.md +++ b/apps/slpquiet/README.md @@ -25,3 +25,9 @@ It reuses the widget from [Quiet Mode Schedule and Widget](https://github.com/es -optimization of code (and check if needed) -feedback is always welcome + +#### Attributions +The app icon is downloaded from [https://icons8.com](https://icons8.com). + +#### License +[MIT License](LICENSE) \ No newline at end of file diff --git a/apps/slpquiet/app.js b/apps/slpquiet/app.js index 1de61498b..0b03ba4fd 100644 --- a/apps/slpquiet/app.js +++ b/apps/slpquiet/app.js @@ -1,5 +1,16 @@ const SETTINGS_FILE = "quietSwitch.json"; const storage = require("Storage"); + +//check if settings file exists and create if missing +if (storage.read(SETTINGS_FILE)=== undefined) { + print("data file not existing, using defaults"); + let saved = { + quietWhenSleep: 0, //off + quietMode: 1, //alerts + }; + storage.writeJSON(SETTINGS_FILE,saved); +} + let saved = storage.readJSON(SETTINGS_FILE, 1) || {}; // Main menu diff --git a/apps/slpquiet/metadata.json b/apps/slpquiet/metadata.json index 342dbc5d5..79fe857b1 100644 --- a/apps/slpquiet/metadata.json +++ b/apps/slpquiet/metadata.json @@ -1,7 +1,7 @@ { "id": "slpquiet", "name": "Sleep Quiet (activate quiet mode when asleep)", "shortName":"Sleep Quiet", - "version":"0.01", + "version":"0.02", "description": "Set Quiet mode (or alarms only mode), when the sleep tracking app detects sleep (each 10 min evaluated)", "icon": "app.png", "tags": "tool,widget", diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog index a341ee512..06c7577bc 100644 --- a/apps/swiperclocklaunch/ChangeLog +++ b/apps/swiperclocklaunch/ChangeLog @@ -4,3 +4,4 @@ 0.04: Update to work with new 'fast switch' clock->launcher functionality 0.05: Keep track of event listeners we "overwrite", and remove them at the start of setUI 0.06: Handle apps that call setUI({}) to reset +0.07: Use a more reliable method of detecting a clock diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index bb033891e..cbb2a4f61 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -3,24 +3,21 @@ var oldSwipe; Bangle.setUI = function(mode, cb) { - if (oldSwipe && oldSwipe !== Bangle.swipeHandler) + if (oldSwipe) { Bangle.removeListener("swipe", oldSwipe); + oldSwipe = undefined; + } + sui(mode,cb); - oldSwipe = Bangle.swipeHandler; - if ("object"==typeof mode) mode = mode.mode; - if (!mode) return; - - if (mode.startsWith("clock")) { + if (Bangle.CLOCK) { // clock -> launcher - Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; - Bangle.on("swipe", Bangle.swipeHandler); - } else { - if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") { - // launcher -> clock - Bangle.swipeHandler = dir => { if (dir>0) load(); }; - Bangle.on("swipe", Bangle.swipeHandler); - } + oldSwipe = dir => { if (dir<0) Bangle.showLauncher(); }; + Bangle.on("swipe", oldSwipe); + } else if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type==="launch") { + // launcher -> clock + oldSwipe = dir => { if (dir>0) load(); }; + Bangle.on("swipe", oldSwipe); } }; })(); diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json index 436d36868..d474b38e3 100644 --- a/apps/swiperclocklaunch/metadata.json +++ b/apps/swiperclocklaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "swiperclocklaunch", "name": "Swiper Clock Launch", - "version": "0.06", + "version": "0.07", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", "type": "bootloader", diff --git a/apps/thmswtch/README.md b/apps/thmswtch/README.md index 31e6aabe9..b30686e8f 100644 --- a/apps/thmswtch/README.md +++ b/apps/thmswtch/README.md @@ -33,3 +33,9 @@ The app functionality is inspired by the [Quiet Mode Schedule and Widget](https: -transfer of configuration into settings, so app can be used as a shortcut to switch theme. -feedback is always welcome + +#### Attributions +The app icon is downloaded from [https://icons8.com](https://icons8.com). + +#### License +[MIT License](LICENSE)