Merge branch 'espruino:master' into sleeplogalarm
|
@ -10,3 +10,4 @@
|
|||
0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021)
|
||||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
0.12: Actual pixels as of 29th Nov 2021
|
||||
0.13: Bangle.js 2: Use setUI to add software back button
|
||||
|
|
|
@ -69,4 +69,7 @@ function drawImage() {
|
|||
|
||||
// TODO: a nice little animation before
|
||||
setTimeout(drawInfo, 1000);
|
||||
setWatch(_=>load(), BTN1);
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
back : load
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "about",
|
||||
"name": "About",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night
|
||||
0.07: Fix bug on the cutting edge firmware
|
||||
0.08: Use default Bangle formatter for booleans
|
||||
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
||||
0.09: New app screen (instead of showing settings or the alert) and some optimisations
|
||||
0.10: Add software back button via setUI
|
||||
|
|
|
@ -47,8 +47,12 @@
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawInfo();
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
back : load
|
||||
})
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Activity Reminder",
|
||||
"shortName":"Activity Reminder",
|
||||
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
|
||||
"version":"0.09",
|
||||
"version":"0.10",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"tags": "tool,activity",
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Disable past events display from settings
|
||||
0.04: Added awareness of allDay field
|
||||
0.05: Displaying calendar colour and name
|
||||
0.06: Added clkinfo for clocks.
|
|
@ -0,0 +1,29 @@
|
|||
(function() {
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
|
||||
items: []
|
||||
};
|
||||
|
||||
var now = new Date();
|
||||
var agenda = storage.readJSON("android.calendar.json")
|
||||
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
|
||||
.sort((a,b)=>a.timestamp - b.timestamp);
|
||||
|
||||
agenda.forEach((entry, i) => {
|
||||
|
||||
var title = entry.title.slice(0,18);
|
||||
var date = new Date(entry.timestamp*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||
|
||||
agendaItems.items.push({
|
||||
name: "agendaEntry-" + i,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||
hide: function () {}
|
||||
});
|
||||
});
|
||||
|
||||
return agendaItems;
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
@ -12,6 +12,7 @@
|
|||
"storage": [
|
||||
{"name":"agenda.app.js","url":"agenda.js"},
|
||||
{"name":"agenda.settings.js","url":"settings.js"},
|
||||
{"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"},
|
||||
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"agenda.settings.json"}]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{ "id": "agpsdata",
|
||||
"name": "A-GPS Data",
|
||||
"name": "A-GPS Data Downloader App",
|
||||
"shortName":"A-GPS Data",
|
||||
"icon": "agpsdata.png",
|
||||
"version":"0.02",
|
||||
"description": "Download assisted GPS (A-GPS) data directly to your Bangle.js **using Gadgetbridge**",
|
||||
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||
"tags": "boot,tool,assisted,gps,agps,http",
|
||||
"allow_emulator":true,
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -10,4 +10,5 @@
|
|||
week is buffered until date or timezone changes
|
||||
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
|
||||
0.08: fixed calendar weeknumber not shortened to two digits
|
||||
0.09: Use default Bangle formatter for booleans
|
||||
0.09: Use default Bangle formatter for booleans
|
||||
0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Update (AGPS)",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"version": "0.03",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"sortorder": -1,
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,outdoors,agps",
|
||||
"tags": "tool,outdoors,agps,gps,a-gps",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Add sit ups
|
||||
Add more feedback to the user about the exercises
|
||||
Clean up code
|
||||
0.03: Add software back button on main menu
|
||||
|
|
|
@ -71,7 +71,8 @@ function showMainMenu() {
|
|||
let menu;
|
||||
menu = {
|
||||
"": {
|
||||
title: "BanglExercise"
|
||||
title: "BanglExercise",
|
||||
back: load
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -381,4 +382,5 @@ Bangle.on('HRM', function(hrm) {
|
|||
});
|
||||
|
||||
g.clear(1);
|
||||
Bangle.loadWidgets();
|
||||
showMainMenu();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: bug fix
|
||||
0.05: proper fix for the race condition in queueDraw()
|
||||
0.06: Tell clock widgets to hide.
|
||||
0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge
|
||||
|
|
|
@ -11,6 +11,8 @@ Graphics.prototype.setFontOpenSans = function(scale) {
|
|||
};
|
||||
|
||||
var drawTimeout;
|
||||
var lastBattCheck = 0;
|
||||
var width = 0;
|
||||
|
||||
function queueDraw(millis_now) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
|
@ -24,12 +26,15 @@ function draw() {
|
|||
var date = new Date();
|
||||
var h = date.getHours(),
|
||||
m = date.getMinutes();
|
||||
var d = date.getDate(),
|
||||
w = date.getDay(); // d=1..31; w=0..6
|
||||
const level = E.getBattery();
|
||||
const width = level + (level/2);
|
||||
var d = date.getDate();
|
||||
var is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
var dows = require("date_utils").dows(0,1);
|
||||
var dow = require("date_utils").dows(0,1)[date.getDay()];
|
||||
|
||||
if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) {
|
||||
lastBattcheck = date.getTime();
|
||||
width = E.getBattery();
|
||||
width += width/2;
|
||||
}
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
|
@ -47,24 +52,35 @@ function draw() {
|
|||
g.drawString(d, g.getWidth() -6, 98);
|
||||
g.setFont('Vector', 52);
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString(dows[w].slice(0,2).toUpperCase(), 6, 103);
|
||||
g.drawString(dow.slice(0,2).toUpperCase(), 6, 103);
|
||||
|
||||
g.fillRect(9,159,166,171);
|
||||
g.fillRect(167,163,170,167);
|
||||
if (Bangle.isCharging()) {
|
||||
g.setColor(1,1,0);
|
||||
} else if (level > 40) {
|
||||
g.setColor(0,1,0);
|
||||
g.fillRect(12,162,12+width,168);
|
||||
} else {
|
||||
g.setColor(1,0,0);
|
||||
g.fillRect(12,162,57,168);
|
||||
g.setColor(1,1,0);
|
||||
g.fillRect(58,162,72,168);
|
||||
g.setColor(0,1,0);
|
||||
g.fillRect(73,162,162,168);
|
||||
}
|
||||
g.fillRect(12,162,12+width,168);
|
||||
if (level < 100) {
|
||||
if (width < 150) {
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(12+width+1,162,162,168);
|
||||
}
|
||||
|
||||
g.setColor(0, 1, 0);
|
||||
if (Bangle.isCharging()) {
|
||||
g.setColor(1,1,0);
|
||||
} else if (width <= 45) {
|
||||
g.setColor(1,0,0);
|
||||
} else if (width <= 60) {
|
||||
g.setColor(1,1,0);
|
||||
} else {
|
||||
g.setColor(0, 1, 0);
|
||||
}
|
||||
g.fillRect(0, 90, g.getWidth(), 94);
|
||||
|
||||
// widget redraw
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "bigdclock",
|
||||
"name": "Big digit clock containing just the essentials",
|
||||
"shortName":"Big digit clk",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
|
||||
"icon": "bigdclock.png",
|
||||
"type": "clock",
|
||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
@ -18,4 +18,6 @@
|
|||
0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set.
|
||||
0.19: Fix - Compatibility with "Digital clock widget"
|
||||
0.20: Better handling of async data such as getPressure.
|
||||
0.21: On the default menu the week of year can be shown.
|
||||
0.21: On the default menu the week of year can be shown.
|
||||
0.22: Use the new clkinfo module for the menu.
|
||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
|
@ -1,12 +1,15 @@
|
|||
# BW Clock
|
||||
A very minimalistic clock to mainly show date and time.
|
||||
A very minimalistic clock.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
The BW clock provides many features and also 3rd party integrations:
|
||||
The BW clock implements features that are exposed by other apps through the `clkinfo` module.
|
||||
For example, if you install the HomeAssistant app, this menu item will be shown if you click right
|
||||
and additionally allows you to send triggers directly from the clock (select triggers via up/down and
|
||||
send via click center). Here are examples of other apps that are integrated:
|
||||
|
||||
- Bangle data such as steps, heart rate, battery or charging state.
|
||||
- A timer can be set directly. *Requirement: Scheduler library*
|
||||
- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
|
||||
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
|
||||
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
|
||||
|
@ -20,27 +23,27 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is
|
|||
- Your bangle uses the sys color settings so you can change the color too.
|
||||
|
||||
## Menu structure
|
||||
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to set a timer or send a HomeAssistant trigger.
|
||||
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant.
|
||||
|
||||
Simply click left / right to go through the menu entries such as Bangle, Timer etc.
|
||||
Simply click left / right to go through the menu entries such as Bangle, Weather etc.
|
||||
and click up/down to move into this sub-menu. You can then click in the middle of the screen
|
||||
to e.g. send a trigger via HomeAssistant once you selected it.
|
||||
to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend
|
||||
on the app that provide this sub-menu through the `clkinfo` module.
|
||||
|
||||
```
|
||||
+5min
|
||||
|
|
||||
Bangle -- Timer[Optional] -- Agenda 1[Optional] -- Weather[Optional] -- HomeAssistant [Optional]
|
||||
| | | | |
|
||||
Bpm -5min Agenda 2 Temperature Trigger1
|
||||
| | | |
|
||||
Steps ... ... ...
|
||||
Bangle -- Agenda -- Weather -- HomeAssistant
|
||||
| | | |
|
||||
Battery Entry 1 Temperature Trigger1
|
||||
| | | |
|
||||
Steps ... ... ...
|
||||
|
|
||||
Battery
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Thanks to
|
||||
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||
- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
|
||||
- <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||
|
||||
## Creator
|
||||
[David Peer](https://github.com/peerdavid)
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
/************
|
||||
/************************************************
|
||||
* Includes
|
||||
*/
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
|
||||
/************
|
||||
* Statics
|
||||
|
||||
/************************************************
|
||||
* Globals
|
||||
*/
|
||||
const SETTINGS_FILE = "bwclk.setting.json";
|
||||
const TIMER_IDX = "bwclk_timer";
|
||||
const TIMER_AGENDA_IDX = "bwclk_agenda";
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
var lock_input = false;
|
||||
|
||||
/************
|
||||
|
||||
/************************************************
|
||||
* Settings
|
||||
*/
|
||||
let settings = {
|
||||
|
@ -29,8 +31,7 @@ for (const key in saved_settings) {
|
|||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
|
||||
/************
|
||||
/************************************************
|
||||
* Assets
|
||||
*/
|
||||
// Manrope font
|
||||
|
@ -45,14 +46,12 @@ Graphics.prototype.setLargeFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setMediumFont = function(scale) {
|
||||
// Actual height 41 (42 - 2)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setSmallFont = function(scale) {
|
||||
// Actual height 28 (27 - 0)
|
||||
this.setFontCustom(
|
||||
|
@ -64,7 +63,6 @@ Graphics.prototype.setSmallFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setMiniFont = function(scale) {
|
||||
// Actual height 16 (15 - 0)
|
||||
this.setFontCustom(
|
||||
|
@ -76,8 +74,6 @@ Graphics.prototype.setMiniFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
||||
function imgLock(){
|
||||
return {
|
||||
width : 16, height : 16, bpp : 1,
|
||||
|
@ -86,434 +82,100 @@ function imgLock(){
|
|||
}
|
||||
}
|
||||
|
||||
function imgSteps(){
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/H///wv4CBn4CD8ACCj4IBj8f+Eeh/wjgCBngCCg/4nEH//4h/+jEP/gRBAQX+jkf/wgB//8GwP4FoICDHgICCBwIA=="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgBattery(){
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/4AN4EAg4TBgd///9oEAAQv8ARQRDDQQgCEwQ4OA"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgCharging() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgBpm() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/4AOn4CD/wCCjgCCv/8jF/wGYgOA5MB//BC4PDAQnjAQPnAQgANA"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgTemperature() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("//D///wICBjACBngCNkgCP/0kv/+s1//nDn/8wICEBAIOC/08v//IYJECA=="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgWeather(){
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgWind () {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/0f//8h///Pn//zAQXzwf/88B//mvGAh18gEevn/DIICB/PwgEBAQMHBAIADFwM/wEAGAP/54CD84CE+eP//wIQU/A=="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgHumidity () {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("//7///+YCB+ICB8ACE4F/AQX9AQP54H//AOB+F/34CBj/gn8f4E+h/Aj0H4Ecg+AjED4ACE8E4gfwvEDEgICB/kHGwMP"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgTimer() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/+B/4CD84CEBAPygFP+F+h/x/+P+fz5/n+HnAQNn5/wuYCBmYCC5kAAQfOgFz80As/ngHn+fD54mC/F+j/+gF/HAQA=="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgWatch() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/8B//+ARANB/l4//5/1/+f/n/n5+fAQnf9/P44CC8/n7/n+YOB/+fDQQgCEwQsCHBBEC"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgHomeAssistant() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/4AF84CB4YCBwICBCAP+jFH/k8g/4kkH+AFB8ACB4cY4eHzPhgmZkHnzPn8fb4/gvwUD8EYARhAC"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgAgenda() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("/4AFnPP+ALBAQX4CIgLFAQvggEBAQvAgEDAQMCwEAgwTBhgiB/AlCGQ8BGQQ"))
|
||||
}
|
||||
}
|
||||
|
||||
function imgMountain() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : atob("//////////////////////3///n///D//uZ//E8//A+/+Z+f8//P5//n7//3z//zn//5AAAAAAAA////////////////////")
|
||||
}
|
||||
}
|
||||
|
||||
/************
|
||||
* 2D MENU with entries of:
|
||||
* [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
|
||||
*
|
||||
/************************************************
|
||||
* Menu
|
||||
*/
|
||||
var menu = [
|
||||
[
|
||||
function(){ return [ null, null ] },
|
||||
function(){ return [ "Week " + weekOfYear(), null ] },
|
||||
],
|
||||
[
|
||||
function(){ return [ "Bangle", imgWatch() ] },
|
||||
function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
|
||||
function(){ return [ getSteps(), imgSteps() ] },
|
||||
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
|
||||
function(){ return [ measureAltitude, imgMountain() ]},
|
||||
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
|
||||
var bwItems = {
|
||||
name: null,
|
||||
img: null,
|
||||
items: [
|
||||
{ name: "WeekOfYear",
|
||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||
show: function() { bwItems.items[0].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
/*
|
||||
* Timer Menu
|
||||
*/
|
||||
try{
|
||||
require('sched');
|
||||
menu.push([
|
||||
function(){
|
||||
var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer";
|
||||
return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ]
|
||||
},
|
||||
]);
|
||||
} catch(ex) {
|
||||
// If sched is not installed, we hide this menu item
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AGENDA MENU
|
||||
* Note that we handle the agenda differently in order to hide old entries...
|
||||
*/
|
||||
var agendaIdx = 0;
|
||||
var agendaTimerIdx = 0;
|
||||
if(storage.readJSON("android.calendar.json") !== undefined){
|
||||
function nextAgendaEntry(){
|
||||
agendaIdx += 1;
|
||||
}
|
||||
|
||||
function previousAgendaEntry(){
|
||||
agendaIdx -= 1;
|
||||
}
|
||||
|
||||
menu.push([
|
||||
function(){
|
||||
var now = new Date();
|
||||
var agenda = storage.readJSON("android.calendar.json")
|
||||
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
|
||||
.sort((a,b)=>a.timestamp - b.timestamp);
|
||||
|
||||
if(agenda.length <= 0){
|
||||
return ["All done", imgAgenda()]
|
||||
}
|
||||
|
||||
agendaIdx = agendaIdx < 0 ? 0 : agendaIdx;
|
||||
agendaIdx = agendaIdx >= agenda.length ? agendaIdx -1 : agendaIdx;
|
||||
|
||||
var entry = agenda[agendaIdx];
|
||||
var title = entry.title.slice(0,14);
|
||||
var date = new Date(entry.timestamp*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||
|
||||
function dynImgAgenda(){
|
||||
if(isAlarmEnabled(TIMER_AGENDA_IDX) && agendaTimerIdx == agendaIdx){
|
||||
return imgTimer();
|
||||
} else {
|
||||
return imgAgenda();
|
||||
}
|
||||
}
|
||||
|
||||
return [title + "\n" + dateStr, dynImgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), function(){
|
||||
try{
|
||||
var alarm = require('sched')
|
||||
|
||||
// If other time, we disable the old one and enable this one.
|
||||
if(agendaIdx != agendaTimerIdx){
|
||||
agendaTimerIdx = -1;
|
||||
alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
|
||||
}
|
||||
|
||||
// Disable alarm if enabled
|
||||
if(isAlarmEnabled(TIMER_AGENDA_IDX)){
|
||||
agendaTimerIdx = -1;
|
||||
alarm.setAlarm(TIMER_AGENDA_IDX, undefined);
|
||||
alarm.reload();
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, set alarm for given event
|
||||
agendaTimerIdx = agendaIdx;
|
||||
alarm.setAlarm(TIMER_AGENDA_IDX, {
|
||||
msg: title,
|
||||
timer : parseInt((date - now)),
|
||||
});
|
||||
alarm.reload();
|
||||
} catch(ex){ }
|
||||
}]
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* WEATHER MENU
|
||||
*/
|
||||
if(storage.readJSON('weather.json') !== undefined){
|
||||
menu.push([
|
||||
function(){ return [ "Weather", imgWeather() ] },
|
||||
function(){ return [ getWeather().temp, imgTemperature() ] },
|
||||
function(){ return [ getWeather().hum, imgHumidity() ] },
|
||||
function(){ return [ getWeather().wind, imgWind() ] },
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HOME ASSISTANT MENU
|
||||
*/
|
||||
try{
|
||||
var triggers = require("ha.lib.js").getTriggers();
|
||||
var haMenu = [
|
||||
function(){ return [ "Home", imgHomeAssistant() ] },
|
||||
];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
haMenu.push(function(){
|
||||
return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){
|
||||
var ha = require("ha.lib.js");
|
||||
ha.sendTrigger("TRIGGER_BW");
|
||||
ha.sendTrigger(trigger.trigger);
|
||||
}]
|
||||
});
|
||||
})
|
||||
menu.push(haMenu);
|
||||
} catch(ex){
|
||||
// If HomeAssistant is not installed, we hide this item
|
||||
}
|
||||
|
||||
|
||||
function getMenuEntry(){
|
||||
// In case the user removes HomeAssistant entries, showInfo
|
||||
// could be larger than infoArray.length...
|
||||
settings.menuPosX = settings.menuPosX % menu.length;
|
||||
settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length;
|
||||
var menuEntry = menu[settings.menuPosX][settings.menuPosY]();
|
||||
|
||||
if(menuEntry[0] == null){
|
||||
return menuEntry;
|
||||
}
|
||||
|
||||
// For the first entry we always convert it into a callback function
|
||||
// such that the menu is compatible with async functions such as
|
||||
// measuring the pressure, altitude or sending http requests...
|
||||
if(typeof menuEntry[0] !== 'function'){
|
||||
var value = menuEntry[0];
|
||||
menuEntry[0] = function(callbackFun){
|
||||
callbackFun(String(value), settings.menuPosX, settings.menuPosY);
|
||||
}
|
||||
}
|
||||
return menuEntry;
|
||||
}
|
||||
|
||||
|
||||
/************
|
||||
* Helper
|
||||
*/
|
||||
function isFullscreen(){
|
||||
var s = settings.screen.toLowerCase();
|
||||
if(s == "dynamic"){
|
||||
return Bangle.isLocked()
|
||||
} else {
|
||||
return s == "full"
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
|
||||
function getWeather(){
|
||||
var weatherJson;
|
||||
|
||||
try {
|
||||
weatherJson = storage.readJSON('weather.json');
|
||||
var weather = weatherJson.weather;
|
||||
|
||||
// Temperature
|
||||
weather.temp = locale.temp(weather.temp-273.15);
|
||||
|
||||
// Humidity
|
||||
weather.hum = weather.hum + "%";
|
||||
|
||||
// Wind
|
||||
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
weather.wind = Math.round(wind[1]) + "kph";
|
||||
|
||||
return weather
|
||||
|
||||
} catch(ex) {
|
||||
// Return default
|
||||
}
|
||||
|
||||
return {
|
||||
temp: " ? ",
|
||||
hum: " ? ",
|
||||
txt: " ? ",
|
||||
wind: " ? ",
|
||||
wdir: " ? ",
|
||||
wrose: " ? "
|
||||
};
|
||||
}
|
||||
|
||||
// From https://weeknumber.com/how-to/javascript
|
||||
function weekOfYear() {
|
||||
var date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
||||
// January 4 is always in week 1.
|
||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
||||
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
|
||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
|
||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
var date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
||||
// January 4 is always in week 1.
|
||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
||||
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
|
||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
|
||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
}
|
||||
|
||||
|
||||
function isAlarmEnabled(idx){
|
||||
try{
|
||||
var alarm = require('sched');
|
||||
var alarmObj = alarm.getAlarm(idx);
|
||||
if(alarmObj===undefined || !alarmObj.on){
|
||||
return false;
|
||||
// Load menu
|
||||
var menu = clock_info.load();
|
||||
menu = menu.concat(bwItems);
|
||||
|
||||
|
||||
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
|
||||
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 0;
|
||||
}
|
||||
|
||||
// Set draw functions for each item
|
||||
menu.forEach((menuItm, x) => {
|
||||
menuItm.items.forEach((item, y) => {
|
||||
function drawItem() {
|
||||
// For the clock, we have a special case, as we don't wanna redraw
|
||||
// immediately when something changes. Instead, we update data each minute
|
||||
// to save some battery etc. Therefore, we hide (and disable the listener)
|
||||
// immedeately after redraw...
|
||||
item.hide();
|
||||
|
||||
// After drawing the item, we enable inputs again...
|
||||
lock_input = false;
|
||||
|
||||
var info = item.get();
|
||||
drawMenuItem(info.text, info.img);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch(ex){ }
|
||||
return false;
|
||||
}
|
||||
item.on('redraw', drawItem);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function getAlarmMinutes(idx){
|
||||
if(!isAlarmEnabled(idx)){
|
||||
return -1;
|
||||
function canRunMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
var alarm = require('sched');
|
||||
var alarmObj = alarm.getAlarm(idx);
|
||||
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
return item.run !== undefined;
|
||||
}
|
||||
|
||||
|
||||
function increaseAlarm(idx){
|
||||
function runMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
try{
|
||||
var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0;
|
||||
var alarm = require('sched');
|
||||
alarm.setAlarm(idx, {
|
||||
timer : (minutes+5)*60*1000,
|
||||
});
|
||||
alarm.reload();
|
||||
} catch(ex){ }
|
||||
}
|
||||
|
||||
|
||||
function decreaseAlarm(idx){
|
||||
try{
|
||||
var minutes = getAlarmMinutes(idx);
|
||||
minutes -= 5;
|
||||
|
||||
var alarm = require('sched')
|
||||
alarm.setAlarm(idx, undefined);
|
||||
|
||||
if(minutes > 0){
|
||||
alarm.setAlarm(idx, {
|
||||
timer : minutes*60*1000,
|
||||
});
|
||||
var ret = item.run();
|
||||
if(ret){
|
||||
Bangle.buzz(300, 0.6);
|
||||
}
|
||||
|
||||
alarm.reload();
|
||||
} catch(ex){ }
|
||||
}
|
||||
|
||||
|
||||
function measureAltitude(callbackFun){
|
||||
var oldX = settings.menuPosX;
|
||||
var oldY = settings.menuPosY;
|
||||
try{
|
||||
Bangle.getPressure().then(data=>{
|
||||
if(data && data.altitude && data.altitude > -100){
|
||||
callbackFun(Math.round(data.altitude) + "m", oldX, oldY);
|
||||
} else {
|
||||
callbackFun("???", oldX, oldY);
|
||||
}
|
||||
});
|
||||
}catch(ex){
|
||||
callbackFun("err", oldX, oldY);
|
||||
} catch (ex) {
|
||||
// Simply ignore it...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************
|
||||
* DRAW
|
||||
/************************************************
|
||||
* Draw
|
||||
*/
|
||||
function draw() {
|
||||
// Queue draw again
|
||||
|
@ -521,7 +183,7 @@ function draw() {
|
|||
|
||||
// Draw clock
|
||||
drawDate();
|
||||
drawTime();
|
||||
drawMenuAndTime();
|
||||
drawLock();
|
||||
drawWidgets();
|
||||
}
|
||||
|
@ -529,12 +191,12 @@ function draw() {
|
|||
|
||||
function drawDate(){
|
||||
// Draw background
|
||||
var y = H/5*2;
|
||||
g.reset().clearRect(0,0,W,W);
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
g.reset().clearRect(0,0,W,y);
|
||||
|
||||
// Draw date
|
||||
y = parseInt(y/2)+4;
|
||||
y += isFullscreen() ? 0 : 13;
|
||||
y += isFullscreen() ? 0 : 8;
|
||||
var date = new Date();
|
||||
var dateStr = date.getDate();
|
||||
dateStr = ("0" + dateStr).substr(-2);
|
||||
|
@ -557,11 +219,8 @@ function drawDate(){
|
|||
}
|
||||
|
||||
|
||||
function drawTime(){
|
||||
function drawTime(y, smallText){
|
||||
// Draw background
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(0,y,W,H);
|
||||
var date = new Date();
|
||||
|
||||
// Draw time
|
||||
|
@ -577,56 +236,65 @@ function drawTime(){
|
|||
// Set y coordinates correctly
|
||||
y += parseInt((H - y)/2) + 5;
|
||||
|
||||
var menuEntry = getMenuEntry();
|
||||
var menuTextFun = menuEntry[0];
|
||||
var menuImg = menuEntry[1];
|
||||
var printImgLeft = settings.menuPosY != 0;
|
||||
|
||||
// Show large or small time depending on info entry
|
||||
if(menuTextFun == null){
|
||||
g.setLargeFont();
|
||||
g.drawString(timeStr, W/2, y);
|
||||
return;
|
||||
} else {
|
||||
if(smallText){
|
||||
y -= 15;
|
||||
g.setMediumFont();
|
||||
g.drawString(timeStr, W/2, y);
|
||||
} else {
|
||||
g.setLargeFont();
|
||||
}
|
||||
g.drawString(timeStr, W/2, y);
|
||||
}
|
||||
|
||||
// Async set the menu (could be that some data is async fetched)
|
||||
menuTextFun((menuText, oldX, oldY) => {
|
||||
function drawMenuItem(text, image){
|
||||
// First clear the time region
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
|
||||
// We display the text IFF the user did not change the menu
|
||||
if(settings.menuPosX != oldX || settings.menuPosY != oldY){
|
||||
return;
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(0,y,W,H);
|
||||
|
||||
// As its a callback, we have to ensure that the color
|
||||
// font etc. is still correct...
|
||||
g.setColor(g.theme.bg);
|
||||
// Draw menu text
|
||||
var hasText = (text != null && text != "");
|
||||
if(hasText){
|
||||
g.setFontAlign(0,0);
|
||||
y += 35;
|
||||
|
||||
if(menuText.split('\n').length > 1){
|
||||
// For multiline text we show an even smaller font...
|
||||
text = String(text);
|
||||
if(text.split('\n').length > 1){
|
||||
g.setMiniFont();
|
||||
} else {
|
||||
g.setSmallFont();
|
||||
}
|
||||
|
||||
var imgWidth = 0;
|
||||
if(menuImg){
|
||||
imgWidth = 24.0;
|
||||
var strWidth = g.stringWidth(menuText);
|
||||
var scale = imgWidth / menuImg.width;
|
||||
g.drawImage(
|
||||
menuImg,
|
||||
W/2 + (printImgLeft ? -strWidth/2-4 : strWidth/2+4) - parseInt(imgWidth/2),
|
||||
y - parseInt(imgWidth/2),
|
||||
{ scale: scale }
|
||||
);
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var strWidth = g.stringWidth(text);
|
||||
g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
|
||||
g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
|
||||
|
||||
if(image != null){
|
||||
var scale = imgWidth / image.width;
|
||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
|
||||
}
|
||||
g.drawString(menuText, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
|
||||
});
|
||||
}
|
||||
|
||||
// Draw time
|
||||
drawTime(y, hasText);
|
||||
}
|
||||
|
||||
|
||||
function drawMenuAndTime(){
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
|
||||
// The first entry is the overview...
|
||||
if(settings.menuPosY == 0){
|
||||
drawMenuItem(menuEntry.name, menuEntry.img);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw item if needed
|
||||
lock_input = true;
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
item.show();
|
||||
}
|
||||
|
||||
|
||||
|
@ -647,9 +315,19 @@ function drawWidgets(){
|
|||
}
|
||||
|
||||
|
||||
function isFullscreen(){
|
||||
var s = settings.screen.toLowerCase();
|
||||
if(s == "dynamic"){
|
||||
return Bangle.isLocked()
|
||||
} else {
|
||||
return s == "full"
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw timeout
|
||||
|
||||
|
||||
/************************************************
|
||||
* Listener
|
||||
*/
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
@ -692,7 +370,7 @@ Bangle.on('charging',function(charging) {
|
|||
drawTimeout = undefined;
|
||||
|
||||
// Jump to battery
|
||||
settings.menuPosX = 1;
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 1;
|
||||
draw();
|
||||
});
|
||||
|
@ -710,17 +388,15 @@ Bangle.on('touch', function(btn, e){
|
|||
var is_right = e.x > right && !is_upper && !is_lower;
|
||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
||||
|
||||
if(lock_input){
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_lower){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length;
|
||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
||||
|
||||
// Handle custom menu entry function
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 2){
|
||||
menuEntry[2]();
|
||||
}
|
||||
|
||||
drawTime();
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_upper){
|
||||
|
@ -730,53 +406,29 @@ Bangle.on('touch', function(btn, e){
|
|||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = settings.menuPosY-1;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].length-1 : settings.menuPosY;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
||||
|
||||
// Handle custom menu entry function
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 3){
|
||||
menuEntry[3]();
|
||||
}
|
||||
|
||||
drawTime();
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_right){
|
||||
// A bit hacky but we ensure that always the first agenda entry is shown...
|
||||
agendaIdx = 0;
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
||||
settings.menuPosY = 0;
|
||||
drawTime();
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_left){
|
||||
// A bit hacky but we ensure that always the first agenda entry is shown...
|
||||
agendaIdx = 0;
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = 0;
|
||||
settings.menuPosX = settings.menuPosX-1;
|
||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
||||
drawTime();
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_center){
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 4 && menuEntry[4] != null){
|
||||
Bangle.buzz(80, 0.6).then(()=>{
|
||||
try{
|
||||
menuEntry[4]();
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(80, 0.6);
|
||||
drawTime();
|
||||
}, 250);
|
||||
} catch(ex){
|
||||
// In case it fails, we simply ignore it.
|
||||
}
|
||||
}
|
||||
);
|
||||
if(canRunMenuItem()){
|
||||
runMenuItem();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -791,9 +443,10 @@ E.on("kill", function(){
|
|||
});
|
||||
|
||||
|
||||
/*
|
||||
* Draw clock the first time
|
||||
/************************************************
|
||||
* Startup Clock
|
||||
*/
|
||||
|
||||
// The upper part is inverse i.e. light if dark and dark if light theme
|
||||
// is enabled. In order to draw the widgets correctly, we invert the
|
||||
// dark/light theme as well as the colors.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.21",
|
||||
"version": "0.23",
|
||||
"description": "A very minimalistic clock to mainly show date and time.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: Release
|
||||
0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
|
||||
0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
|
||||
0.03: Added clkinfo for clocks.
|
||||
0.04: Feedback if clkinfo run is called.
|
|
@ -0,0 +1,26 @@
|
|||
(function() {
|
||||
var ha = require("ha.lib.js");
|
||||
var triggers = ha.getTriggers();
|
||||
|
||||
var haItems = {
|
||||
name: "Home",
|
||||
img: atob("GBiBAf/////////n///D//+B//8A//48T/wkD/gkD/A8D+AYB8AYA4eZ4QyZMOyZN+fb5+D/B+B+B+A8B+AYB+AYB+AYB+AYB+A8Bw=="),
|
||||
items: []
|
||||
};
|
||||
|
||||
triggers.forEach((trigger, i) => {
|
||||
haItems.items.push({
|
||||
name: "haTrigger-" + i,
|
||||
get: () => ({ text: trigger.display, img: trigger.getIcon()}),
|
||||
show: function() { haItems.items[i].emit("redraw"); },
|
||||
hide: function () {},
|
||||
run: function() {
|
||||
ha.sendTrigger("TRIGGER_BW");
|
||||
ha.sendTrigger(trigger.trigger);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return haItems;
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ha",
|
||||
"name": "HomeAssistant",
|
||||
"version": "0.02",
|
||||
"version": "0.04",
|
||||
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||
"icon": "ha.png",
|
||||
"type": "app",
|
||||
|
@ -20,6 +20,7 @@
|
|||
"storage": [
|
||||
{"name":"ha.app.js","url":"ha.app.js"},
|
||||
{"name":"ha.lib.js","url":"ha.lib.js"},
|
||||
{"name":"ha.clkinfo.js","url":"ha.clkinfo.js"},
|
||||
{"name":"ha.img","url":"ha.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,3 +14,4 @@
|
|||
Add /*LANG*/ tags for internationalisation
|
||||
0.13: Add fullscreen mode
|
||||
0.14: Use default Bangle formatter for booleans
|
||||
0.15: Support for unload and quick return to the clock on 2v16
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
var s = require("Storage");
|
||||
var scaleval = 1;
|
||||
var vectorval = 20;
|
||||
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
let s = require("Storage");
|
||||
let scaleval = 1;
|
||||
let vectorval = 20;
|
||||
let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
fullscreen: false
|
||||
|
@ -20,7 +21,7 @@ if ("font" in settings){
|
|||
scaleval = (font.split("x")[1])/20;
|
||||
}
|
||||
}
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type));
|
||||
let apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || !app.type));
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
|
@ -69,18 +70,30 @@ E.showScroller({
|
|||
}
|
||||
});
|
||||
|
||||
function returnToClock() {
|
||||
// unload everything manually
|
||||
// ... or we could just call `load();` but it will be slower
|
||||
Bangle.setUI(); // remove scroller's handling
|
||||
if (lockTimeout) clearTimeout(lockTimeout);
|
||||
Bangle.removeListener("lock", lockHandler);
|
||||
// now load the default clock - just call .bootcde as this has the code already
|
||||
setTimeout(eval,0,s.read(".bootcde"));
|
||||
}
|
||||
|
||||
// on bangle.js 2, the screen is used for navigating, so the single button goes back
|
||||
// on bangle.js 1, the buttons are used for navigating
|
||||
if (process.env.HWVERSION==2) {
|
||||
setWatch(_=>load(), BTN1, {edge:"falling"});
|
||||
setWatch(returnToClock, BTN1, {edge:"falling"});
|
||||
}
|
||||
|
||||
// 10s of inactivity goes back to clock
|
||||
Bangle.setLocked(false); // unlock initially
|
||||
var lockTimeout;
|
||||
Bangle.on("lock", locked => {
|
||||
let lockTimeout;
|
||||
function lockHandler(locked) {
|
||||
if (lockTimeout) clearTimeout(lockTimeout);
|
||||
lockTimeout = undefined;
|
||||
if (locked)
|
||||
lockTimeout = setTimeout(_=>load(), 10000);
|
||||
});
|
||||
lockTimeout = setTimeout(returnToClock, 10000);
|
||||
}
|
||||
Bangle.on("lock", lockHandler);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "launch",
|
||||
"name": "Launcher",
|
||||
"shortName": "Launcher",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -66,4 +66,8 @@
|
|||
0.50: Add `getMessages` and `status` functions to library
|
||||
Option to disable auto-open of messages
|
||||
Option to make message icons monochrome (not colored)
|
||||
messages widget buzz now returns a promise
|
||||
messages widget buzz now returns a promise
|
||||
0.51: Emit "message events"
|
||||
Setting to hide widget
|
||||
Add custom event handlers to prevent default app form loading
|
||||
Move WIDGETS.messages.buzz() to require("messages").buzz()
|
|
@ -25,7 +25,7 @@ it starts getting clipped.
|
|||
* `Auto-Open Music` - Should the app automatically open when the phone starts playing music?
|
||||
* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app?
|
||||
* `Flash Icon` - Toggle flashing of the widget icon.
|
||||
* `Widget messages` - The maximum amount of message icons to show on the widget.
|
||||
* `Widget messages` - The maximum amount of message icons to show on the widget, or `Hide` the widget completely.
|
||||
|
||||
## New Messages
|
||||
|
||||
|
@ -56,6 +56,24 @@ _2. What the notify icon looks like (it's touchable on Bangle.js2!)_
|
|||

|
||||
|
||||
|
||||
## Events (for app/widget developers)
|
||||
|
||||
When a new message arrives, a `"message"` event is emitted, you can listen for
|
||||
it like this:
|
||||
|
||||
```js
|
||||
myMessageListener = Bangle.on("message", (type, message)=>{
|
||||
if (message.handled) return; // another app already handled this message
|
||||
// <type> is one of "text", "call", "alarm", "map", "music", or "clearAll"
|
||||
if (type === "clearAll") return; // not a message
|
||||
// see `messages/lib.js` for possible <message> formats
|
||||
// message.t could be "add", "modify" or "remove"
|
||||
E.showMessage(`${message.title}\n${message.body}`, `${message.t} ${type} message`);
|
||||
// You can prevent the default `message` app from loading by setting `message.handled = true`:
|
||||
message.handled = true;
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
|
|
|
@ -54,8 +54,7 @@ var onMessagesModified = function(msg) {
|
|||
// TODO: if new, show this new one
|
||||
if (msg && msg.id!=="music" && msg.new && active!="map" &&
|
||||
!((require('Storage').readJSON('setting.json', 1) || {}).quiet)) {
|
||||
if (WIDGETS["messages"]) WIDGETS["messages"].buzz(msg.src);
|
||||
else Bangle.buzz();
|
||||
require("messages").buzz(msg.src);
|
||||
}
|
||||
if (msg && msg.id=="music") {
|
||||
if (msg.state && msg.state!="play") openMusic = false; // no longer playing music to go back to
|
||||
|
@ -356,13 +355,13 @@ function checkMessages(options) {
|
|||
// If we have a new message, show it
|
||||
if (options.showMsgIfUnread && newMessages.length) {
|
||||
showMessage(newMessages[0].id);
|
||||
// buzz after showMessage, so beingbusy during layout doesn't affect the buzz pattern
|
||||
// buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
|
||||
if (global.BUZZ_ON_NEW_MESSAGE) {
|
||||
// this is set if we entered the messages app by loading `messages.new.js`
|
||||
// ... but only buzz the first time we view a new message
|
||||
global.BUZZ_ON_NEW_MESSAGE = false;
|
||||
// messages.buzz respects quiet mode - no need to check here
|
||||
WIDGETS.messages.buzz(newMessages[0].src);
|
||||
require("messages").buzz(newMessages[0].src);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -8,15 +8,11 @@ function openMusic() {
|
|||
/* Push a new message onto messages queue, event is:
|
||||
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool}
|
||||
{t:"add",id:int, id:"music", state, artist, track, etc} // add new
|
||||
{t:"remove-",id:int} // remove
|
||||
{t:"remove",id:int} // remove
|
||||
{t:"modify",id:int, title:string} // modified
|
||||
*/
|
||||
exports.pushMessage = function(event) {
|
||||
var messages, inApp = "undefined"!=typeof MESSAGES;
|
||||
if (inApp)
|
||||
messages = MESSAGES; // we're in an app that has already loaded messages
|
||||
else // no app - load messages
|
||||
messages = require("Storage").readJSON("messages.json",1)||[];
|
||||
var messages = exports.getMessages();
|
||||
// now modify/delete as appropriate
|
||||
var mIdx = messages.findIndex(m=>m.id==event.id);
|
||||
if (event.t=="remove") {
|
||||
|
@ -35,69 +31,81 @@ exports.pushMessage = function(event) {
|
|||
else Object.assign(messages[mIdx], event);
|
||||
if (event.id=="music" && messages[mIdx].state=="play") {
|
||||
messages[mIdx].new = true; // new track, or playback (re)started
|
||||
type = 'music';
|
||||
}
|
||||
}
|
||||
require("Storage").writeJSON("messages.json",messages);
|
||||
var message = mIdx<0 ? {id:event.id, t:'remove'} : messages[mIdx];
|
||||
// if in app, process immediately
|
||||
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
|
||||
if ("undefined"!=typeof MESSAGES) return onMessagesModified(message);
|
||||
// emit message event
|
||||
var type = 'text';
|
||||
if (["call", "music", "map"].includes(message.id)) type = message.id;
|
||||
if (message.src && message.src.toLowerCase().startsWith("alarm")) type = "alarm";
|
||||
Bangle.emit("message", type, message);
|
||||
// update the widget icons shown
|
||||
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages,true);
|
||||
var handleMessage = () => {
|
||||
// if no new messages now, make sure we don't load the messages app
|
||||
if (event.t=="remove" && exports.messageTimeout && !messages.some(m=>m.new)) {
|
||||
clearTimeout(exports.messageTimeout);
|
||||
delete exports.messageTimeout;
|
||||
}
|
||||
// ok, saved now
|
||||
if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) {
|
||||
// just load the app to display music: no buzzing
|
||||
load("messages.app.js");
|
||||
} else if (event.t!="add") {
|
||||
// we only care if it's new
|
||||
return;
|
||||
} else if(event.new == false) {
|
||||
return;
|
||||
}
|
||||
// otherwise load messages/show widget
|
||||
var loadMessages = Bangle.CLOCK || event.important;
|
||||
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||
var appSettings = require('Storage').readJSON('messages.settings.json',1)||{};
|
||||
var unlockWatch = appSettings.unlockWatch;
|
||||
// don't auto-open messages in quiet mode if quietNoAutOpn is true
|
||||
if((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
|
||||
loadMessages = false;
|
||||
delete appSettings;
|
||||
// after a delay load the app, to ensure we have all the messages
|
||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||
exports.messageTimeout = setTimeout(function() {
|
||||
exports.messageTimeout = undefined;
|
||||
// if we're in a clock or it's important, go straight to messages app
|
||||
if (loadMessages){
|
||||
if(!quiet && unlockWatch){
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDPower(1); // turn screen on
|
||||
}
|
||||
// we will buzz when we enter the messages app
|
||||
return load("messages.new.js");
|
||||
if (event.t=="remove" && exports.messageTimeout && !messages.some(m => m.new)) {
|
||||
clearTimeout(exports.messageTimeout);
|
||||
delete exports.messageTimeout;
|
||||
}
|
||||
if (!quiet && (!global.WIDGETS || !WIDGETS.messages)) return Bangle.buzz(); // no widgets - just buzz once to let someone know
|
||||
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages);
|
||||
}, 500);
|
||||
// ok, saved now
|
||||
if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) {
|
||||
// just load the app to display music: no buzzing
|
||||
load("messages.app.js");
|
||||
} else if (event.t!="add") {
|
||||
// we only care if it's new
|
||||
return;
|
||||
} else if (event.new==false) {
|
||||
return;
|
||||
}
|
||||
// otherwise load messages/show widget
|
||||
var loadMessages = Bangle.CLOCK || event.important;
|
||||
var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
|
||||
var appSettings = require('Storage').readJSON('messages.settings.json', 1) || {};
|
||||
var unlockWatch = appSettings.unlockWatch;
|
||||
// don't auto-open messages in quiet mode if quietNoAutOpn is true
|
||||
if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
|
||||
loadMessages = false;
|
||||
delete appSettings;
|
||||
// after a delay load the app, to ensure we have all the messages
|
||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||
exports.messageTimeout = setTimeout(function() {
|
||||
exports.messageTimeout = undefined;
|
||||
// if we're in a clock or it's important, go straight to messages app
|
||||
if (loadMessages) {
|
||||
if (!quiet && unlockWatch) {
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDPower(1); // turn screen on
|
||||
}
|
||||
// we will buzz when we enter the messages app
|
||||
return load("messages.new.js");
|
||||
}
|
||||
if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages);
|
||||
exports.buzz(message.src);
|
||||
}, 500);
|
||||
};
|
||||
setTimeout(()=>{
|
||||
if (!message.handled) handleMessage();
|
||||
},0);
|
||||
}
|
||||
/// Remove all messages
|
||||
exports.clearAll = function(event) {
|
||||
var messages, inApp = "undefined"!=typeof MESSAGES;
|
||||
if (inApp) {
|
||||
exports.clearAll = function() {
|
||||
if ("undefined"!= typeof MESSAGES) { // we're in a messages app, clear that as well
|
||||
MESSAGES = [];
|
||||
messages = MESSAGES; // we're in an app that has already loaded messages
|
||||
} else // no app - empty messages
|
||||
messages = [];
|
||||
// Save all messages
|
||||
require("Storage").writeJSON("messages.json",messages);
|
||||
// update app if in app
|
||||
if (inApp) return onMessagesModified();
|
||||
}
|
||||
// Clear all messages
|
||||
require("Storage").writeJSON("messages.json", []);
|
||||
// if we have a widget, update it
|
||||
if (global.WIDGETS && WIDGETS.messages)
|
||||
WIDGETS.messages.update(messages);
|
||||
WIDGETS.messages.update([]);
|
||||
// let message listeners know
|
||||
Bangle.emit("message", "clearAll", {}); // guarantee listeners an object as `message`
|
||||
// clearAll cannot be marked as "handled"
|
||||
// update app if in app
|
||||
if ("function"== typeof onMessagesModified) onMessagesModified();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,6 +134,45 @@ exports.getMessages = function() {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start buzzing for new message
|
||||
* @param {string} msgSrc Message src to buzz for
|
||||
* @return {Promise} Resolves when initial buzz finishes (there might be repeat buzzes later)
|
||||
*/
|
||||
exports.buzz = function(msgSrc) {
|
||||
exports.stopBuzz(); // cancel any previous buzz timeouts
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode
|
||||
|
||||
var pattern;
|
||||
if (msgSrc && msgSrc.toLowerCase() === "phone") {
|
||||
// special vibration pattern for incoming calls
|
||||
pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls;
|
||||
} else {
|
||||
pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate;
|
||||
}
|
||||
if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here
|
||||
if (!pattern) return Promise.resolve();
|
||||
|
||||
var repeat = (require('Storage').readJSON("messages.settings.json", true) || {}).repeat;
|
||||
if (repeat===undefined) repeat=4; // repeat may be zero
|
||||
if (repeat) {
|
||||
exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000);
|
||||
var vibrateTimeout = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateTimeout;
|
||||
if (vibrateTimeout===undefined) vibrateTimeout=60;
|
||||
if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopTimeout, vibrateTimeout*1000);
|
||||
}
|
||||
return require("buzz").pattern(pattern);
|
||||
};
|
||||
/**
|
||||
* Stop buzzing
|
||||
*/
|
||||
exports.stopBuzz = function() {
|
||||
if (exports.buzzTimeout) clearTimeout(exports.buzzTimeout);
|
||||
delete exports.buzzTimeout;
|
||||
if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
|
||||
delete exports.stopTimeout;
|
||||
};
|
||||
|
||||
exports.getMessageImage = function(msg) {
|
||||
/*
|
||||
* icons should be 24x24px or less with 1bpp colors and 'Transparency to Color'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.50",
|
||||
"version": "0.51",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -73,7 +73,8 @@
|
|||
},
|
||||
/*LANG*/'Widget messages': {
|
||||
value:0|settings().maxMessages,
|
||||
min: 1, max: 5,
|
||||
min: 0, max: 5,
|
||||
format: v => v ? v :/*LANG*/"Hide",
|
||||
onchange: v => updateSetting("maxMessages", v)
|
||||
},
|
||||
/*LANG*/'Icon color mode': {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(() => {
|
||||
if ((require('Storage').readJSON("messages.settings.json", true) || {}).maxMessages===0) return;
|
||||
|
||||
function filterMessages(msgs) {
|
||||
return msgs.filter(msg => msg.new && msg.id != "music")
|
||||
|
@ -14,15 +15,14 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
|
|||
}
|
||||
Bangle.removeListener('touch', this.touch);
|
||||
if (!this.width) return;
|
||||
var c = (Date.now()-this.t)/1000;
|
||||
let settings = Object.assign({flash:true, maxMessages:3, repeat:4, vibrateTimeout:60},require('Storage').readJSON("messages.settings.json", true) || {});
|
||||
let settings = Object.assign({flash:true, maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {});
|
||||
if (recall !== true || settings.flash) {
|
||||
var msgsShown = E.clip(this.msgs.length, 0, settings.maxMessages);
|
||||
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23);
|
||||
for(let i = 0;i < msgsShown;i++) {
|
||||
const msg = this.msgs[i];
|
||||
const colors = [g.theme.bg, g.setColor(require("messages").getMessageImageCol(msg)).getColor()];
|
||||
if (settings.flash && (c&1)) {
|
||||
if (settings.flash && ((Date.now()/1000)&1)) {
|
||||
if (colors[1] == g.theme.fg) {
|
||||
colors.reverse();
|
||||
} else {
|
||||
|
@ -35,38 +35,13 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
|
|||
this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/});
|
||||
}
|
||||
}
|
||||
if (c<settings.vibrateTimeout && // not going on too long...
|
||||
(settings.repeat || c<1) && // repeated, or no repeat and first attempt
|
||||
(Date.now()-this.l)>settings.repeat*1000) { // the period between vibrations
|
||||
this.l = Date.now();
|
||||
WIDGETS["messages"].buzz(); // buzz every 4 seconds
|
||||
}
|
||||
WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000);
|
||||
if (process.env.HWVERSION>1) Bangle.on('touch', this.touch);
|
||||
},update:function(rawMsgs, quiet) {
|
||||
},update:function(rawMsgs) {
|
||||
const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {});
|
||||
this.msgs = filterMessages(rawMsgs);
|
||||
if (this.msgs.length === 0) {
|
||||
delete this.t;
|
||||
delete this.l;
|
||||
} else {
|
||||
this.t=Date.now(); // first time
|
||||
this.l=Date.now()-10000; // last buzz
|
||||
if (quiet) this.t -= 500000; // if quiet, set last time in the past so there is no buzzing
|
||||
}
|
||||
this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages);
|
||||
Bangle.drawWidgets();
|
||||
},buzz:function(msgSrc) { // return a promise
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode
|
||||
var pattern;
|
||||
if (msgSrc != undefined && msgSrc.toLowerCase() == "phone") {
|
||||
// special vibration pattern for incoming calls
|
||||
pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls;
|
||||
} else {
|
||||
pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate;
|
||||
}
|
||||
if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here
|
||||
return require("buzz").pattern(pattern);
|
||||
},touch:function(b,c) {
|
||||
var w=WIDGETS["messages"];
|
||||
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+24) return;
|
||||
|
@ -74,8 +49,7 @@ WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) {
|
|||
}};
|
||||
|
||||
/* We might have returned here if we were in the Messages app for a
|
||||
message but then the watch was never viewed. In that case we don't
|
||||
want to buzz but should still show that there are unread messages. */
|
||||
message but then the watch was never viewed. */
|
||||
if (global.MESSAGES===undefined)
|
||||
WIDGETS["messages"].update(require("messages").getMessages(), true);
|
||||
WIDGETS["messages"].update(require("messages").getMessages());
|
||||
})();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkA/4AC+c/AoYAR+QTTj4DBkYXS+YUB+cSl4YS+P/mUiL4RLQ+chiUziPyAAIXPmJEC+Ui+UhO6QABj8iVKocEACUTC60j+YXWPoZHTkcyC6sibYaPpC63ziXzkYXUkXyO6nykMykIXUl8xmcykQ2BSZ4XBkTXB+LdBMgPyDRnzj8vmf/AIMyDgMjAoIALiQSCVYI0CD4QAL+MTIILFCI4TJOmMRkUzn40CGQRLBMRipBiIABkR2DTAIAQmURF4KcBZScxn5qBACgWWbwUhMCT7CmU/WQQAR+awBF6jdBVggXSPCwXXVAPyJCkimUieKinBI6sxAQIvUeAMyMQIXT+MjI6iNC+RIT+bAB+cSYiZdBMQMzSCkf+ZIUGAMiYKsjkTbVkMSl4A=="))
|
|
@ -0,0 +1,104 @@
|
|||
var randomNumber;
|
||||
var guessNumber = 1;
|
||||
|
||||
function mathRandomInt(a, b) {
|
||||
if (a > b) {
|
||||
// Swap a and b to ensure a is smaller.
|
||||
var c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
return Math.floor(Math.random() * (b - a + 1) + a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe this function...
|
||||
*/
|
||||
function game() {
|
||||
|
||||
g.drawString('',0,20,true);
|
||||
E.showMenu(numMenu);
|
||||
console.log(randomNumber);
|
||||
}
|
||||
|
||||
var numMenu = {
|
||||
"" : {
|
||||
"title" : "Number Chaser",
|
||||
},
|
||||
"Guess Number" : {
|
||||
value : guessNumber,
|
||||
min:1,max:100,step:1,
|
||||
onchange : v => { guessNumber=v; }
|
||||
},
|
||||
"OK" : function () {
|
||||
g.clear();
|
||||
if (guessNumber == randomNumber) {
|
||||
//if guess is correct
|
||||
g.setFont("Vector",13);g.setFontAlign(-1,-1);
|
||||
status = "You won! ";
|
||||
gameOver();
|
||||
} else {
|
||||
//if guess is incorrect
|
||||
g.setFont("Vector",13);g.setFontAlign(-1,-1);
|
||||
if (guessNumber > randomNumber) {
|
||||
//Decreases number if guess is greater
|
||||
randomNumber = randomNumber - 1;
|
||||
status = "Too high!";
|
||||
} else if (guessNumber < randomNumber) {
|
||||
//Increases number if guess is lower
|
||||
status = "Too low!";
|
||||
randomNumber = randomNumber + 1;
|
||||
}
|
||||
if (randomNumber < 0 || randomNumber > 100) {
|
||||
//You lose when the number is out of the 1 to 100 range
|
||||
g.setFont("Vector",13);g.setFontAlign(-1,-1);
|
||||
g.drawString('You have lost\nNumber is out\nof range.',10,10,true);
|
||||
status = "You lost!";
|
||||
} else {
|
||||
g.drawString(status+"\nTry again!",10,10);
|
||||
Bangle.on('tap', function() {
|
||||
delay(3000).then(() => game());
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function gameOver()
|
||||
{
|
||||
E.showPrompt(status+'Play again?',{title:""+'Number Chaser'}).then(function(a) {
|
||||
if (a) {
|
||||
randomNumber = mathRandomInt(1, 100);
|
||||
game();
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function delay(time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
function instructions()
|
||||
{
|
||||
g.setFont("Vector",13);g.setFontAlign(-1,-1);
|
||||
g.drawString('Guess the number\nbetween 1 and 100.\nGuess too high, it\ndecreases by 1.\nToo low, it increases\nby 1.\nIf the number\ngoes below 0 or\nabove 100, it\nis out of range\nand you have\nlost.',10,10,true);
|
||||
randomNumber = mathRandomInt(1, 100);
|
||||
delay(10000).then(() => game());
|
||||
}
|
||||
|
||||
|
||||
g.clear();
|
||||
E.showPrompt('Do you need instructions?',{title:""+'Number Chaser'}).then(function(a)
|
||||
{ if (a) {
|
||||
instructions();
|
||||
} else
|
||||
{
|
||||
randomNumber = mathRandomInt(1, 100);
|
||||
game();
|
||||
}
|
||||
}
|
||||
);
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "numberchaser",
|
||||
"name": "Number Chaser",
|
||||
"shortName":"Number Chaser",
|
||||
"version":"0.01",
|
||||
"description": "A number guessing game, but the number goes up or down based on if you're guessing too high or too low.",
|
||||
"icon": "numberchaser.png",
|
||||
"tags": "game,fun",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"numberchaser.app.js","url":"app.js"},
|
||||
{"name":"numberchaser.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -66,20 +66,20 @@ TODO:
|
|||
However some don't allow cross-origin use */
|
||||
//var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast, TOO SLOW
|
||||
//var TILELAYER = 'http://a.tile.stamen.com/toner/{z}/{x}/{y}.png'; // black and white
|
||||
var TILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var TILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
|
||||
// Create map and try and set the location to where the browser thinks we are
|
||||
var map = L.map('map').locate({setView: true, maxZoom: 16, enableHighAccuracy:true});
|
||||
// Tiles used for Bangle.js itself
|
||||
var bangleTileLayer = L.tileLayer(TILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
// Tiles used for the may the user sees (faster)
|
||||
var previewTileLayer = L.tileLayer(PREVIEWTILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
// Could optionally overlay trails: https://wiki.openstreetmap.org/wiki/Tiles
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Power Saver
|
||||
|
||||
Save your watch's battery power by halting foreground app execution while the screen is off.
|
||||
|
||||
## Features
|
||||
- Stops foreground app processes
|
||||
- Background processes still run
|
||||
- Clears screen
|
||||
- Decreases accelerometer polls
|
||||
- Foreground app is returned to when screen is turned back on (app state is not preserved)
|
||||
|
||||
## Controls
|
||||
- Automatically activates when screen times out, timing can be adjusted using normal timeout settings
|
||||
- Deactivates when screen is turned back on
|
||||
|
||||
## Warnings
|
||||
- Due to an Espruino bug, this does not take affect immediately when installed. Switch apps for these features to take affect.
|
||||
- This is not compatible with apps that need to run in the foreground even while the screen is off, such as most stopwatch apps and some health trackers.
|
||||
- If you check your watch super often (like multiple times per minute), this may end of costing you more power than it saves since the app you are using will have to restart everytime you check it.
|
||||
|
||||
## Requests
|
||||
|
||||
[Contact information is on my website](https://kyleplo.com/#contact)
|
||||
|
||||
## Creator
|
||||
|
||||
[kyleplo](https://kyleplo.com)
|
|
@ -0,0 +1,15 @@
|
|||
var Storage = Storage || require("Storage");
|
||||
Bangle.on("lock", locked => {
|
||||
if(locked){
|
||||
g.clear().reset();
|
||||
Bangle.setLCDBrightness(0);
|
||||
Bangle.setPollInterval(1000);
|
||||
load("powersave.screen.js");
|
||||
}else{
|
||||
load(Storage.read("resumeaftersleep") || JSON.parse(Storage.read("setting.json")).clock);
|
||||
}
|
||||
});
|
||||
E.on("init", () => {
|
||||
if(__FILE__ && __FILE__ !== "powersave.screen.js")
|
||||
Storage.write("resumeaftersleep", __FILE__);
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "powersave",
|
||||
"name": "Power Save",
|
||||
"version": "0.01",
|
||||
"description": "Halts foreground app execution while screen is off while still allowing background processes.",
|
||||
"readme": "README.md",
|
||||
"icon": "powersave.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"powersave.boot.js","url":"boot.js"},
|
||||
{"name":"powersave.screen.js","url":"boot.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,7 @@
|
|||
var Storage = Storage || require("Storage");
|
||||
g.clear();
|
||||
Bangle.setLCDBrightness(0);
|
||||
Bangle.setPollInterval(1000);
|
||||
if(!Bangle.isLocked()){
|
||||
load(Storage.read("resumeaftersleep") || JSON.parse(Storage.read("setting.json")).clock);
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
0.01: First version for upload
|
||||
0.02: Fix for leftover date artifacts on display.
|
||||
|
|
|
@ -57,6 +57,10 @@ function draw() {
|
|||
var minutes = ("0"+m).substr(-2);
|
||||
g.reset();
|
||||
|
||||
// If midnight clear display to remove day / date artifacts
|
||||
if (h == 0 && m == 0)
|
||||
g.clear();
|
||||
|
||||
// Convert to 12hr time mode
|
||||
if (is12Hour && h > 12) {
|
||||
h = h - 12;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"shortName":"Slash",
|
||||
"icon": "slash.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Slash Watch based on Pebble watch face by Nikki.",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Release
|
||||
0.02: Rewrite with new interface
|
||||
0.02: Rewrite with new interface
|
||||
0.03: Added clock infos to expose timer functionality to clocks.
|
|
@ -0,0 +1,97 @@
|
|||
(function() {
|
||||
const TIMER_IDX = "smpltmr";
|
||||
|
||||
function isAlarmEnabled(){
|
||||
try{
|
||||
var alarm = require('sched');
|
||||
var alarmObj = alarm.getAlarm(TIMER_IDX);
|
||||
if(alarmObj===undefined || !alarmObj.on){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch(ex){ }
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAlarmMinutes(){
|
||||
if(!isAlarmEnabled()){
|
||||
return -1;
|
||||
}
|
||||
|
||||
var alarm = require('sched');
|
||||
var alarmObj = alarm.getAlarm(TIMER_IDX);
|
||||
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
|
||||
}
|
||||
|
||||
function getAlarmMinutesText(){
|
||||
var min = getAlarmMinutes();
|
||||
if(min < 0){
|
||||
return "OFF";
|
||||
}
|
||||
|
||||
return "T-" + String(min);
|
||||
}
|
||||
|
||||
function increaseAlarm(t){
|
||||
try{
|
||||
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
|
||||
var alarm = require('sched')
|
||||
alarm.setAlarm(TIMER_IDX, {
|
||||
timer : (minutes+t)*60*1000,
|
||||
});
|
||||
alarm.reload();
|
||||
} catch(ex){ }
|
||||
}
|
||||
|
||||
function decreaseAlarm(t){
|
||||
try{
|
||||
var minutes = getAlarmMinutes();
|
||||
minutes -= t;
|
||||
|
||||
var alarm = require('sched')
|
||||
alarm.setAlarm(TIMER_IDX, undefined);
|
||||
|
||||
if(minutes > 0){
|
||||
alarm.setAlarm(TIMER_IDX, {
|
||||
timer : minutes*60*1000,
|
||||
});
|
||||
}
|
||||
|
||||
alarm.reload();
|
||||
} catch(ex){ }
|
||||
}
|
||||
|
||||
var img = atob("GBiBAeAAB+AAB/v/3/v/3/v/3/v/3/v/n/n/H/z+P/48//85//+b//+b//8p//4E//yCP/kBH/oAn/oAX/oAX/oAX/oAX+AAB+AABw==")
|
||||
var smpltmrItems = {
|
||||
name: "Timer",
|
||||
img: img,
|
||||
items: [
|
||||
{
|
||||
name: "Timer",
|
||||
get: () => ({ text: getAlarmMinutesText() + (isAlarmEnabled() ? " min" : ""), img: null}),
|
||||
show: function() { smpltmrItems.items[0].emit("redraw"); },
|
||||
hide: function () {},
|
||||
run: function() { }
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
var offsets = [+1,+5,-1,-5];
|
||||
offsets.forEach((o, i) => {
|
||||
smpltmrItems.items = smpltmrItems.items.concat({
|
||||
name: String(o),
|
||||
get: () => ({ text: getAlarmMinutesText() + " (" + (o > 0 ? "+" : "") + o + ")", img: null}),
|
||||
show: function() { smpltmrItems.items[i+1].emit("redraw"); },
|
||||
hide: function () {},
|
||||
run: function() {
|
||||
if(o > 0) increaseAlarm(o);
|
||||
else decreaseAlarm(Math.abs(o));
|
||||
this.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return smpltmrItems;
|
||||
})
|
|
@ -2,7 +2,7 @@
|
|||
"id": "smpltmr",
|
||||
"name": "Simple Timer",
|
||||
"shortName": "Simple Timer",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A very simple app to start a timer.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,timer",
|
||||
|
@ -12,6 +12,7 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"smpltmr.app.js","url":"app.js"},
|
||||
{"name":"smpltmr.clkinfo.js","url":"clkinfo.js"},
|
||||
{"name":"smpltmr.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Inital release.
|
|
@ -0,0 +1,9 @@
|
|||
This first release seems servicable in testing so far.
|
||||
|
||||
To get the standard menu scrolling back, just remove this app from your Bangle.
|
||||
|
||||
TODO:
|
||||
- Maybe have how much of "trailing space" there are after the last entry should be dynamic in size, now it's always 8 pixels which corresponds to if there are a widget field and a menu title present.
|
||||
- I want to change the size of menu entries to be a little bigger vertically.
|
||||
|
||||
<a target="_blank" href="https://icons8.com/icon/18714/drag-list-down">Drag List Down</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
After Width: | Height: | Size: 279 B |
|
@ -0,0 +1,100 @@
|
|||
E.showScroller = (function(options) {
|
||||
/* options = {
|
||||
h = height
|
||||
c = # of items
|
||||
scroll = initial scroll position
|
||||
scrollMin = minimum scroll amount (can be negative)
|
||||
draw = function(idx, rect)
|
||||
select = function(idx)
|
||||
}
|
||||
|
||||
returns {
|
||||
draw = draw all
|
||||
drawItem(idx) = draw specific item
|
||||
}
|
||||
*/
|
||||
if (!options) return Bangle.setUI(); // remove existing handlers
|
||||
|
||||
var menuShowing = false;
|
||||
var R = Bangle.appRect;
|
||||
var Y = Bangle.appRect.y;
|
||||
var n = Math.ceil(R.h/options.h);
|
||||
var menuScrollMin = 0|options.scrollMin;
|
||||
var menuScrollMax = options.h*options.c - R.h;
|
||||
if (menuScrollMax<menuScrollMin) menuScrollMax=menuScrollMin;
|
||||
|
||||
function idxToY(i) {
|
||||
return i*options.h + R.y - rScroll;
|
||||
}
|
||||
function YtoIdx(y) {
|
||||
return Math.floor((y + rScroll - R.y)/options.h);
|
||||
}
|
||||
|
||||
var s = {
|
||||
scroll : E.clip(0|options.scroll,menuScrollMin,menuScrollMax),
|
||||
draw : () => {
|
||||
g.reset().clearRect(R.x,R.y,R.x2,R.y2);
|
||||
g.setClipRect(R.x,R.y,R.x2,R.y2);
|
||||
var a = YtoIdx(R.y);
|
||||
var b = Math.min(YtoIdx(R.y2),options.c-1);
|
||||
for (var i=a;i<=b;i++)
|
||||
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h});
|
||||
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
}, drawItem : i => {
|
||||
var y = idxToY(i);
|
||||
g.reset().setClipRect(R.x,y,R.x2,y+options.h);
|
||||
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
|
||||
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
}};
|
||||
var rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither)
|
||||
s.draw(); // draw the full scroller
|
||||
g.flip(); // force an update now to make this snappier
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
back : options.back,
|
||||
swipe : (hor,ver)=>{
|
||||
pixels = 120;
|
||||
var dy = ver*pixels;
|
||||
if (s.scroll - dy > menuScrollMax)
|
||||
dy = s.scroll - menuScrollMax-8; // Makes it so the last 'page' has the same position as previous pages. This should be done dynamically (change the static 8 to be a variable) so the offset is correct even when no widget field or title field is present.
|
||||
if (s.scroll - dy < menuScrollMin)
|
||||
dy = s.scroll - menuScrollMin;
|
||||
s.scroll -= dy;
|
||||
var oldScroll = rScroll;
|
||||
rScroll = s.scroll &~1;
|
||||
dy = oldScroll-rScroll;
|
||||
if (!dy || options.c<=3) return; //options.c<=3 should maybe be dynamic, so 3 would be replaced by a variable dependent on R=Bangle.appRect. It's here so we don't try to scroll if all entries fit in the app rectangle.
|
||||
g.reset().setClipRect(R.x,R.y,R.x2,R.y2);
|
||||
g.scroll(0,dy);
|
||||
var d = ver*pixels;
|
||||
if (d < 0) {
|
||||
g.setClipRect(R.x,R.y2-(1-d),R.x2,R.y2);
|
||||
let i = YtoIdx(R.y2-(1-d));
|
||||
let y = idxToY(i);
|
||||
//print(i, options.c, options.c-i); //debugging info
|
||||
while (y < R.y2 - (options.h*((options.c-i)<=0)) ) { //- (options.h*((options.c-i)<=0)) makes sure we don't go beyond the menu entries in the menu object "options". This has to do with "dy = s.scroll - menuScrollMax-8" above.
|
||||
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
|
||||
i++;
|
||||
y += options.h;
|
||||
}
|
||||
} else { // d>0
|
||||
g.setClipRect(R.x,R.y,R.x2,R.y+d);
|
||||
let i = YtoIdx(R.y+d);
|
||||
let y = idxToY(i);
|
||||
//print(i, options.c, options.c-i); //debugging info
|
||||
while (y > R.y-options.h) {
|
||||
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
|
||||
y -= options.h;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
}, touch : (_,e)=>{
|
||||
if (e.y<R.y-4) return;
|
||||
var i = YtoIdx(e.y);
|
||||
if ((menuScrollMin<0 || i>=0) && i<options.c)
|
||||
options.select(i);
|
||||
}
|
||||
});
|
||||
return s;
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "swscroll",
|
||||
"name": "Swipe menus",
|
||||
"version": "0.01",
|
||||
"description": "Replace built in E.showScroller to act on swipe instead of drag. Navigate menus in discrete steps instead of a continuous motion.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"swscroll.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New Widget!
|
||||
0.02: Fix calling null on draw
|
||||
0.03: Only vibrate during work
|
||||
0.04: Convert to boot code
|
|
@ -0,0 +1,17 @@
|
|||
# Twenties
|
||||
|
||||
Follow the [20-20-20 rule](https://www.aoa.org/AOA/Images/Patients/Eye%20Conditions/20-20-20-rule.pdf) with discrete reminders. Your Bangle will buzz every 20 minutes for you to look away from your screen, and then buzz 20 seconds later to look back. Additionally, alternate between standing and sitting every 20 minutes to be standing for [more than 30 minutes](https://uwaterloo.ca/kinesiology-health-sciences/how-long-should-you-stand-rather-sit-your-work-station) per hour.
|
||||
|
||||
## Usage
|
||||
|
||||
Download this app and it will automatically run in the background.
|
||||
|
||||
## Features
|
||||
|
||||
Vibrates to remind you to stand up and look away for healthy living.
|
||||
|
||||
Only vibrates during work days and hours.
|
||||
|
||||
## Creator
|
||||
|
||||
[@splch](https://github.com/splch/)
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,19 @@
|
|||
(() => {
|
||||
const move = 20 * 60 * 1000; // 20 minutes
|
||||
const look = 20 * 1000; // 20 seconds
|
||||
|
||||
const buzz = _ => {
|
||||
const date = new Date();
|
||||
const day = date.getDay();
|
||||
const hour = date.getHours();
|
||||
// buzz at work
|
||||
if (day >= 1 && day <= 5 &&
|
||||
hour >= 8 && hour <= 17) {
|
||||
Bangle.buzz().then(_ => {
|
||||
setTimeout(Bangle.buzz, look);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setInterval(buzz, move); // buzz to stand / sit
|
||||
})();
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"id": "widtwenties",
|
||||
"id": "twenties",
|
||||
"name": "Twenties",
|
||||
"shortName": "twenties",
|
||||
"version": "0.02",
|
||||
"version": "0.04",
|
||||
"description": "Buzzes every 20m to stand / sit and look 20ft away for 20s.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,tools",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "alarm,tool",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [{ "name": "widtwenties.wid.js", "url": "widget.js" }]
|
||||
"storage": [{ "name": "twenties.boot.js", "url": "boot.js" }]
|
||||
}
|
|
@ -13,3 +13,4 @@
|
|||
0.14: Use weather condition code for icon selection
|
||||
0.15: Fix widget icon
|
||||
0.16: Don't mark app as clock
|
||||
0.17: Added clkinfo for clocks.
|
|
@ -0,0 +1,43 @@
|
|||
(function() {
|
||||
var weather = {
|
||||
temp: "?",
|
||||
hum: "?",
|
||||
wind: "?",
|
||||
};
|
||||
|
||||
var weatherJson = storage.readJSON('weather.json');
|
||||
if(weatherJson !== undefined && weatherJson.weather !== undefined){
|
||||
weather = weatherJson.weather;
|
||||
weather.temp = locale.temp(weather.temp-273.15);
|
||||
weather.hum = weather.hum + "%";
|
||||
weather.wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
weather.wind = Math.round(weather.wind[1]) + "kph";
|
||||
}
|
||||
|
||||
var weatherItems = {
|
||||
name: "Weather",
|
||||
img: atob("GBiBAf+///u5//n7//8f/9wHP8gDf/gB//AB/7AH/5AcP/AQH/DwD/uAD84AD/4AA/wAAfAAAfAAAfAAAfgAA/////+bP/+zf/+zfw=="),
|
||||
items: [
|
||||
{
|
||||
name: "temperature",
|
||||
get: () => ({ text: weather.temp, img: atob("GBiBAf/D//+B//8Y//88//88//88//88//88//8k//8k//8k//8k//8k//8k//4kf/5mf/zDP/yBP/yBP/zDP/5mf/48f/8A///D/w==")}),
|
||||
show: function() { weatherItems.items[0].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
{
|
||||
name: "humidity",
|
||||
get: () => ({ text: weather.hum, img: atob("GBiBAf/7///z///x///g///g///Af//Af/3Af/nA//jg//B/v/B/H+A/H8A+D8AeB8AcB4AYA8AYA8AYA+A4A/B4A//4A//8B///Dw==")}),
|
||||
show: function() { weatherItems.items[1].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
{
|
||||
name: "wind",
|
||||
get: () => ({ text: weather.wind, img: atob("GBiBAf4f//wP//nn//Pn//Pzg//nAf/meIAOfAAefP///P//+fAAAfAAB////////wAAP4AAH///z///z//nz//nz//zj//wH//8Pw==")}),
|
||||
show: function() { weatherItems.items[2].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
return weatherItems;
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "weather",
|
||||
"name": "Weather",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
@ -13,7 +13,8 @@
|
|||
{"name":"weather.wid.js","url":"widget.js"},
|
||||
{"name":"weather","url":"lib.js"},
|
||||
{"name":"weather.img","url":"icon.js","evaluate":true},
|
||||
{"name":"weather.settings.js","url":"settings.js"}
|
||||
{"name":"weather.settings.js","url":"settings.js"},
|
||||
{"name":"weather.clkinfo.js","url":"clkinfo.js"}
|
||||
],
|
||||
"data": [{"name":"weather.json"}]
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{ "id": "widagps",
|
||||
"name": "AGPS Widget",
|
||||
"name": "AGPS Widget (automatic download)",
|
||||
"shortName":"AGPS Widget",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"version":"0.01",
|
||||
"description": "Load AGPS data in the background **using Gadgetbridge**",
|
||||
"description": "Once installed, this widget allows your Bangle.js 2 to load AGPS data in the background **via Gadgetbridge on an Android phone** so it is always up to date. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
|
||||
"readme": "README.md",
|
||||
"tags": "widget,agps,http",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
0.12: Prevent repeated execution of `draw()` from the current app.
|
||||
0.13: Added "connection restored" notification. Fixed restoring of the watchface.
|
||||
0.14: Added configuration option
|
||||
0.15: Added option to hide widget when connected
|
||||
0.15: Added option to hide widget when connected
|
||||
0.16: Simplify code, add option to disable displaying a message
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "widbt_notify",
|
||||
"name": "Bluetooth Widget with Notification",
|
||||
"version": "0.15",
|
||||
"description": "Show the current Bluetooth connection status in the top right of the watch. Optional buzz and/or and hide if disconnected",
|
||||
"version": "0.16",
|
||||
"description": "Show the current Bluetooth connection status with some optional features: show message, buzz on connect/loss, hide always/if connected.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget,bluetooth",
|
||||
|
|
|
@ -1,69 +1,57 @@
|
|||
(function(back) {
|
||||
var FILE = "widbt_notify.json";
|
||||
|
||||
var filename = "widbt_notify.json";
|
||||
|
||||
// set Storage and load settings
|
||||
var storage = require("Storage");
|
||||
var settings = Object.assign({
|
||||
secondsOnUnlock: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
showWidget: true,
|
||||
buzzOnConnect: true,
|
||||
buzzOnLoss: true,
|
||||
hideConnected: true,
|
||||
showMessage: true,
|
||||
nextBuzz: 30000
|
||||
}, storage.readJSON(filename, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
// setup boolean menu entries
|
||||
function boolEntry(key) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
value: settings[key],
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
// change the value of key
|
||||
settings[key] = v;
|
||||
// write to storage
|
||||
storage.writeJSON(filename, settings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
var mainmenu = {
|
||||
// setup menu
|
||||
var menu = {
|
||||
"": {
|
||||
"title": "Bluetooth Widget WN"
|
||||
},
|
||||
"< Back": () => back(),
|
||||
"Show Widget": {
|
||||
value: (settings.showWidget !== undefined ? settings.showWidget : true),
|
||||
"Show Widget": boolEntry("showWidget"),
|
||||
"Buzz on connect": boolEntry("buzzOnConnect"),
|
||||
"Buzz on loss": boolEntry("buzzOnLoss"),
|
||||
"Hide connected": boolEntry("hideConnected"),
|
||||
"Show Message": boolEntry("showMessage"),
|
||||
"Next Buzz": {
|
||||
value: settings.nextBuzz,
|
||||
step: 1000,
|
||||
min: 1000,
|
||||
max: 120000,
|
||||
wrap: true,
|
||||
format: v => (v / 1000) + "s",
|
||||
onchange: v => {
|
||||
settings.showWidget = v;
|
||||
writeSettings();
|
||||
settings.nextBuzz = v;
|
||||
storage.writeJSON(filename, settings);
|
||||
}
|
||||
},
|
||||
"Buzz on Connect": {
|
||||
value: (settings.buzzOnConnect !== undefined ? settings.buzzOnConnect : true),
|
||||
onchange: v => {
|
||||
settings.buzzOnConnect = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Buzz on loss": {
|
||||
value: (settings.buzzOnLoss !== undefined ? settings.buzzOnLoss : true),
|
||||
onchange: v => {
|
||||
settings.buzzOnLoss = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Hide connected": {
|
||||
value: (settings.hideConnected !== undefined ? settings.hideConnected : false),
|
||||
onchange: v => {
|
||||
settings.hideConnected = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
// draw main menu
|
||||
E.showMenu(menu);
|
||||
|
||||
});
|
||||
})
|
|
@ -1,110 +1,90 @@
|
|||
WIDGETS.bluetooth_notify = {
|
||||
(function() {
|
||||
// load settings
|
||||
var settings = Object.assign({
|
||||
showWidget: true,
|
||||
buzzOnConnect: true,
|
||||
buzzOnLoss: true,
|
||||
hideConnected: true,
|
||||
showMessage: true,
|
||||
nextBuzz: 30000
|
||||
}, require("Storage").readJSON("widbt_notify.json", true) || {});
|
||||
|
||||
// setup widget with to hide if connected and option set
|
||||
var widWidth = settings.hideConnected && NRF.getSecurityStatus().connected ? 0 : 15;
|
||||
|
||||
// write widget with loaded settings
|
||||
WIDGETS.bluetooth_notify = Object.assign(settings, {
|
||||
|
||||
// set area and width
|
||||
area: "tr",
|
||||
width: 15,
|
||||
width: widWidth,
|
||||
|
||||
// setup warning status
|
||||
warningEnabled: 1,
|
||||
|
||||
// ------------ Settings -------- very lame - need to improve
|
||||
readshowWidget: function() {
|
||||
var showWidget;
|
||||
const SETTINGSFILE = "widbt_notify.json";
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
showWidget = def(settings.showWidget, true);
|
||||
return showWidget;
|
||||
},
|
||||
|
||||
readBuzzOnConnect: function() {
|
||||
var buzzOnConnect;
|
||||
const SETTINGSFILE = "widbt_notify.json";
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
buzzOnConnect = def(settings.buzzOnConnect, true);
|
||||
return buzzOnConnect;
|
||||
},
|
||||
|
||||
readBuzzOnLoss: function() {
|
||||
var buzzOnLoss;
|
||||
const SETTINGSFILE = "widbt_notify.json";
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
buzzOnLoss = def(settings.buzzOnLoss, true);
|
||||
return buzzOnLoss;
|
||||
},
|
||||
|
||||
readHideConnected: function() {
|
||||
var hideConnected;
|
||||
const SETTINGSFILE = "widbt_notify.json";
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
hideConnected = def(settings.hideConnected, true);
|
||||
return hideConnected;
|
||||
},
|
||||
|
||||
|
||||
// ------------ Settings --------
|
||||
|
||||
draw: function() {
|
||||
if (WIDGETS.bluetooth_notify.readshowWidget()){
|
||||
g.reset();
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
if (!WIDGETS.bluetooth_notify.readHideConnected()) {
|
||||
g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
|
||||
}
|
||||
} else {
|
||||
// g.setColor(g.theme.dark ? "#666" : "#999");
|
||||
g.setColor("#f00"); // red is easier to distinguish from blue
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
|
||||
}
|
||||
if (this.showWidget) {
|
||||
g.reset();
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
if (!this.hideConnected) {
|
||||
g.setColor((g.getBPP() > 8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
|
||||
}
|
||||
} else {
|
||||
// g.setColor(g.theme.dark ? "#666" : "#999");
|
||||
g.setColor("#f00"); // red is easier to distinguish from blue
|
||||
g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), 2 + this.x, 2 + this.y);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
redrawCurrentApp: function(){
|
||||
if(typeof(draw)=='function'){
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}else{
|
||||
load(); // fallback. This might reset some variables
|
||||
|
||||
redrawCurrentApp: function() {
|
||||
if (typeof(draw) == 'function') {
|
||||
g.clear();
|
||||
draw();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
} else {
|
||||
load(); // fallback. This might reset some variables
|
||||
}
|
||||
},
|
||||
|
||||
onNRF: function(connect) {
|
||||
// setup widget with and reload widgets to show/hide if hideConnected is enabled
|
||||
if (this.hideConnected) {
|
||||
this.width = connect ? 0 : 15; // ensures correct redraw
|
||||
Bangle.drawWidgets();
|
||||
} else {
|
||||
// redraw widget
|
||||
this.draw();
|
||||
}
|
||||
|
||||
if (this.warningEnabled) {
|
||||
if (this.showMessage) {
|
||||
E.showMessage( /*LANG*/ 'Connection\n' + (connect ? /*LANG*/ 'restored.' : /*LANG*/ 'lost.'), 'Bluetooth');
|
||||
setTimeout(() => {
|
||||
WIDGETS.bluetooth_notify.redrawCurrentApp();
|
||||
}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
|
||||
}
|
||||
},
|
||||
|
||||
connect: function() {
|
||||
|
||||
if(WIDGETS.bluetooth_notify.warningEnabled == 1){
|
||||
E.showMessage(/*LANG*/'Connection\nrestored.', 'Bluetooth');
|
||||
setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
|
||||
|
||||
WIDGETS.bluetooth_notify.warningEnabled = 0;
|
||||
setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds.
|
||||
|
||||
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||
if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnConnect()){
|
||||
Bangle.buzz(700, 1); // buzz on connection resume
|
||||
}
|
||||
}
|
||||
WIDGETS.bluetooth_notify.draw();
|
||||
this.warningEnabled = 0;
|
||||
setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', this.nextBuzz); // don't buzz for the next X seconds.
|
||||
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
if(WIDGETS.bluetooth_notify.warningEnabled == 1){
|
||||
E.showMessage(/*LANG*/ 'Connection\nlost.', 'Bluetooth');
|
||||
setTimeout(()=>{WIDGETS.bluetooth_notify.redrawCurrentApp();}, 3000); // clear message - this will reload the widget, resetting 'warningEnabled'.
|
||||
|
||||
WIDGETS.bluetooth_notify.warningEnabled = 0;
|
||||
setTimeout('WIDGETS.bluetooth_notify.warningEnabled = 1;', 30000); // don't buzz for the next 30 seconds.
|
||||
|
||||
var quiet = (require('Storage').readJSON('setting.json',1)||{}).quiet;
|
||||
if(!quiet && WIDGETS.bluetooth_notify.readBuzzOnLoss()){
|
||||
Bangle.buzz(700, 1); // buzz on connection loss
|
||||
}
|
||||
}
|
||||
|
||||
WIDGETS.bluetooth_notify.draw();
|
||||
var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet;
|
||||
if (!quiet && (connect ? this.buzzOnConnect : this.buzzOnLoss)) {
|
||||
Bangle.buzz(700, 1); // buzz on connection resume or loss
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NRF.on('connect', WIDGETS.bluetooth_notify.connect);
|
||||
NRF.on('disconnect', WIDGETS.bluetooth_notify.disconnect);
|
||||
});
|
||||
|
||||
// clear variables
|
||||
settings = undefined;
|
||||
widWidth = undefined;
|
||||
|
||||
// setup bluetooth connection events
|
||||
NRF.on('connect', (addr) => WIDGETS.bluetooth_notify.onNRF(addr));
|
||||
NRF.on('disconnect', () => WIDGETS.bluetooth_notify.onNRF());
|
||||
|
||||
})()
|
|
@ -1,2 +0,0 @@
|
|||
0.01: New Widget!
|
||||
0.02: Fix calling null on draw
|
|
@ -1,15 +0,0 @@
|
|||
# Twenties
|
||||
|
||||
Follow the [20-20-20 rule](https://www.aoa.org/AOA/Images/Patients/Eye%20Conditions/20-20-20-rule.pdf) with discrete reminders. Your BangleJS will buzz every 20 minutes for you to look away from your screen, and then buzz 20 seconds later to look back. Additionally, alternate between standing and sitting every 20 minutes to be standing for [more than 30 minutes](https://uwaterloo.ca/kinesiology-health-sciences/how-long-should-you-stand-rather-sit-your-work-station) per hour.
|
||||
|
||||
## Usage
|
||||
|
||||
Download this widget and, as long as your watch-face supports widgets, it will automatically run in the background.
|
||||
|
||||
## Features
|
||||
|
||||
Vibrate to remind you to stand up and look away for healthy living.
|
||||
|
||||
## Creator
|
||||
|
||||
[@splch](https://github.com/splch/)
|
|
@ -1,24 +0,0 @@
|
|||
// WIDGETS = {}; // <-- for development only
|
||||
|
||||
/* run widgets in their own function scope so
|
||||
they don't interfere with currently-running apps */
|
||||
(() => {
|
||||
const move = 20 * 60 * 1000; // 20 minutes
|
||||
const look = 20 * 1000; // 20 seconds
|
||||
|
||||
buzz = _ => {
|
||||
Bangle.buzz().then(_ => {
|
||||
setTimeout(Bangle.buzz, look);
|
||||
});
|
||||
};
|
||||
|
||||
// add widget
|
||||
WIDGETS.twenties = {
|
||||
buzz: buzz,
|
||||
draw: _ => { return null; },
|
||||
};
|
||||
|
||||
setInterval(WIDGETS.twenties.buzz, move); // buzz to stand / sit
|
||||
})();
|
||||
|
||||
// Bangle.drawWidgets(); // <-- for development only
|
|
@ -2,9 +2,14 @@ var exports = {};
|
|||
/* Module that allows for loading of clock 'info' displays
|
||||
that can be scrolled through on the clock face.
|
||||
|
||||
`load()` returns an array of menu items:
|
||||
`load()` returns an array of menu objects, where each object contains a list of menu items:
|
||||
* 'name' : text to display and identify menu object (e.g. weather)
|
||||
* 'img' : a 24x24px image
|
||||
* 'items' : menu items such as temperature, humidity, wind etc.
|
||||
|
||||
* 'item.name' : friendly name
|
||||
Note that each item is an object with:
|
||||
|
||||
* 'item.name' : friendly name to identify an item (e.g. temperature)
|
||||
* 'item.get' : function that resolves with:
|
||||
{
|
||||
'text' : the text to display for this item
|
||||
|
@ -13,21 +18,25 @@ that can be scrolled through on the clock face.
|
|||
* 'item.show' : called when item should be shown. Enables updates. Call BEFORE 'get'
|
||||
* 'item.hide' : called when item should be hidden. Disables updates.
|
||||
* .on('redraw', ...) : event that is called when 'get' should be called again (only after 'item.show')
|
||||
* 'item.run' : (optional) called if the info screen is tapped - can perform some action
|
||||
* 'item.run' : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user.
|
||||
|
||||
See the bottom of this file for example usage...
|
||||
|
||||
example.clkinfo.js :
|
||||
|
||||
(function() {
|
||||
return [
|
||||
{ name : "Example",
|
||||
get : () => ({ text : "Bangle.js",
|
||||
img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
|
||||
show : () => {},
|
||||
hide : () => {}
|
||||
}
|
||||
];
|
||||
return {
|
||||
name: "Bangle",
|
||||
img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
|
||||
items: [
|
||||
{ name : "Item1",
|
||||
get : () => ({ text : "TextOfItem1",
|
||||
img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
|
||||
show : () => {},
|
||||
hide : () => {}
|
||||
}
|
||||
]
|
||||
};
|
||||
}) // must not have a semi-colon!
|
||||
|
||||
*/
|
||||
|
@ -38,65 +47,72 @@ exports.load = function() {
|
|||
var hrm = "--";
|
||||
var alt = "--";
|
||||
// callbacks (needed for easy removal of listeners)
|
||||
function batteryUpdateHandler() { items[0].emit("redraw"); }
|
||||
function stepUpdateHandler() { items[1].emit("redraw"); }
|
||||
function hrmUpdateHandler() { items[2].emit("redraw"); }
|
||||
function batteryUpdateHandler() { bangleItems[0].emit("redraw"); }
|
||||
function stepUpdateHandler() { bangleItems[1].emit("redraw"); }
|
||||
function hrmUpdateHandler() { bangleItems[2].emit("redraw"); }
|
||||
function altUpdateHandler() {
|
||||
Bangle.getPressure().then(data=>{
|
||||
if (!data) return;
|
||||
alt = Math.round(data.altitude) + "m";
|
||||
items[3].emit("redraw");
|
||||
bangleItems[3].emit("redraw");
|
||||
});
|
||||
}
|
||||
// actual items
|
||||
var items = [
|
||||
// actual menu
|
||||
var menu = [{
|
||||
name: "Bangle",
|
||||
img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="),
|
||||
items: [
|
||||
{ name : "Battery",
|
||||
get : () => ({
|
||||
text : E.getBattery() + "%",
|
||||
img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() {
|
||||
this.interval = setInterval(()=>this.emit('redraw'), 60000);
|
||||
Bangle.on("charging", batteryUpdateHandler);
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
delete this.interval;
|
||||
Bangle.removeListener("charging", batteryUpdateHandler);
|
||||
},
|
||||
show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); },
|
||||
hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); },
|
||||
},
|
||||
{ name : "Steps", get : () => ({
|
||||
text : Bangle.getHealthStatus("day").steps,
|
||||
img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }),
|
||||
show : function() { Bangle.on("step", stepUpdateHandler); },
|
||||
show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
|
||||
hide : function() { Bangle.removeListener("step", stepUpdateHandler); },
|
||||
},
|
||||
{ name : "HRM", get : () => ({
|
||||
text : Math.round(Bangle.getHealthStatus("last").bpm) + " bpm",
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); },
|
||||
show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); },
|
||||
hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; },
|
||||
}
|
||||
];
|
||||
if (Bangle.getPressure) // Altimeter may not exist
|
||||
items.push({ name : "Altitude", get : () => ({
|
||||
],
|
||||
}];
|
||||
var bangleItems = menu[0].items;
|
||||
|
||||
if (Bangle.getPressure){ // Altimeter may not exist
|
||||
bangleItems.push({ name : "Altitude", get : () => ({
|
||||
text : alt,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
||||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||
});
|
||||
// now load extra data from a third party files
|
||||
}
|
||||
|
||||
// In case there exists already a menu object b with the same name as the next
|
||||
// object a, we append the items. Otherwise we add the new object a to the list.
|
||||
require("Storage").list(/clkinfo.js$/).forEach(fn => {
|
||||
items = items.concat(eval(require("Storage").read(fn))());
|
||||
var a = eval(require("Storage").read(fn))();
|
||||
var b = menu.find(x => x.name === a.name)
|
||||
if(b) b.items = b.items.concat(a.items);
|
||||
else menu = menu.concat(a);
|
||||
});
|
||||
|
||||
// return it all!
|
||||
return items;
|
||||
return menu;
|
||||
};
|
||||
|
||||
|
||||
// Code for testing
|
||||
/*
|
||||
g.clear();
|
||||
var items = exports.load(); // or require("clock_info").load()
|
||||
var menu = exports.load(); // or require("clock_info").load()
|
||||
var itemsFirstMenu = menu[0].items;
|
||||
items.forEach((itm,i) => {
|
||||
var y = i*24;
|
||||
console.log("Starting", itm.name);
|
||||
|
|