1
0
Fork 0

Merge branch 'espruino:master' into master

master
Peer David 2022-09-29 22:15:26 +02:00 committed by GitHub
commit cdc2fb12ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 294 additions and 172 deletions

View File

@ -1,2 +1,4 @@
0.01: First, proof of concept
0.02: Load AGPS data on app start and automatically in background
0.03: Do not load AGPS data on boot
Increase minimum interval to 6 hours

View File

@ -16,13 +16,6 @@
}
if (settings.enabled) {
let lastUpdate = settings.lastUpdate;
if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){
if (!waiting){
waiting = true;
require("agpsdata").pull(successCallback, errorCallback);
}
}
setInterval(() => {
if (!waiting && NRF.getSecurityStatus().connected){
waiting = true;

View File

@ -2,7 +2,7 @@
"name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data",
"icon": "agpsdata.png",
"version":"0.02",
"version":"0.03",
"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,

View File

@ -35,7 +35,7 @@ function buildMainMenu() {
},
"Refresh every" : {
value : settings.refresh / 60,
min : 1,
min : 6,
max : 168,
step : 1,
format : v => v + "h",

View File

@ -13,3 +13,4 @@
0.13: Added Bangle.http function (see Readme file for more info)
0.14: Fix timeout of http function not being cleaned up
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)

View File

@ -139,6 +139,8 @@
// options = {id,timeout,xpath}
Bangle.http = (url,options)=>{
options = options||{};
if (!NRF.getSecurityStatus().connected)
return Promise.reject("Not connected to Bluetooth");
if (Bangle.httpRequest === undefined)
Bangle.httpRequest={};
if (options.id === undefined) {

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.15",
"version": "0.16",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

View File

@ -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!)_
![](screenshot-notify.gif)
## 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

View File

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

View File

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

View File

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

View File

@ -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': {

View File

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

2
apps/powersave/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Initial release
0.02: Removed accelerometer poll interval adjustment, fixed a few issues with detecting the current app

25
apps/powersave/README.md Normal file
View File

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

20
apps/powersave/boot.js Normal file
View File

@ -0,0 +1,20 @@
var Storage = Storage || require("Storage");
Bangle.on("lock", locked => {
if(locked){
load("powersave.screen.js");
}else{
const data = JSON.parse(Storage.read("powersave.json") || Storage.read("setting.json"));
load(data.app || data.clock);
}
});
E.on("init", () => {
if("__FILE__" in global && __FILE__ !== "powersave.screen.js"){
Storage.write("powersave.json", {
app: __FILE__
});
}else{
Storage.write("powersave.json", {
app: null
});
}
});

View File

@ -0,0 +1,18 @@
{
"id": "powersave",
"name": "Power Save",
"version": "0.02",
"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"}
],
"data": [
{"name": "powersave.json"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

7
apps/powersave/screen.js Normal file
View File

@ -0,0 +1,7 @@
g.clear();
Bangle.setLCDBrightness(0);
if(!Bangle.isLocked()){
var Storage = Storage || require("Storage");
const data = JSON.parse(Storage.read("powersave.json") || Storage.read("setting.json"));
load(data.app || data.clock);
}

4
apps/twenties/ChangeLog Normal file
View File

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

17
apps/twenties/README.md Normal file
View File

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

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

19
apps/twenties/boot.js Normal file
View File

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

View File

@ -1,13 +1,13 @@
{
"id": "widtwenties",
"id": "twenties",
"name": "Twenties",
"shortName": "twenties",
"version": "0.03",
"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" }]
}

View File

@ -1,3 +0,0 @@
0.01: New Widget!
0.02: Fix calling null on draw
0.03: Only vibrate during work

View File

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

View File

@ -1,30 +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 = _ => {
const date = new Date();
const day = date.getDay();
const hour = date.getHours();
// buzz at work
if (day <= 5 && hour >= 8 && hour <= 17) {
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