1
0
Fork 0

Merge branch 'espruino:master' into gpsnav_b2

master
storm64 2022-09-05 13:14:48 +02:00 committed by GitHub
commit dff2fb5217
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 415 additions and 36 deletions

View File

@ -15,3 +15,5 @@
0.15: Added option for a dynamic mode to show widgets only if unlocked. 0.15: Added option for a dynamic mode to show widgets only if unlocked.
0.16: You can now show your agenda if your calendar is synced with Gadgetbridge. 0.16: You can now show your agenda if your calendar is synced with Gadgetbridge.
0.17: Fix - Step count was no more shown in the menu. 0.17: Fix - Step count was no more shown in the menu.
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"

View File

@ -7,7 +7,7 @@ A very minimalistic clock to mainly show date and time.
The BW clock provides many features and also 3rd party integrations: The BW clock provides many features and also 3rd party integrations:
- Bangle data such as steps, heart rate, battery or charging state. - Bangle data such as steps, heart rate, battery or charging state.
- A timer can be set directly. *Requirement: Scheduler library* - A timer can be set directly. *Requirement: Scheduler library*
- Show agenda entries. *Requirement: Gadgetbridge calendar sync enabled* - 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* - Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app* - HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*

View File

@ -8,10 +8,16 @@ const storage = require('Storage');
* Statics * Statics
*/ */
const SETTINGS_FILE = "bwclk.setting.json"; const SETTINGS_FILE = "bwclk.setting.json";
const TIMER_IDX = "bwclk"; const TIMER_IDX = "bwclk_timer";
const TIMER_AGENDA_IDX = "bwclk_agenda";
const W = g.getWidth(); const W = g.getWidth();
const H = g.getHeight(); const H = g.getHeight();
/************
* Global data
*/
var pressureData;
/************ /************
* Settings * Settings
*/ */
@ -181,6 +187,14 @@ function imgAgenda() {
} }
} }
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: * 2D MENU with entries of:
* [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]] * [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
@ -195,6 +209,7 @@ var menu = [
function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] }, function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
function(){ return [ getSteps(), imgSteps() ] }, function(){ return [ getSteps(), imgSteps() ] },
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] }, function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
function(){ return [ getAltitude(), imgMountain() ]},
] ]
] ]
@ -205,8 +220,8 @@ try{
require('sched'); require('sched');
menu.push([ menu.push([
function(){ function(){
var text = isAlarmEnabled() ? getAlarmMinutes() + " min." : "Timer"; var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer";
return [text, imgTimer(), () => decreaseAlarm(), () => increaseAlarm(), null ] return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ]
}, },
]); ]);
} catch(ex) { } catch(ex) {
@ -219,6 +234,7 @@ try{
* Note that we handle the agenda differently in order to hide old entries... * Note that we handle the agenda differently in order to hide old entries...
*/ */
var agendaIdx = 0; var agendaIdx = 0;
var agendaTimerIdx = 0;
if(storage.readJSON("android.calendar.json") !== undefined){ if(storage.readJSON("android.calendar.json") !== undefined){
function nextAgendaEntry(){ function nextAgendaEntry(){
agendaIdx += 1; agendaIdx += 1;
@ -246,9 +262,43 @@ if(storage.readJSON("android.calendar.json") !== undefined){
var title = entry.title.slice(0,14); var title = entry.title.slice(0,14);
var date = new Date(entry.timestamp*1000); var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,""); var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
dateStr += entry.durationInSeconds < 86400 ? " / " + locale.time(date,1) : ""; dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
return [title + "\n" + dateStr, imgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), null] 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){ }
}]
}, },
]); ]);
} }
@ -330,6 +380,14 @@ function getSteps() {
} }
function getAltitude(){
if(pressureData && pressureData.altitude){
return Math.round(pressureData.altitude) + "m";
}
return "???";
}
function getWeather(){ function getWeather(){
var weatherJson; var weatherJson;
@ -364,10 +422,10 @@ function getWeather(){
} }
function isAlarmEnabled(){ function isAlarmEnabled(idx){
try{ try{
var alarm = require('sched'); var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX); var alarmObj = alarm.getAlarm(idx);
if(alarmObj===undefined || !alarmObj.on){ if(alarmObj===undefined || !alarmObj.on){
return false; return false;
} }
@ -379,22 +437,22 @@ function isAlarmEnabled(){
} }
function getAlarmMinutes(){ function getAlarmMinutes(idx){
if(!isAlarmEnabled()){ if(!isAlarmEnabled(idx)){
return -1; return -1;
} }
var alarm = require('sched'); var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX); var alarmObj = alarm.getAlarm(idx);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000)); return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
} }
function increaseAlarm(){ function increaseAlarm(idx){
try{ try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0;
var alarm = require('sched') var alarm = require('sched');
alarm.setAlarm(TIMER_IDX, { alarm.setAlarm(idx, {
timer : (minutes+5)*60*1000, timer : (minutes+5)*60*1000,
}); });
alarm.reload(); alarm.reload();
@ -402,16 +460,16 @@ function increaseAlarm(){
} }
function decreaseAlarm(){ function decreaseAlarm(idx){
try{ try{
var minutes = getAlarmMinutes(); var minutes = getAlarmMinutes(idx);
minutes -= 5; minutes -= 5;
var alarm = require('sched') var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined); alarm.setAlarm(idx, undefined);
if(minutes > 0){ if(minutes > 0){
alarm.setAlarm(TIMER_IDX, { alarm.setAlarm(idx, {
timer : minutes*60*1000, timer : minutes*60*1000,
}); });
} }
@ -421,6 +479,19 @@ function decreaseAlarm(){
} }
function handleAsyncData(){
try{
if (settings.menuPosX == 1){
Bangle.getPressure().then(data=>{
pressureData = data
});
}
}catch(ex){ }
}
/************ /************
* DRAW * DRAW
*/ */
@ -428,6 +499,9 @@ function draw() {
// Queue draw again // Queue draw again
queueDraw(); queueDraw();
// Now lets measure some data..
handleAsyncData();
// Draw clock // Draw clock
drawDate(); drawDate();
drawTime(); drawTime();
@ -670,6 +744,7 @@ Bangle.on('touch', function(btn, e){
menuEntry[4](); menuEntry[4]();
setTimeout(()=>{ setTimeout(()=>{
Bangle.buzz(80, 0.6); Bangle.buzz(80, 0.6);
drawTime();
}, 250); }, 250);
} catch(ex){ } catch(ex){
// In case it fails, we simply ignore it. // In case it fails, we simply ignore it.
@ -698,6 +773,9 @@ E.on("kill", function(){
// dark/light theme as well as the colors. // dark/light theme as well as the colors.
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear(); g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets and draw clock the first time // Load widgets and draw clock the first time
Bangle.loadWidgets(); Bangle.loadWidgets();
@ -707,6 +785,3 @@ for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
// Draw first time // Draw first time
draw(); draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{ {
"id": "bwclk", "id": "bwclk",
"name": "BW Clock", "name": "BW Clock",
"version": "0.17", "version": "0.19",
"description": "A very minimalistic clock to mainly show date and time.", "description": "A very minimalistic clock to mainly show date and time.",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

2
apps/calclock/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Initial version
0.02: More compact rendering & app icon

9
apps/calclock/README.md Normal file
View File

@ -0,0 +1,9 @@
# Calendar Clock - Your day at a glance
This clock shows a chronological view of your current and future events.
It uses events synced from Gadgetbridge to achieve this.
The current time and date is highlighted in cyan.
## Screenshot
![](screenshot.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgpm5gAB4AVRhgWCAAQWWDCARC/4ACJR4uB54WDAAP8DBotFGIgXLFwv4GAouQC4gwMLooXF/gXJOowXGJBIXBCIgXQxgXLMAIXXMAmIC5OIx4XJhH/wAXIxnIC78IxGIHoIABI44MBC4wQBEQIDB5gXGPAJgEC6IxBC5oABC4wwDa4YTCxAWD5nPDAzvGFYgAB5AXWJBK+GcAq5CGBIuBC5X4GBIJBdoQXB/GIx4CDPJAuEC5JoCDAgWBFwYXJxCBIFwYXKYwoACCwZ3IPQoWIC5YABGYIABCwpHKAQYMBCwwX/C5QAMC8R3/R/4XNhAXNwAXHgGIABgWIAFwA=="))

119
apps/calclock/calclock.js Normal file
View File

@ -0,0 +1,119 @@
var calendar = [];
var current = [];
var next = [];
function updateCalendar() {
calendar = require("Storage").readJSON("android.calendar.json",true)||[];
calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp);
calendar.sort((a,b) => a.timestamp - b.timestamp);
current = calendar.filter(isActive);
next = calendar.filter(e=>!isActive(e));
}
function isActive(event) {
var timeActive = getTime() - event.timestamp;
return timeActive >= 0 && timeActive <= event.durationInSeconds;
}
function zp(str) {
return ("0"+str).substr(-2);
}
function drawEventHeader(event, y) {
g.setFont("Vector", 24);
var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000);
var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes());
g.drawString(timeStr, 5, y);
y += 24;
g.setFont("12x20", 1);
if (isActive(event)) {
g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21);
} else {
var offset = 0-time.getTimezoneOffset()/1440;
var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset);
if(days > 0) {
var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days";
g.drawString(daysStr,15*timeStr.length,y-21);
}
}
return y;
}
function drawEventBody(event, y) {
g.setFont("12x20", 1);
var lines = g.wrapString(event.title, g.getWidth()-10);
if (lines.length > 2) {
lines = lines.slice(0,2);
lines[1] = lines[1].slice(0,-3)+"...";
}
g.drawString(lines.join('\n'), 5, y);
y+=20 * lines.length;
if(event.location) {
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y);
g.drawString(event.location, 20, y);
y+=20;
}
y+=5;
return y;
}
function drawEvent(event, y) {
y = drawEventHeader(event, y);
y = drawEventBody(event, y);
return y;
}
var curEventHeight = 0;
function drawCurrentEvents(y) {
g.setColor("#0ff");
g.clearRect(5, y, g.getWidth() - 5, y + curEventHeight);
curEventHeight = y;
if(current.length === 0) {
y = drawEvent({timestamp: getTime(), durationInSeconds: 100}, y);
} else {
y = drawEventHeader(current[0], y);
for (var e of current) {
y = drawEventBody(e, y);
}
}
curEventHeight = y - curEventHeight;
return y;
}
function drawFutureEvents(y) {
g.setColor(g.theme.fg);
for (var e of next) {
y = drawEvent(e, y);
if(y>g.getHeight())break;
}
return y;
}
function fullRedraw() {
g.clearRect(5,24,g.getWidth()-5,g.getHeight());
updateCalendar();
var y = 30;
y = drawCurrentEvents(y);
drawFutureEvents(y);
}
function redraw() {
g.reset();
if (current.find(e=>!isActive(e)) || next.find(isActive)) {
fullRedraw();
} else {
drawCurrentEvents(30);
}
}
g.clear();
fullRedraw();
var minuteInterval = setInterval(redraw, 60 * 1000);
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setUI("clock");

BIN
apps/calclock/calclock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
apps/calclock/location.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

View File

@ -0,0 +1,17 @@
{
"id": "calclock",
"name": "Calendar Clock",
"shortName": "CalClock",
"version": "0.02",
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
"icon": "calclock.png",
"type": "clock",
"tags": "clock agenda",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"calclock.app.js","url":"calclock.js"},
{"name":"calclock.img","url":"calclock-icon.js","evaluate":true}
],
"screenshots": [{"url":"screenshot.png"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

2
apps/chimer/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Initial Creation
0.02: Fixed some sleep bugs. Added a sleep mode toggle

View File

@ -1,12 +1,13 @@
{ {
"id": "chimer", "id": "chimer",
"name": "Chimer", "name": "Chimer",
"version": "0.01", "version": "0.02",
"description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime", "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime",
"icon": "widget.png", "icon": "widget.png",
"type": "widget", "type": "widget",
"tags": "widget", "tags": "widget",
"supports": ["BANGLEJS", "BANGLEJS2"], "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.MD",
"storage": [ "storage": [
{ "name": "chimer.wid.js", "url": "widget.js" }, { "name": "chimer.wid.js", "url": "widget.js" },
{ "name": "chimer.settings.js", "url": "settings.js" } { "name": "chimer.settings.js", "url": "settings.js" }

View File

@ -36,14 +36,21 @@
Repetition: { Repetition: {
value: settings.repeat, value: settings.repeat,
min: 1, min: 1,
max: 3, max: 5,
format: (v) => v, format: (v) => v,
onchange: (v) => { onchange: (v) => {
settings.repeat = v; settings.repeat = v;
writeSettings(settings); writeSettings(settings);
}, },
}, },
"Start Hour": { "Sleep Mode": {
value: !!settings.sleep,
onchange: (v) => {
settings.sleep = v;
writeSettings(settings);
},
},
"Sleep Start": {
value: settings.start, value: settings.start,
min: 0, min: 0,
max: 23, max: 23,
@ -53,7 +60,7 @@
writeSettings(settings); writeSettings(settings);
}, },
}, },
"End Hour": { "Sleep End": {
value: settings.end, value: settings.end,
min: 0, min: 0,
max: 23, max: 23,
@ -71,6 +78,7 @@
type: 1, type: 1,
freq: 0, freq: 0,
repeat: 1, repeat: 1,
sleep: true,
start: 6, start: 6,
end: 22, end: 22,
}; };

View File

@ -7,6 +7,7 @@
type: 1, type: 1,
freq: 0, freq: 0,
repeat: 1, repeat: 1,
sleep: true,
start: 6, start: 6,
end: 22, end: 22,
}; };
@ -32,7 +33,7 @@
} else { } else {
return; return;
} }
sleep(100); sleep(150);
} }
} }
@ -44,7 +45,11 @@
m = now.getMinutes(), m = now.getMinutes(),
s = now.getSeconds(), s = now.getSeconds(),
ms = now.getMilliseconds(); ms = now.getMilliseconds();
if (h > settings.end || h < settings.start) { if (
(settings.sleep && h > settings.end) ||
(settings.sleep && h >= settings.end && m !== 0) ||
(settings.sleep && h < settings.start)
) {
var mLeft = 60 - m, var mLeft = 60 - m,
sLeft = mLeft * 60 - s, sLeft = mLeft * 60 - s,
msLeft = sLeft * 1000 - ms; msLeft = sLeft * 1000 - ms;
@ -52,7 +57,8 @@
return; return;
} }
if (settings.freq === 1) { if (settings.freq === 1) {
if ((m !== lastMinute && m === 0) || m === 30) chime(); if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30))
chime();
lastHour = h; lastHour = h;
lastMinute = m; lastMinute = m;
// check again in 30 minutes // check again in 30 minutes
@ -70,7 +76,12 @@
} }
setTimeout(check, msLeft); setTimeout(check, msLeft);
} else if (settings.freq === 2) { } else if (settings.freq === 2) {
if ((m !== lastMinute && m === 0) || m === 15 || m === 30 || m === 45) if (
(m !== lastMinute && m === 0) ||
(m !== lastMinute && m === 15) ||
(m !== lastMinute && m === 30) ||
(m !== lastMinute && m === 45)
)
chime(); chime();
lastHour = h; lastHour = h;
lastMinute = m; lastMinute = m;

View File

@ -6,3 +6,4 @@
0.06: Periodically update so the always on display does show current GPS fix 0.06: Periodically update so the always on display does show current GPS fix
0.07: Alternative marker icon (configurable via settings) 0.07: Alternative marker icon (configurable via settings)
0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance. 0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance.
0.09: Do not take widget space if icon is hidden

View File

@ -1,7 +1,7 @@
{ {
"id": "widgps", "id": "widgps",
"name": "GPS Widget", "name": "GPS Widget",
"version": "0.08", "version": "0.09",
"description": "Tiny widget to show the power and fix status of the GPS/GNSS", "description": "Tiny widget to show the power and fix status of the GPS/GNSS",
"icon": "widget.png", "icon": "widget.png",
"type": "widget", "type": "widget",

View File

@ -11,13 +11,14 @@ var interval;
var oldSetGPSPower = Bangle.setGPSPower; var oldSetGPSPower = Bangle.setGPSPower;
Bangle.setGPSPower = function(on, id) { Bangle.setGPSPower = function(on, id) {
var isGPSon = oldSetGPSPower(on, id); var isGPSon = oldSetGPSPower(on, id);
WIDGETS.gps.draw(); WIDGETS.gps.width = !isGPSon && settings.hideWhenGpsOff ? 0 : 24;
Bangle.drawWidgets();
return isGPSon; return isGPSon;
}; };
WIDGETS.gps = { WIDGETS.gps = {
area : "tr", area : "tr",
width : 24, width : !Bangle.isGPSOn() && settings.hideWhenGpsOff ? 0 : 24,
draw : function() { draw : function() {
g.reset(); g.reset();

1
apps/widmeda/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial Medical Alert Widget!

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

@ -0,0 +1,23 @@
# Medical Alert Widget
Shows a medical alert logo in the top right widget area, and a medical alert message in the bottom widget area.
**Note:** this is not a replacement for a medical alert band but hopefully a useful addition.
## Features
Implemented:
- Basic medical alert logo and message
- Only display bottom widget on clocks
- High contrast colours depending on theme
Future:
- Configure when to show bottom widget (always/never/clocks)
- Configure medical alert text
- Show details when touched
## Creator
James Taylor ([jt-nti](https://github.com/jt-nti))

View File

@ -0,0 +1,15 @@
{ "id": "widmeda",
"name": "Medical Alert Widget",
"shortName":"Medical Alert",
"version":"0.01",
"description": "Display a medical alert in the bottom widget section.",
"icon": "widget.png",
"type": "widget",
"tags": "health,medical,tools,widget",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"screenshot_light.png"}],
"storage": [
{"name":"widmeda.wid.js","url":"widget.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

30
apps/widmeda/widget.js Normal file
View File

@ -0,0 +1,30 @@
(() => {
// Top right star of life logo
WIDGETS["widmedatr"]={
area: "tr",
width: 24,
draw: function() {
g.reset();
g.setColor("#f00");
g.drawImage(atob("FhYBAAAAA/AAD8AAPwAc/OD/P8P8/x/z/n+/+P5/wP58A/nwP5/x/v/n/P+P8/w/z/Bz84APwAA/AAD8AAAAAA=="), this.x + 1, this.y + 1);
}
};
// Bottom medical alert message
WIDGETS["widmedabl"]={
area: "bl",
width: Bangle.CLOCK?Bangle.appRect.w:0,
draw: function() {
// Only show the widget on clocks
if (!Bangle.CLOCK) return;
g.reset();
g.setBgColor(g.theme.dark ? "#fff" : "#f00");
g.setColor(g.theme.dark ? "#f00" : "#fff");
g.setFont("Vector",18);
g.setFontAlign(0,0);
g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23);
g.drawString("MEDICAL ALERT", this.width / 2, this.y + ( 23 / 2 ));
}
};
})();

BIN
apps/widmeda/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,2 @@
0.01: New Widget!
0.02: Fix calling null on draw

View File

@ -0,0 +1,15 @@
# 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/)

View File

@ -0,0 +1,13 @@
{
"id": "widtwenties",
"name": "Twenties",
"shortName": "twenties",
"version": "0.02",
"description": "Buzzes every 20m to stand / sit and look 20ft away for 20s.",
"icon": "widget.png",
"type": "widget",
"tags": "widget,tools",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [{ "name": "widtwenties.wid.js", "url": "widget.js" }]
}

View File

@ -0,0 +1,24 @@
// 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

BIN
apps/widtwenties/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -225,6 +225,13 @@ apps.forEach((app,appIdx) => {
console.log("====================================================="); console.log("=====================================================");
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`, {file:appDirRelative+file.url}); ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`, {file:appDirRelative+file.url});
} }
// clock app checks
if (app.type=="clock") {
var a = fileContents.indexOf("Bangle.loadWidgets()");
var b = fileContents.indexOf("Bangle.setUI(");
if (a>=0 && b>=0 && a<b)
WARN(`Clock ${app.id} file calls loadWidgets before setUI (clock widget/etc won't be aware a clock app is running)`, {file:appDirRelative+file.url});
}
} }
for (const key in file) { for (const key in file) {
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`, {file:appDirRelative+file.url}); if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`, {file:appDirRelative+file.url});