diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 262cba1fa..da3b3ab5c 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -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 \ No newline at end of file + 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() \ No newline at end of file diff --git a/apps/messages/README.md b/apps/messages/README.md index 2e583d1c2..72a989146 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -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 + // is one of "text", "call", "alarm", "map", "music", or "clearAll" + if (type === "clearAll") return; // not a message + // see `messages/lib.js` for possible 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 diff --git a/apps/messages/app.js b/apps/messages/app.js index 40dff9635..20fa8aaa3 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -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; } diff --git a/apps/messages/lib.js b/apps/messages/lib.js index d8599c93d..ed71ec04b 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -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' diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index da2e0945a..057a95026 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -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", diff --git a/apps/messages/settings.js b/apps/messages/settings.js index 0edb17797..09c9db455 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -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': { diff --git a/apps/messages/widget.js b/apps/messages/widget.js index a5e1f8b6c..c8d132f82 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -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 (csettings.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.xw.x+w.width||c.yw.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()); })();