1
0
Fork 0

Merge branch 'espruino:master' into master

master
jeonlab 2024-07-09 07:44:19 -04:00 committed by GitHub
commit cac0e053e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 1246 additions and 72 deletions

View File

@ -5,7 +5,7 @@
"description": "A detailed description of my great app",
"icon": "app.png",
"tags": "",
"supports" : ["BANGLEJS2"],
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.app.js","url":"app.js"},

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

View File

@ -71,3 +71,4 @@
0.60: Minor code improvements
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
0.62: Handle setting for configuring BLE privacy
0.63: Only set BLE `display:1` if we have a passkey

View File

@ -79,9 +79,9 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},` : "";
let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},display:1,mitm:1,` : "";
let privacy = s.bleprivacy ? `privacy:${E.toJS(s.bleprivacy)},` : "";
boot+=`NRF.setSecurity({${passkey}${privacy}mitm:1,display:1});\n`;
boot+=`NRF.setSecurity({${passkey}${privacy}});\n`;
}
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.62",
"version": "0.63",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -0,0 +1 @@
0.01: Initial release.

View File

@ -0,0 +1,11 @@
# BLE BTHome Battery Service
Broadcasts battery remaining percentage over BLE using the [BTHome protocol](https://bthome.io/) - which makes for easy integration into [Home Assistant](https://www.home-assistant.io/)
## Usage
This boot code runs in the background and has no user interface.
## Creator
[Deirdre O'Byrne](https://github.com/deirdreobyrne)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
var btHomeBatterySequence = 0;
function advertiseBTHomeBattery() {
var advert = [0x40, 0x00, btHomeBatterySequence, 0x01, E.getBattery()];
require("ble_advert").set(0xFCD2, advert);
btHomeBatterySequence = (btHomeBatterySequence + 1) & 255;
}
setInterval(function() {
advertiseBTHomeBattery();
}, 300000); // update every 5 min
advertiseBTHomeBattery();

View File

@ -0,0 +1,15 @@
{
"id": "bootbthomebatt",
"name": "BLE BTHome Battery Service",
"shortName": "BTHome Battery Service",
"version": "0.01",
"description": "Broadcasts battery remaining over bluetooth using the BTHome protocol - makes for easy integration with Home Assistant.\n",
"icon": "bluetooth.png",
"type": "bootloader",
"tags": "battery,ble,bluetooth,bthome",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"bthomebat.boot.js","url":"boot.js"}
]
}

View File

@ -1,3 +1,4 @@
0.01: New app!
0.02: Advertise accelerometer data and sensor location
0.03: Use the bleAdvert module
0.04: Actually use the ble_advert module

View File

@ -1,4 +1,3 @@
var _a;
{
var __assign = Object.assign;
var Layout_1 = require("Layout");
@ -441,8 +440,6 @@ var _a;
NRF.setServices(ad, {
uart: false,
});
var bangle2 = Bangle;
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for (var id in ad) {
var serv = ad[id];
var value = void 0;
@ -450,11 +447,7 @@ var _a;
value = serv[ch].value;
break;
}
cycle.push((_a = {}, _a[id] = value || [], _a));
require("ble_advert").set(id, value || []);
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(cycle, {
interval: 100,
});
}
}

View File

@ -767,12 +767,6 @@ enableSensors();
},
);
type BleAdvert = { [key: string]: number[] };
const bangle2 = Bangle as {
bleAdvert?: BleAdvert | BleAdvert[];
};
const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for(const id in ad){
const serv = ad[id as BleServ];
let value;
@ -783,16 +777,7 @@ enableSensors();
break;
}
cycle.push({ [id]: value || [] });
require("ble_advert").set(id, value || []);
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(
cycle,
{
interval: 100,
}
);
}
}

View File

@ -2,7 +2,7 @@
"id": "btadv",
"name": "btadv",
"shortName": "btadv",
"version": "0.03",
"version": "0.04",
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
"icon": "icon.png",
"tags": "health,tool,sensors,bluetooth",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Handle the case where other apps have set bleAdvert to an array
0.03: Use the ble_advert module

View File

@ -38,21 +38,7 @@ function onTemperature(p) {
pressure100&255,(pressure100>>8)&255,pressure100>>16
];
if(Array.isArray(Bangle.bleAdvert)){
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0xFCD2]){
ad[0xFCD2] = advert;
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0xFCD2: advert });
}else{
Bangle.bleAdvert[0xFCD2] = advert;
}
NRF.setAdvertising(Bangle.bleAdvert);
require("ble_advert").set(0xFCD2, advert);
}
// Gets the temperature in the most accurate way with pressure sensor
@ -60,7 +46,6 @@ function drawTemperature() {
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
}
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
setInterval(function() {
drawTemperature();
}, 10000); // update every 10s

View File

@ -1,7 +1,7 @@
{ "id": "bthometemp",
"name": "BTHome Temperature and Pressure",
"shortName":"BTHome T",
"version":"0.02",
"version":"0.03",
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
"icon": "app.png",
"tags": "bthome,bluetooth,temperature",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Remove 's' after seconds (on some clocks this looks like '5')

View File

@ -11,7 +11,7 @@
g.drawImage(atob("GBgBAP4AA/+ABwHAHABwGAAwMAAYYAAMYAAMwAAGwAAGwAAGwAAGwAAGwAAGwAAGYAAMYAAMMAAYGAAwHABwBwHAA/+AAP4AAAAA"));
g.drawLine(11,11,x,y).drawLine(12,11,x+1,y).drawLine(11,12,x,y+1).drawLine(12,12,x+1,y+1);
return {
text : s.toString().padStart(2,0)+"s",
text : s.toString().padStart(2,0),
img : g.asImage("string")
};
},

View File

@ -1,6 +1,6 @@
{ "id": "clkinfosec",
"name": "Secondx Clockinfo",
"version":"0.01",
"name": "Seconds Clockinfo",
"version":"0.02",
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the time in seconds (many clocks only display minutes)",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -9,3 +9,5 @@
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
0.10: Use widget_utils.
0.11: Minor code improvements
0.12: Added setting to change Battery estimate to hours
0.13: Fixed Battery estimate Default to percentage and improved setting string

View File

@ -10,7 +10,7 @@ Forum](http://forum.espruino.com/microcosms/1424/)
* Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate)
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate, Battery Estimate)
* The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle
* The heart value is displayed in RED if the confidence value is less than 50%
* NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about.
@ -20,6 +20,7 @@ See [#1248](https://github.com/espruino/BangleApps/issues/1248)
[MyLocation](https://banglejs.com/apps/?id=mylocation)
* The screen is updated every minute to save battery power
* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use
* You need to run >2V22 to show the battery estimate in hours
## Future Development
* Use mini icons in the information line rather that text

View File

@ -83,6 +83,7 @@ function loadSettings() {
settings.gy = settings.gy||'#020';
settings.fg = settings.fg||'#0f0';
settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check);
settings.batt_hours = (settings.batt_hours === undefined ? false : settings.batt_hours);
assignPalettes();
}
@ -112,13 +113,39 @@ function updateSunRiseSunSet(now, lat, lon, line){
sunSet = extractTime(times.sunset);
}
function batteryString(){
let stringToInsert;
if (settings.batt_hours) {
var batt_usage = 200000/E.getPowerUsage().total;
let rounded;
if (batt_usage > 24) {
var days = Math.floor(batt_usage/24);
var hours = Math.round((batt_usage/24 - days) * 24);
stringToInsert = '\n' + days + ((days < 2) ? 'd' : 'ds') + ' ' + hours + ((hours < 2) ? 'h' : 'hs');
}
else if (batt_usage > 9) {
rounded = Math.round(200000/E.getPowerUsage().total * 10) / 10;
}
else {
rounded = Math.round(200000/E.getPowerUsage().total * 100) / 100;
}
if (batt_usage < 24) {
stringToInsert = '\n' + rounded + ' ' + ((batt_usage < 2) ? 'h' : 'hs');
}
}
else{
stringToInsert = ' ' + E.getBattery() + '%';
}
return 'BATTERY' + stringToInsert;
}
const infoData = {
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
ID_SS: { calc: () => 'SUNSET ' + sunSet },
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' },
ID_BATT: { calc: batteryString},
ID_HRM: { calc: () => hrmCurrent }
};

View File

@ -1,6 +1,6 @@
{ "id": "daisy",
"name": "Daisy",
"version": "0.11",
"version": "0.13",
"dependencies": {"mylocation":"app"},
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
"icon": "app.png",

View File

@ -5,7 +5,8 @@
let s = {'gy' : '#020',
'fg' : '#0f0',
'color': 'Green',
'check_idle' : true};
'check_idle' : true,
'batt_hours' : false};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
@ -45,6 +46,14 @@
s.idle_check = v;
save();
},
},
'Expected Battery Life In Days Not Percentage': {
value: !!s.batt_hours,
onchange: v => {
s.batt_hours = v;
save();
},
}
});
})

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

View File

@ -505,7 +505,7 @@ function checkMessages(options) {
// draw the body
g.drawString(l.join("\n"), x+10,r.y+20+pady);
}
if (!longBody && msg.src) g.setFontAlign(-1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2);
if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2);
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
},
select : idx => {

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 [Making Bangle.js more responsive](https://forum.espruino.com/conversations/397606/).
## 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)

View File

@ -0,0 +1 @@
0.01: Initial fork from hwid_a_battery_widget

11
apps/widbattpwr/README.md Normal file
View File

@ -0,0 +1,11 @@
# Battery Power Widget
Show the time remaining at the current power consumption, and battery percentage via shading of the text and a percentage bar.
Requires firmware 2v23 or above.
This is a copy of `hwid_a_battery_widget` (that being a copy of `wid_a_battery_widget`).
## Creator
[@bobrippling](https://github.com/bobrippling)

View File

@ -0,0 +1,19 @@
{
"id": "widbattpwr",
"name": "Battery power and percentage widget",
"shortName": "Batt Pwr",
"icon": "widget.png",
"version": "0.01",
"type": "widget",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"description": "A battery widget showing percentage (via shading) and time remaining at current power consumption",
"tags": "widget,battery",
"provides_widgets": ["battery"],
"storage": [
{
"name": "widbattpwr.wid.js",
"url": "widget.js"
}
]
}

81
apps/widbattpwr/widget.js Normal file
View File

@ -0,0 +1,81 @@
(function () {
var intervalLow = 60000;
var intervalHigh = 2000;
var width = 30;
var height = 24;
var showPct = false;
var powerColour = function (pwr) {
return pwr >= 23000
? "#f00"
: pwr > 2000
? "#fc0"
: "#0f0";
};
var drawBar = function (x, y, batt) {
return g.fillRect(x + 1, y + height - 3, x + 1 + (width - 2) * batt / 100, y + height - 1);
};
var drawString = function (x, y, txt) {
return g.drawString(txt, x + 14, y + 10);
};
function draw() {
var x = this.x;
var y = this.y;
var batt = E.getBattery();
var pwr = E.getPowerUsage();
var usage = 0;
for (var key in pwr.device) {
if (!/^(LCD|LED)/.test(key))
usage += pwr.device[key];
}
var pwrColour = powerColour(usage);
g.reset()
.setBgColor(g.theme.bg)
.clearRect(x, y, x + width - 1, y + height - 1);
g.setColor(g.theme.fg);
drawBar(x, y, 100);
g.setColor(pwrColour);
drawBar(x, y, batt);
g.setFontAlign(0, 0);
g.setFont("Vector", 16);
{
var txt = void 0;
if (showPct) {
txt = "".concat(batt, "%");
}
else {
var hrs = 175000 * batt / (100 * usage);
var days = hrs / 24;
txt = days >= 1 ? "".concat(Math.round(Math.min(days, 99)), "d") : "".concat(Math.round(hrs), "h");
}
var txth = 14;
g.setColor(g.theme.fg);
g.setClipRect(x, y, x + width, y + txth);
drawString(x, y, txt);
g.setColor(pwrColour);
g.setClipRect(x, y + txth * (1 - batt / 100), x + width, y + txth);
drawString(x, y, txt);
}
}
var id = setInterval(function () {
var w = WIDGETS["battpwr"];
w.draw(w);
}, intervalLow);
Bangle.on("charging", function (charging) {
changeInterval(id, charging ? intervalHigh : intervalLow);
});
Bangle.on("touch", function (_btn, xy) {
if (WIDGETS["back"] || !xy)
return;
var oversize = 5;
var w = WIDGETS["battpwr"];
var x = xy.x, y = xy.y;
if (w.x - oversize <= x && x < w.x + width + oversize
&& w.y - oversize <= y && y < w.y + height + oversize) {
E.stopEventPropagation && E.stopEventPropagation();
showPct = true;
setTimeout(function () { return (showPct = false, w.draw(w)); }, 1000);
w.draw(w);
}
});
WIDGETS["battpwr"] = { area: "tr", width: width, draw: draw };
})();

BIN
apps/widbattpwr/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

96
apps/widbattpwr/widget.ts Normal file
View File

@ -0,0 +1,96 @@
(() => {
const intervalLow = 60000;
const intervalHigh = 2000;
const width = 30;
const height = 24;
let showPct = false;
const powerColour = (pwr: number) =>
pwr >= 23000
? "#f00" // red, e.g. GPS ~20k
: pwr > 2000
? "#fc0" // yellow, e.g. CPU ~1k, HRM ~700
: "#0f0"; // green: ok
const drawBar = (x: number, y: number, batt: number) =>
g.fillRect(x+1, y+height-3, x+1+(width-2)*batt/100, y+height-1);
const drawString = (x: number, y: number, txt: string) =>
g.drawString(txt, x + 14, y + 10);
function draw(this: Widget) {
let x = this.x!;
let y = this.y!;
const batt = E.getBattery();
const pwr = E.getPowerUsage();
let usage = 0;
for(const key in pwr.device){
if(!/^(LCD|LED)/.test(key))
usage += pwr.device[key];
}
const pwrColour = powerColour(usage);
g.reset()
.setBgColor(g.theme.bg)
.clearRect(x, y, x + width - 1, y + height - 1);
g.setColor(g.theme.fg);
drawBar(x, y, 100);
g.setColor(pwrColour);
drawBar(x, y, batt);
g.setFontAlign(0, 0);
g.setFont("Vector", 16);
{
let txt;
if(showPct){
txt = `${batt}%`;
}else{
// 175mAh, scaled based on battery (batt/100), scaled down based on usage
const hrs = 175000 * batt / (100 * usage);
const days = hrs / 24;
txt = days >= 1 ? `${Math.round(Math.min(days, 99))}d` : `${Math.round(hrs)}h`;
}
// draw time remaining, then shade it based on batt %
const txth = 14;
g.setColor(g.theme.fg);
g.setClipRect(x, y, x + width, y + txth);
drawString(x, y, txt);
g.setColor(pwrColour);
g.setClipRect(x, y + txth * (1 - batt / 100), x + width, y + txth);
drawString(x, y, txt);
}
}
const id = setInterval(() => {
const w = WIDGETS["battpwr"]!;
w.draw(w);
}, intervalLow);
Bangle.on("charging", charging => {
changeInterval(id, charging ? intervalHigh : intervalLow);
});
Bangle.on("touch", (_btn, xy) => {
if(WIDGETS["back"] || !xy) return;
const oversize = 5;
const w = WIDGETS["battpwr"]!;
const { x, y } = xy;
if(w.x! - oversize <= x && x < w.x! + width + oversize
&& w.y! - oversize <= y && y < w.y! + height + oversize)
{
E.stopEventPropagation && E.stopEventPropagation();
showPct = true;
setTimeout(() => (showPct = false, w.draw(w)), 1000);
w.draw(w);
}
});
WIDGETS["battpwr"] = { area: "tr", width, draw };
})();

View File

@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") {
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
}
var RECOMMENDED_VERSION = "2v22";
var RECOMMENDED_VERSION = "2v23";
// could check http://www.espruino.com/json/BANGLEJS.json for this
// We're only interested in Bangles