pull/3490/head
pinq- 2024-07-05 23:31:06 +03:00
commit d1274bc77b
53 changed files with 936 additions and 17 deletions

BIN
apps/ashadyclock/0.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/1.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/2.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/3.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/4.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/5.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/6.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/7.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/8.bin Normal file

Binary file not shown.

BIN
apps/ashadyclock/9.bin Normal file

Binary file not shown.

View File

@ -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"))

107
apps/ashadyclock/app.js Normal file
View File

@ -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();
}

BIN
apps/ashadyclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

View File

@ -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();
}
},
});
})

1
apps/delaylock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

23
apps/delaylock/README.md Normal file
View File

@ -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/

BIN
apps/delaylock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

21
apps/delaylock/boot.js Normal file
View File

@ -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);
}

View File

@ -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"}
]
}

View File

@ -0,0 +1,3 @@
0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock

View File

@ -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

View File

@ -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=="))

174
apps/ffcniftyapp/app.js Normal file
View File

@ -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();

BIN
apps/ffcniftyapp/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -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);
}
}
});
})

1
apps/reply/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New Library!

23
apps/reply/README.md Normal file
View File

@ -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.

BIN
apps/reply/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

122
apps/reply/interface.html Normal file
View File

@ -0,0 +1,122 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
</head>
<body>
<form id="replyForm">
<label class="label">New Custom Reply:</label>
<input class="form-input" id="msg" type="text" required></input>
<input class="btn btn-primary" type="submit" value="Add">
<br>
<br>
</form>
<button class="btn btn-primary" onclick="updateDevice()">Update Device</button>
<div>
<div id="loading">
<div class="empty">
<div class="empty-icon">
<div class="loading loading-lg"></div>
</div>
<p class="empty-title h5">Loading</p>
<p class="empty-subtitle">Syncing custom replies with your watch</p>
</div>
</div>
<div id="empty" class="d-hide">
<div class="empty">
<div class="empty-icon">
<i class="icon icon-more-horiz"></i>
</div>
<p class="empty-title h5">No custom replies</p>
<p class="empty-subtitle">Use the field above to add a custom reply</p>
</div>
</div>
<table id="replyList" class="table table-striped table-hover"></table>
</div>
<script src="../../core/lib/customize.js"></script>
<script>
const form = document.querySelector("#replyForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
saveReply();
});
var replies = [];
var fetching = true; // Bit of a hack
var el = document.getElementById('items');
function onInit(device) {
fetching = true;
Util.readStorageJSON("replies.json", (arr) => {
if (arr) {
replies = replies.concat(arr);
fetching = false;
renderReplyList();
}
});
}
function toggleVisibility() {
let empty = document.getElementById("empty");
let loading = document.getElementById("loading");
if (fetching) {
loading.setAttribute("class", "d-block");
empty.setAttribute("class", "d-hide");
}
else if (replies.length == 0) {
empty.setAttribute("class", "d-block");
loading.setAttribute("class", "d-hide");
}
else {
loading.setAttribute("class", "d-hide");
empty.setAttribute("class", "d-hide");
}
}
function renderReplyList() {
toggleVisibility();
let table = document.getElementById("replyList");
table.innerHTML = "";
if (!fetching) {
for (var i = 0; i < replies.length; i++) {
var reply = replies[i];
var li = document.createElement("tr");
var label = document.createElement("td");
label.innerHTML = reply.text + " ";
var deleteButton = document.createElement("button");
deleteButton.innerText = "Delete";
deleteButton.setAttribute("onclick", `deleteReply(${i})`);
deleteButton.setAttribute("class", "btn btn-error");
li.appendChild(label);
li.appendChild(deleteButton);
table.appendChild(li);
}
}
}
function deleteReply(index) {
replies.splice(index, 1);
renderReplyList(replies);
}
function saveReply() {
var reply = {};
reply.text = document.getElementById('msg').value;
replies.push(reply);
updateDevice();
}
function updateDevice() {
fetching = true;
renderReplyList();
Util.writeStorage("replies.json", JSON.stringify(replies), () => {
fetching = false;
renderReplyList();
});
}
</script>
</body>
</html>

69
apps/reply/lib.js Normal file
View File

@ -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);
});
};

16
apps/reply/metadata.json Normal file
View File

@ -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"}]
}

View File

@ -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

View File

@ -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",

View File

@ -1,3 +1,4 @@
{
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -984,3 +985,4 @@ function showTouchscreenCalibration() {
}
showMainMenu();
}

View File

@ -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.

View File

@ -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.

BIN
apps/setuichange/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

152
apps/setuichange/boot.js Normal file
View File

@ -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);
}
})

View File

@ -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"}
]
}

View File

@ -1 +1,2 @@
0.01: New App!
0.02: added check if settings file exists and create if missing

View File

@ -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)

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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);
}
};
})();

View File

@ -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",

View File

@ -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)