Merge pull request #2146 from rigrig/message-events

Message events
pull/2148/head
Gordon Williams 2022-09-26 09:55:43 +01:00 committed by GitHub
commit 85573a9817
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 96 deletions

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