From 2a506e7421f2320ba65afcadeb9d3458a8958dd1 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 20 Nov 2022 15:41:02 +0100 Subject: [PATCH] messages: split library/gui/widget into separate apps --- apps/android/ChangeLog | 1 + apps/android/metadata.json | 4 +- apps/android/settings.js | 2 +- apps/hcclock/ChangeLog | 3 +- apps/hcclock/hcclock.app.js | 2 +- apps/hcclock/metadata.json | 2 +- apps/ios/ChangeLog | 1 + apps/ios/app.js | 2 +- apps/ios/metadata.json | 4 +- apps/messagegui/ChangeLog | 79 +++++ apps/messagegui/README.md | 68 +++++ apps/{messages => messagegui}/app-icon.js | 0 .../app-newmessage.js | 4 +- apps/{messages => messagegui}/app.js | 25 +- apps/messagegui/app.png | Bin 0 -> 917 bytes apps/messagegui/boot.js | 3 + apps/messagegui/lib.js | 60 ++++ apps/messagegui/metadata.json | 22 ++ apps/{messages => messagegui}/screenshot.png | Bin apps/messages/ChangeLog | 78 +---- apps/messages/README.md | 89 ++---- apps/messages/lib.js | 283 +++++++++++------- apps/messages/metadata.json | 21 +- apps/messages/widget.js | 56 ---- apps/messagesmusic/ChangeLog | 1 + apps/messagesmusic/README.md | 6 - apps/messagesmusic/app.js | 15 +- apps/messagesmusic/metadata.json | 4 +- apps/widmessages/ChangeLog | 1 + apps/widmessages/README.md | 30 ++ apps/widmessages/app.png | Bin 0 -> 917 bytes apps/widmessages/lib.js | 8 + apps/widmessages/metadata.json | 18 ++ .../screenshot.gif} | Bin apps/widmessages/widget.js | 73 +++++ apps/widmsggrid/ChangeLog | 1 + apps/widmsggrid/README.md | 6 +- apps/widmsggrid/lib.js | 8 + apps/widmsggrid/metadata.json | 9 +- apps/widmsggrid/widget.js | 19 +- bin/sanitycheck.js | 3 +- 41 files changed, 642 insertions(+), 369 deletions(-) create mode 100644 apps/messagegui/ChangeLog create mode 100644 apps/messagegui/README.md rename apps/{messages => messagegui}/app-icon.js (100%) rename apps/{messages => messagegui}/app-newmessage.js (50%) rename apps/{messages => messagegui}/app.js (96%) create mode 100644 apps/messagegui/app.png create mode 100644 apps/messagegui/boot.js create mode 100644 apps/messagegui/lib.js create mode 100644 apps/messagegui/metadata.json rename apps/{messages => messagegui}/screenshot.png (100%) delete mode 100644 apps/messages/widget.js create mode 100644 apps/widmessages/ChangeLog create mode 100644 apps/widmessages/README.md create mode 100644 apps/widmessages/app.png create mode 100644 apps/widmessages/lib.js create mode 100644 apps/widmessages/metadata.json rename apps/{messages/screenshot-notify.gif => widmessages/screenshot.gif} (100%) create mode 100644 apps/widmessages/widget.js create mode 100644 apps/widmsggrid/lib.js diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index fcb139c94..f75638265 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -15,3 +15,4 @@ 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) 0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge +0.18: Use new message library diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ac47449d6..45c08a75a 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,11 +2,11 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.17", + "version": "0.18", "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", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/android/settings.js b/apps/android/settings.js index c7c34a76f..aede4b413 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -23,7 +23,7 @@ updateSettings(); } }, - /*LANG*/"Messages" : ()=>load("messages.app.js"), + /*LANG*/"Messages" : ()=>require("message").openGUI(), }; E.showMenu(mainmenu); }) diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 289c7ac2d..e2eb18be3 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,4 +1,5 @@ 0.01: Base code 0.02: Saved settings when switching color scheme 0.03: Added Button 3 opening messages (if app is installed) -0.04: Use `messages` library to check for new messages \ No newline at end of file +0.04: Use `messages` library to check for new messages +0.05: Use `messages` library to open message GUI \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index 9558c052b..f12a4733e 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -234,7 +234,7 @@ function handleMessages() { if(!hasMessages()) return; E.showMessage("Loading Messages..."); - load("messages.app.js"); + require("messages").openGUI(); } function hasMessages() diff --git a/apps/hcclock/metadata.json b/apps/hcclock/metadata.json index b8f8c14b9..407114e25 100644 --- a/apps/hcclock/metadata.json +++ b/apps/hcclock/metadata.json @@ -1,7 +1,7 @@ { "id": "hcclock", "name": "Hi-Contrast Clock", - "version": "0.04", + "version": "0.05", "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", "icon": "hcclock-icon.png", "type": "clock", diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 85aafb34f..5763801f5 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -9,3 +9,4 @@ 0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365) 0.10: Added more bundleIds 0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language +0.12: Use new message library diff --git a/apps/ios/app.js b/apps/ios/app.js index b210886fd..2a9e8f322 100644 --- a/apps/ios/app.js +++ b/apps/ios/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -setTimeout(()=>load("messages.app.js"),10); +setTimeout(()=>require("messages").openGUI(),10); diff --git a/apps/ios/metadata.json b/apps/ios/metadata.json index a26d22cb1..5042ff1c0 100644 --- a/apps/ios/metadata.json +++ b/apps/ios/metadata.json @@ -1,11 +1,11 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.11", + "version": "0.12", "description": "Display notifications/music/etc from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog new file mode 100644 index 000000000..3dcb20fe8 --- /dev/null +++ b/apps/messagegui/ChangeLog @@ -0,0 +1,79 @@ +0.01: New App! +0.02: Add 'messages' library +0.03: Fixes for Bangle.js 1 +0.04: Add require("messages").clearAll() +0.05: Handling of message actions (ok/clear) +0.06: New messages now go at the start (fix #898) + Answering true/false now exits the messages app if no new messages + Back now marks a message as read + Clicking top-left opens a menu which allows you to delete a message or mark unread +0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) +0.08: Fix rendering of long messages (fix #969) + buzz on new message (fix #999) +0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) + Fix phone icon (#1014) +0.10: Respect the 'new' attribute if it was set from iOS integrations +0.11: Open app when touching the widget (Bangle.js 2 only) +0.12: Extra app-specific notification icons + New animated notification icon (instead of large blinking 'MESSAGES') + Added screenshots +0.13: Add /*LANG*/ comments for internationalisation + Add 'Delete All' option to message options + Now update correctly when 'require("messages").clearAll()' is called +0.14: Hide widget when all unread notifications are dismissed from phone +0.15: Don't buzz when Quiet Mode is active +0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) +0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font +0.18: Use app-specific icon colors + Spread message action buttons out + Back button now goes back to list of messages + If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) +0.19: Use a larger font for message text if it'll fit +0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) +0.21: Improve list readability on dark theme +0.22: Add Home Assistant icon + Allow repeat to be switched Off, so there is no buzzing repetition. + Also gave the widget a pixel more room to the right +0.23: Change message colors to match current theme instead of using green + Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured +0.24: Remove left-over debug statement +0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) +0.26: Setting to auto-open music +0.27: Add 'mark all read' option to popup menu (fix #1624) +0.28: Option to auto-unlock the watch when a new message arrives +0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) +0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) +0.31: Option to disable icon flashing +0.32: Added an option to allow quiet mode to override message auto-open +0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going +0.34: Don't buzz for 'map' update messages +0.35: Reset graphics colors before rendering a message (possibly fix #1752) +0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) +0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items +0.38: Add telegram foss handling +0.39: Set default color for message icons according to theme +0.40: Use default Bangle formatter for booleans +0.41: Add notification icons in the widget +0.42: Fix messages ignoring "Vibrate: Off" setting +0.43: Add new Icons (Airbnb, warnwetter) +0.44: Separate buzz pattern for incoming calls +0.45: Added new app colors and icons +0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off + Fix message removal from widget bar (previously caused exception as .hide has been removed) +0.47: Add new Icons (Nextbike, Mattermost, etc.) +0.48: When getting new message from the clock, only buzz once the messages app is loaded +0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally +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 +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() +0.52: Fix require("messages").buzz() regression + Fix background color in messages list after one unread message is shown +0.53: Messages now uses Bangle.load() to load messages app faster (if possible) +0.54: Move icons out to messageicons module +0.55: Rename to messagegui, move global message handling library to message module + Move widget to widmessage diff --git a/apps/messagegui/README.md b/apps/messagegui/README.md new file mode 100644 index 000000000..699588e1b --- /dev/null +++ b/apps/messagegui/README.md @@ -0,0 +1,68 @@ +# Messages app + +Default app to handle the display of messages and message notifications. It allows +them to be listed, viewed, and responded to. +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + + +## Settings + +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received +* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received +* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds +* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and +buzz every `Repeat` seconds. This is how long we continue to do that. +* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. +If there is no user input for this amount of time then the app will exit and return +to the clock where a ringing bell will be shown in the Widget bar. +* `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font +is chosen if there isn't much message text, but this specifies the smallest the font should get before +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? + +## New Messages + +When a new message is received: + +* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. +* If you're in a clock, the Messages app will automatically start and show the message + +When a message is shown, you'll see a screen showing the message title and text. + +* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. +* The top-left icon shows more options, for instance deleting the message of marking unread +* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) +* If shown, the 'tick' button: + * **Android** opens the notification on the phone + * **iOS** responds positively to the notification (accept call/etc) +* If shown, the 'cross' button: + * **Android** dismisses the notification on the phone + * **iOS** responds negatively to the notification (dismiss call/etc) + +## Images +_1. Screenshot of a notification_ + +![](screenshot.png) + + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/messages/app-icon.js b/apps/messagegui/app-icon.js similarity index 100% rename from apps/messages/app-icon.js rename to apps/messagegui/app-icon.js diff --git a/apps/messages/app-newmessage.js b/apps/messagegui/app-newmessage.js similarity index 50% rename from apps/messages/app-newmessage.js rename to apps/messagegui/app-newmessage.js index 328927c70..73d9a79c1 100644 --- a/apps/messages/app-newmessage.js +++ b/apps/messagegui/app-newmessage.js @@ -1,5 +1,5 @@ /* Called when we have a new message when we're in the clock... -BUZZ_ON_NEW_MESSAGE is set so when messages.app.js loads it knows +BUZZ_ON_NEW_MESSAGE is set so when messagegui.app.js loads it knows that it should buzz */ global.BUZZ_ON_NEW_MESSAGE = true; -eval(require("Storage").read("messages.app.js")); +eval(require("Storage").read("messagegui.app.js")); diff --git a/apps/messages/app.js b/apps/messagegui/app.js similarity index 96% rename from apps/messages/app.js rename to apps/messagegui/app.js index bebd92816..bf086dd3d 100644 --- a/apps/messages/app.js +++ b/apps/messagegui/app.js @@ -19,7 +19,6 @@ require("messages").pushMessage({"t":"add","id":1,"src":"Maps","title":"0 yd - H // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) */ - var Layout = require("Layout"); var settings = require('Storage').readJSON("messages.settings.json", true) || {}; var fontSmall = "6x8"; @@ -49,8 +48,11 @@ to the clock. */ var unreadTimeout; /// List of all our messages var MESSAGES = require("messages").getMessages(); -if (!Array.isArray(MESSAGES)) MESSAGES=[]; -var onMessagesModified = function(msg) { + +var onMessagesModified = function(type,msg) { + if (msg.handled) return; + msg.handled = true; + require("messages").apply(msg, MESSAGES); // TODO: if new, show this new one if (msg && msg.id!=="music" && msg.new && active!="map" && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { @@ -62,9 +64,15 @@ var onMessagesModified = function(msg) { } showMessage(msg&&msg.id); }; +Bangle.on("message", onMessagesModified); + function saveMessages() { - require("Storage").writeJSON("messages.json",MESSAGES) + require("messages").write(MESSAGES.map(m => { + delete m.show; + return m; + })); } +E.on("kill", saveMessages); function showMapMessage(msg) { active = "map"; @@ -355,12 +363,16 @@ function checkMessages(options) { } // we have >0 messages var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music"); + var toShow = MESSAGES.find(m=>m.show); + if (toShow) { + newMessages.unshift(toShow); + } // If we have a new message, show it - if (options.showMsgIfUnread && newMessages.length) { + if ((toShow||options.showMsgIfUnread) && newMessages.length) { showMessage(newMessages[0].id); // 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` + // this is set if we entered the messages app by loading `messagegui.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 @@ -428,6 +440,7 @@ function cancelReloadTimeout() { g.clear(); Bangle.loadWidgets(); +require("messages").toggleWidget(false); Bangle.drawWidgets(); setTimeout(() => { diff --git a/apps/messagegui/app.png b/apps/messagegui/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/messagegui/boot.js b/apps/messagegui/boot.js new file mode 100644 index 000000000..4592f825d --- /dev/null +++ b/apps/messagegui/boot.js @@ -0,0 +1,3 @@ +(function() { + Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg)); +})(); \ No newline at end of file diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js new file mode 100644 index 000000000..5b07dd160 --- /dev/null +++ b/apps/messagegui/lib.js @@ -0,0 +1,60 @@ +/** + * Listener set up in boot.js, calls into here to keep boot.js short + */ +exports.listener = function(type, msg) { + // Default handler: Launch the GUI for all unhandled messages (except music if disabled in settings) + if (msg.handled || (global.__FILE__ && __FILE__.startsWith('messagegui.'))) return; // already handled or app open + + // if no new messages now, make sure we don't load the messages app + if (exports.messageTimeout && !msg.new && require("messages").status(msg) !== "new") { + clearTimeout(exports.messageTimeout); + delete exports.messageTimeout; + } + if (msg.t==="remove") return; + + const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; + let loadMessages = (Bangle.CLOCK || event.important); + if (type==="music") { + if (Bangle.CLOCK && msg.new && appSettings.openMusic) loadMessages = true; + else return; + } + require("messages").save(msg); + msg.handled = true; + if (msg.t!=="add" || !msg.new) { + return; + } + + const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet; + const unlockWatch = appSettings.unlockWatch; + // don't auto-open messages in quiet mode if quietNoAutOpn is true + if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) + loadMessages = false; + + // after a delay load the app, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + delete exports.messageTimeout; + if (type!=="music") { + if (!loadMessages) return require("messages").buzz(msg.src); // no opening the app, just buzz + if (!quiet && unlockWatch) { + Bangle.setLocked(false); + Bangle.setLCDPower(1); // turn screen on + } + } + exports.open(msg); + }, 500); +}; + +/** + * Launch GUI app with given message + * @param {object} msg + */ +exports.open = function(msg) { + if (msg && msg.id && !msg.show) { + // store which message to load + msg.show = 1; + require("messages").save(msg, {force: 1}); + } + + Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js"); +}; diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json new file mode 100644 index 000000000..7444050fd --- /dev/null +++ b/apps/messagegui/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "messagegui", + "name": "Messages", + "version": "0.55", + "description": "Default app to display notifications from iOS and Gadgetbridge/Android", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module" }, + "provides_modules": ["messagegui"], + "readme": "README.md", + "storage": [ + {"name":"messagegui","url":"lib.js"}, + {"name":"messagegui.app.js","url":"app.js"}, + {"name":"messagegui.new.js","url":"app-newmessage.js"}, + {"name":"messagegui.boot.js","url":"boot.js"}, + {"name":"messagegui.img","url":"app-icon.js","evaluate":true} + ], + "screenshots": [{"url":"screenshot.png"}], + "sortorder": -9 +} diff --git a/apps/messages/screenshot.png b/apps/messagegui/screenshot.png similarity index 100% rename from apps/messages/screenshot.png rename to apps/messagegui/screenshot.png diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index ee27d41c6..b0a84783b 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,77 +1 @@ -0.01: New App! -0.02: Add 'messages' library -0.03: Fixes for Bangle.js 1 -0.04: Add require("messages").clearAll() -0.05: Handling of message actions (ok/clear) -0.06: New messages now go at the start (fix #898) - Answering true/false now exits the messages app if no new messages - Back now marks a message as read - Clicking top-left opens a menu which allows you to delete a message or mark unread -0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) -0.08: Fix rendering of long messages (fix #969) - buzz on new message (fix #999) -0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) - Fix phone icon (#1014) -0.10: Respect the 'new' attribute if it was set from iOS integrations -0.11: Open app when touching the widget (Bangle.js 2 only) -0.12: Extra app-specific notification icons - New animated notification icon (instead of large blinking 'MESSAGES') - Added screenshots -0.13: Add /*LANG*/ comments for internationalisation - Add 'Delete All' option to message options - Now update correctly when 'require("messages").clearAll()' is called -0.14: Hide widget when all unread notifications are dismissed from phone -0.15: Don't buzz when Quiet Mode is active -0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) -0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font -0.18: Use app-specific icon colors - Spread message action buttons out - Back button now goes back to list of messages - If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) -0.19: Use a larger font for message text if it'll fit -0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) -0.21: Improve list readability on dark theme -0.22: Add Home Assistant icon - Allow repeat to be switched Off, so there is no buzzing repetition. - Also gave the widget a pixel more room to the right -0.23: Change message colors to match current theme instead of using green - Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured -0.24: Remove left-over debug statement -0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) -0.26: Setting to auto-open music -0.27: Add 'mark all read' option to popup menu (fix #1624) -0.28: Option to auto-unlock the watch when a new message arrives -0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) -0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) -0.31: Option to disable icon flashing -0.32: Added an option to allow quiet mode to override message auto-open -0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going -0.34: Don't buzz for 'map' update messages -0.35: Reset graphics colors before rendering a message (possibly fix #1752) -0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) -0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items -0.38: Add telegram foss handling -0.39: Set default color for message icons according to theme -0.40: Use default Bangle formatter for booleans -0.41: Add notification icons in the widget -0.42: Fix messages ignoring "Vibrate: Off" setting -0.43: Add new Icons (Airbnb, warnwetter) -0.44: Separate buzz pattern for incoming calls -0.45: Added new app colors and icons -0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off - Fix message removal from widget bar (previously caused exception as .hide has been removed) -0.47: Add new Icons (Nextbike, Mattermost, etc.) -0.48: When getting new message from the clock, only buzz once the messages app is loaded -0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally -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 -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() -0.52: Fix require("messages").buzz() regression - Fix background color in messages list after one unread message is shown -0.53: Messages now uses Bangle.load() to load messages app faster (if possible) -0.54: Move icons out to messageicons module +0.01: Moved messages library into standalone library \ No newline at end of file diff --git a/apps/messages/README.md b/apps/messages/README.md index 72a989146..1a6758311 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -1,62 +1,25 @@ -# Messages app +# Messages library -This app handles the display of messages and message notifications. It stores -a list of currently received messages and allows them to be listed, viewed, -and responded to. +This library handles the passing of messages. It can storess a list of messages +and allows them to be retrieved by other apps. -It is a replacement for the old `notify`/`gadgetbridge` apps. +## Example -## Settings +Assuming you are using GadgetBridge and "overlay notifications": -You can change settings by going to the global `Settings` app, then `App Settings` -and `Messages`: - -* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received -* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received -* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds -* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and -buzz every `Repeat` seconds. This is how long we continue to do that. -* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. -If there is no user input for this amount of time then the app will exit and return -to the clock where a ringing bell will be shown in the Widget bar. -* `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font -is chosen if there isn't much message text, but this specifies the smallest the font should get before -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, or `Hide` the widget completely. - -## New Messages - -When a new message is received: - -* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. -* If you're in a clock, the Messages app will automatically start and show the message - -When a message is shown, you'll see a screen showing the message title and text. - -* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. -* The top-left icon shows more options, for instance deleting the message of marking unread -* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) -* If shown, the 'tick' button: - * **Android** opens the notification on the phone - * **iOS** responds positively to the notification (accept call/etc) -* If shown, the 'cross' button: - * **Android** dismisses the notification on the phone - * **iOS** responds negatively to the notification (dismiss call/etc) - -## Images -_1. Screenshot of a notification_ - -![](screenshot.png) - -_2. What the notify icon looks like (it's touchable on Bangle.js2!)_ - -![](screenshot-notify.gif) +1. Gadgetbridge sends an event to your watch for an incoming message +2. The `android` app parses the message, and calls `require("messages").pushMessage({/** the message */})` +3. `require("messages")` (provided by `messagelib`) calls `Bangle.emit("message", "text", {/** the message */})` +4. Overlay Notifications shows the message in an overlay, and marks it as `handled` +5. The default GUI app (`messages`) sees the event is marked as `handled`, so does nothing. +6. The default widget (`widmessages`) does nothing with `handled`, and shows a notification icon. +7. You tap the notification, in order to open the full GUI Overlay Notifications + calls `require("messages").openGUI({/** the message */})` +8. The default GUI app (`messages`) sees the "messageGUI" event, and launches itself -## Events (for app/widget developers) + +## Events When a new message arrives, a `"message"` event is emitted, you can listen for it like this: @@ -64,9 +27,8 @@ 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 + // is one of "text", "call", "alarm", "map", or "music" + // see `messagelib/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`: @@ -74,10 +36,23 @@ myMessageListener = Bangle.on("message", (type, message)=>{ }); ``` +Apps can launch the full GUI by calling `require("messages").openGUI()`, if you +want to write your own GUI, it should include boot code that listens for +`"messageGUI"` events: + +```js +Bangle.on("messageGUI", message=>{ + if (message.handled) return; // another app already opened it's GUI + message.handled = true; // prevent other apps form launching + Bangle.load("my_message_gui.app.js"); +}) + +``` + ## Requests -Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messagelib%library ## Creator diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 0ed03e4b6..fa1419c95 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -1,10 +1,16 @@ -function openMusic() { - // only read settings file for first music message - if ("undefined"==typeof exports._openMusic) { - exports._openMusic = !!((require('Storage').readJSON("messages.settings.json", true) || {}).openMusic); - } - return exports._openMusic; +exports.music = {}; +/** + * Emit "message" event with appropriate type from Bangle + * @param {object} msg + */ +function emit(msg) { + let type = "text"; + if (["call", "music", "map"].includes(msg.id)) type = msg.id; + if (type==="music" && msg.t!=="remove" && (!("state" in msg) || (!("track" in msg)))) return; // wait for complete music info + if (msg.src && msg.src.toLowerCase().startsWith("alarm")) type = "alarm"; + Bangle.emit("message", type, msg); } + /* 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 @@ -12,125 +18,178 @@ function openMusic() { {t:"modify",id:int, title:string} // modified */ exports.pushMessage = function(event) { - var messages = exports.getMessages(); // now modify/delete as appropriate - var mIdx = messages.findIndex(m=>m.id==event.id); - if (event.t=="remove") { - if (mIdx>=0) messages.splice(mIdx, 1); // remove item - mIdx=-1; + if (event.t==="remove") { + if (event.id==="music") exports.music = {}; } else { // add/modify - if (event.t=="add"){ - if(event.new === undefined ) { // If 'new' has not been set yet, set it - event.new=true; // Assume it should be new - } + if (event.t==="add") { + if (event.new===undefined) event.new = true; // Assume it should be new + } else if (event.t==="modify") { + const old = exports.getMessages().find(m => m.id===event.id); + if (old) event = Object.assign(old, event); } - if (mIdx<0) { - mIdx=0; - messages.unshift(event); // add new messages to the beginning - } - 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'; + + // combine musicinfo and musicstate events + if (event.id==="music") { + if (event.state==="play") event.new = true; // new track, or playback (re)started + event = Object.assign(exports.music, event); } } - require("Storage").writeJSON("messages.json",messages); - var message = mIdx<0 ? {id:event.id, t:'remove'} : messages[mIdx]; - // if in app, process immediately - 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 - Bangle.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 Bangle.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 + // reset state (just in case) + delete event.handled; + delete event.saved; + emit(event); +}; + +/** + * Save a single message to flash + * Also sets msg.saved=true + * + * @param {object} msg + * @param {object} [options={}] Options: + * {boolean} [force=false] Force save even if msg.saved is already set + */ +exports.save = function(msg, options) { + if (!options) options = {}; + if (msg.saved && !options.force) return; //already saved + let messages = exports.getMessages(); + exports.apply(msg, messages); + exports.write(messages); + msg.saved = true; +}; + +/** + * Apply incoming event to array of messages + * + * @param {object} event Event to apply + * @param {array} messages Array of messages, *will be modified in-place* + * @return {array} Modified messages array + */ +exports.apply = function(event, messages) { + if (!event || !event.id) return messages; + const mIdx = messages.findIndex(m => m.id===event.id); + if (event.t==="remove") { + if (mIdx<0) return messages; // already gone -> nothing to do + messages.splice(mIdx, 1); + } else if (event.t==="add") { + if (mIdx>=0) messages.splice(mIdx, 1); // duplicate ID! erase previous version + messages.unshift(event); + } else if (event.t==="modify") { + if (mIdx>=0) messages[mIdx] = Object.assign(messages[mIdx], event); + else messages.unshift(event); + } + return messages; +}; + +/** + * Accept a call (or other acceptable event) + * @param {object} msg + */ +exports.accept = function(msg) { + if (msg.positive) Bangle.messageResponse(msg, true); +}; + +/** + * Dismiss a message (if applicable), and erase it from flash + * Emits a "message" event with t="remove", only if message existed + * + * @param {object} msg + */ +exports.dismiss = function(msg) { + if (msg.negative) Bangle.messageResponse(msg, false); + let messages = exports.getMessages(); + const mIdx = messages.findIndex(m=>m.id===msg.id); + if (mIdx<0) return; + messages.splice(mIdx, 1); + exports.write(messages); + if (msg.t==="remove") return; // already removed, don't re-emit + msg.t = "remove"; + emit(msg); // emit t="remove", so e.g. widgets know to update +}; + +/** + * Emit a "type=openGUI" event, to open GUI app + * + * @param {object} [msg={}] Message the app should show + */ +exports.openGUI = function(msg) { + if (!require("Storage").read("messagegui")) return; // "messagegui" module is missing! + // Mark the event as unhandled for GUI, but leave passed arguments intact + let copy = Object.assign({}, msg); + delete copy.handled; + require("messagegui").open(copy); +}; + +/** + * Show/hide the messages widget + * + * @param {boolean} show + */ +exports.toggleWidget = function(show) { + if (!require("Storage").read("messagewidget")) return; // "messagewidget" module is missing! + if (show) require("messagewidget").show(); + else require("messagewidget").hide(); +}; + +/** + * Replace all stored messages + * @param {array} messages Messages to save + */ +exports.write = function(messages) { + require("Storage").writeJSON("messages.json", messages.map(m => { + // we never want to save saved/handled status to file; + delete m.saved; + delete m.handled; + return m; + })); +}; +/** + * Erase all messages + */ exports.clearAll = function() { - if ("undefined"!= typeof MESSAGES) { // we're in a messages app, clear that as well - MESSAGES = []; - } - // Clear all messages - require("Storage").writeJSON("messages.json", []); - // if we have a widget, update it - if (global.WIDGETS && WIDGETS.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(); + exports.write([]); + Bangle.emit("message", "clearAll", {}); } /** + * Get saved messages + * + * Optionally pass in a message to apply to the list, this is for event handlers: + * By passing the message from the event, you can make sure the list is up-to-date, + * even if the message has not been saved (yet) + * + * Example: + * Bangle.on("message", (type, msg) => { + * console.log("All messages:", require("messages").getMessages(msg)); + * }); + * + * @param {object} [withMessage] Apply this event to messages * @returns {array} All messages */ -exports.getMessages = function() { - if ("undefined"!=typeof MESSAGES) return MESSAGES; // loaded/managed by app - return require("Storage").readJSON("messages.json",1)||[]; -} +exports.getMessages = function(withMessage) { + let messages = require("Storage").readJSON("messages.json", true); + messages = Array.isArray(messages) ? messages : []; // make sure we always return an array + if (withMessage && withMessage.id) exports.apply(withMessage, messages); + return messages; +}; /** * Check if there are any messages + * + * @param {object} [withMessage] Apply this event to messages, see getMessages * @returns {string} "new"/"old"/"none" */ - exports.status = function() { +exports.status = function(withMessage) { try { - let status= "none"; - for(const m of exports.getMessages()) { + let status = "none"; + for(const m of exports.getMessages(withMessage)) { if (["music", "map"].includes(m.id)) continue; if (m.new) return "new"; status = "old"; } return status; } catch(e) { - return "none"; // don't bother e.g. the widget with errors + return "none"; // don't bother callers with errors } }; @@ -141,24 +200,24 @@ exports.getMessages = function() { */ 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 msgSettings = require('Storage').readJSON("messages.settings.json", true) || {}; - var pattern; - if (msgSrc && msgSrc.toLowerCase() === "phone") { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet) return Promise.resolve(); // never buzz during Quiet Mode + const msgSettings = require("Storage").readJSON("messages.settings.json", true) || {}; + let pattern; + if (msgSrc && msgSrc.toLowerCase()==="phone") { // special vibration pattern for incoming calls pattern = msgSettings.vibrateCalls; } else { pattern = msgSettings.vibrate; } - if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here + if (pattern===undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here if (!pattern) return Promise.resolve(); - var repeat = msgSettings.repeat; - if (repeat===undefined) repeat=4; // repeat may be zero + let repeat = msgSettings.repeat; + if (repeat===undefined) repeat = 4; // repeat may be zero if (repeat) { - exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000); - var vibrateTimeout = msgSettings.vibrateTimeout; - if (vibrateTimeout===undefined) vibrateTimeout=60; + exports.buzzTimeout = setTimeout(() => require("buzz").pattern(pattern), repeat*1000); + let vibrateTimeout = msgSettings.vibrateTimeout; + if (vibrateTimeout===undefined) vibrateTimeout = 60; if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000); } return require("buzz").pattern(pattern); diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index f3051958e..f94f01c26 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,23 +1,18 @@ { "id": "messages", "name": "Messages", - "version": "0.54", - "description": "App to display notifications from iOS and Gadgetbridge/Android", + "version": "0.01", + "description": "Library to handle message events", "icon": "app.png", - "type": "app", + "type": "module", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], - "dependencies" : { "messageicons":"module" }, + "provides_modules" : ["messages"], + "dependencies" : { "messagegui":"module","messagewidget":"module" }, "readme": "README.md", "storage": [ - {"name":"messages.app.js","url":"app.js"}, - {"name":"messages.new.js","url":"app-newmessage.js"}, - {"name":"messages.settings.js","url":"settings.js"}, - {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.wid.js","url":"widget.js"}, - {"name":"messages","url":"lib.js"} + {"name":"messages","url":"lib.js"}, + {"name":"messages.settings.js","url":"settings.js"} ], - "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], - "sortorder": -9 + "data": [{"name":"messages.json"},{"name":"messages.settings.json"}] } diff --git a/apps/messages/widget.js b/apps/messages/widget.js deleted file mode 100644 index c0dcd132f..000000000 --- a/apps/messages/widget.js +++ /dev/null @@ -1,56 +0,0 @@ -(() => { -if ((require('Storage').readJSON("messages.settings.json", true) || {}).maxMessages===0) return; - -function filterMessages(msgs) { - return msgs.filter(msg => msg.new && msg.id != "music") - .map(m => m.src) // we only need this for icon/color - .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); -} - -WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) { - // If we had a setTimeout queued from the last time we were called, remove it - if (WIDGETS["messages"].i) { - clearTimeout(WIDGETS["messages"].i); - delete WIDGETS["messages"].i; - } - Bangle.removeListener('touch', this.touch); - if (!this.width) return; - 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, - require("messageicons").getColor(msg, {settings:settings})]; - if (settings.flash && ((Date.now()/1000)&1)) { - if (colors[1] == g.theme.fg) { - colors.reverse(); - } else { - colors[1] = g.theme.fg; - } - } - g.setColor(colors[1]).setBgColor(colors[0]); - // draw the icon, or '...' if too many messages - g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(msg), - this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/}); - } - } - WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000); - if (process.env.HWVERSION>1) Bangle.on('touch', this.touch); -},update:function(rawMsgs) { - const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); - this.msgs = filterMessages(rawMsgs); - this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages); - Bangle.drawWidgets(); -},touch:function(b,c) { - var w=WIDGETS["messages"]; - if (!w||!w.width||c.xw.x+w.width||c.yw.y+24) return; - load("messages.app.js"); -}}; - -/* We might have returned here if we were in the Messages app for a -message but then the watch was never viewed. */ -if (global.MESSAGES===undefined) - WIDGETS["messages"].update(require("messages").getMessages()); -})(); diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog index 9f4cafb0e..65fd09686 100644 --- a/apps/messagesmusic/ChangeLog +++ b/apps/messagesmusic/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app. +0.03: Use the new messages library \ No newline at end of file diff --git a/apps/messagesmusic/README.md b/apps/messagesmusic/README.md index 85608118d..9a50de93e 100644 --- a/apps/messagesmusic/README.md +++ b/apps/messagesmusic/README.md @@ -1,15 +1,9 @@ Hacky app that uses Messages app and it's library to push a message that triggers the music controls. It's nearly not an app, and yet it moves. -This app require Messages setting 'Auto-open Music' to be 'Yes'. If it isn't, the app will change it to 'Yes' and let it stay that way. - Making the music controls accessible this way lets one start a music stream on the phone in some situations even though the message app didn't receive a music message from gadgetbridge to begin with. (I think.) It is suggested to use Messages Music along side the app Quick Launch. -Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14. - -Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. - Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986). The icon used for this app is from [https://icons8.com](https://icons8.com). diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js index 27f3f6e4d..11e7e5a4e 100644 --- a/apps/messagesmusic/app.js +++ b/apps/messagesmusic/app.js @@ -1,14 +1 @@ -let showMusic = () => { - Bangle.CLOCK = 1; // To pass condition in messages library - require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); -}; - -var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict -if (!settings.openMusic) { - settings.openMusic = true; // This app/hack works as intended only if this setting is true - require('Storage').writeJSON('messages.settings.json', settings); - E.showMessage("First run:\n\nMessages setting\n\n 'Auto-Open Music'\n\n set to 'Yes'"); - setTimeout(()=>{showMusic();}, 5000); -} else { - showMusic(); -} +require('messages').openGUI({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json index c29ffbc34..ce5281563 100644 --- a/apps/messagesmusic/metadata.json +++ b/apps/messagesmusic/metadata.json @@ -1,7 +1,7 @@ { "id": "messagesmusic", "name":"Messages Music", - "version":"0.02", + "version":"0.03", "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", "icon":"app.png", "type": "app", @@ -13,6 +13,6 @@ {"name":"messagesmusic.app.js","url":"app.js"}, {"name":"messagesmusic.img","url":"app-icon.js","evaluate":true} ], - "dependencies": {"messages":"app"} + "dependencies" : { "messagelib":"module" } } diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog new file mode 100644 index 000000000..3a41005e9 --- /dev/null +++ b/apps/widmessages/ChangeLog @@ -0,0 +1 @@ +0.01: Moved messages widget into standalone widget app \ No newline at end of file diff --git a/apps/widmessages/README.md b/apps/widmessages/README.md new file mode 100644 index 000000000..398cb4fa8 --- /dev/null +++ b/apps/widmessages/README.md @@ -0,0 +1,30 @@ +# Messages widget + +The default widget to show icons for new messages +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +![screenshot](screenshot.gif) + +## Settings +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Flash icon` Toggle flashing of the widget icons. + +* `Widget messages` Not used by this widget. + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=widmessages%widget + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/widmessages/app.png b/apps/widmessages/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/widmessages/lib.js b/apps/widmessages/lib.js new file mode 100644 index 000000000..897611ad1 --- /dev/null +++ b/apps/widmessages/lib.js @@ -0,0 +1,8 @@ +exports.hide = function() { + if (!global.WIDGETS||!WIDGETS["messages"]) return; + WIDGETS["messages"].hide(); +} +exports.show = function() { + if (!global.WIDGETS||!WIDGETS["messages"]) return; + WIDGETS["messages"].show(); +} \ No newline at end of file diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json new file mode 100644 index 000000000..0c3ac7e05 --- /dev/null +++ b/apps/widmessages/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "widmessages", + "name": "Message Widget", + "version": "0.01", + "description": "Widget showing new messages", + "icon": "app.png", + "type": "widget", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "screenshots": [{"url": "screenshot.gif"}], + "dependencies" : { "messageicons":"module" }, + "provides_modules" : ["messagewidget"], + "readme": "README.md", + "storage": [ + {"name":"messagewidget","url":"lib.js"}, + {"name":"widmessages.wid.js","url":"widget.js"} + ] +} diff --git a/apps/messages/screenshot-notify.gif b/apps/widmessages/screenshot.gif similarity index 100% rename from apps/messages/screenshot-notify.gif rename to apps/widmessages/screenshot.gif diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js new file mode 100644 index 000000000..2ee11b690 --- /dev/null +++ b/apps/widmessages/widget.js @@ -0,0 +1,73 @@ +(() => { + if ((require("Storage").readJSON("messages.settings.json", true) || {}).maxMessages===0) return; + + function filterMessages(msgs) { + return msgs.filter(msg => msg.new && msg.id != "music") + .map(m => m.src) // we only need this for icon/color + .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); + } + + WIDGETS["messages"] = { + area: "tl", width: 0, srcs: [], draw: function(recall) { + // If we had a setTimeout queued from the last time we were called, remove it + if (WIDGETS["messages"].i) { + clearTimeout(WIDGETS["messages"].i); + delete WIDGETS["messages"].i; + } + Bangle.removeListener("touch", this.touch); + if (!this.width) return; + let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {}); + if (recall!==true || settings.flash) { + const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages), + srcs = Object.keys(this.srcs); + g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); + for(let i = 0; isettings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messageicons").getImage(src), + this.x+12+i*24, this.y+12, {rotate: 0/*force centering*/}); + } + } + WIDGETS["messages"].i = setTimeout(() => WIDGETS["messages"].draw(true), 1000); + if (process.env.HWVERSION>1) Bangle.on("touch", this.touch); + }, onMsg: function(type, msg) { + if (this.hidden) return; + if (type==="music") return; + if (msg.id && !msg.new && msg.t!=="remove") return; + this.srcs = filterMessages(require("messages").getMessages(msg)); + const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); + this.width = 24 * E.clip(this.srcs.length, 0, settings.maxMessages); + if (type!=="init") Bangle.drawWidgets(); // "init" is not a real message type: see below + }, touch: function(b, c) { + var w = WIDGETS["messages"]; + if (!w || !w.width || c.xw.x+w.width || c.yw.y+24) return; + require("messages").openGUI(); + }, hide() { + this.hidden=true; + if (this.width) { + // hide widget + this.width = 0; + Bangle.drawWidgets(); + } + }, show() { + delete this.hidden + this.onMsg("show", {}); // reload messages+redraw + } + }; + + Bangle.on("message", WIDGETS["messages"].onMsg); + this.srcs = {}; + WIDGETS["messages"].onMsg("init", {}); // abuse type="init" to prevent Bangle.drawWidgets(); +})(); diff --git a/apps/widmsggrid/ChangeLog b/apps/widmsggrid/ChangeLog index 75259c4f0..9612da729 100644 --- a/apps/widmsggrid/ChangeLog +++ b/apps/widmsggrid/ChangeLog @@ -1,2 +1,3 @@ 0.01: New widget! 0.02: Adjust to message icons moving to messageicons lib +0.03: Use new message library \ No newline at end of file diff --git a/apps/widmsggrid/README.md b/apps/widmsggrid/README.md index 86a80c403..274858d66 100644 --- a/apps/widmsggrid/README.md +++ b/apps/widmsggrid/README.md @@ -20,9 +20,9 @@ You probably want to disable the default widget, to do so: 3. Scroll down to the `Widget messages` entry, and change it to `Hide` ## Settings -This widget uses the `Widget` settings from the `messages` app: +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: -### Widget * `Flash icon` Toggle flashing of the widget icons. -* `Widget messages` Not used by this widget, but you should select `Hide` to hide the default widget. \ No newline at end of file +* `Widget messages` Not used by this widget. \ No newline at end of file diff --git a/apps/widmsggrid/lib.js b/apps/widmsggrid/lib.js new file mode 100644 index 000000000..430577209 --- /dev/null +++ b/apps/widmsggrid/lib.js @@ -0,0 +1,8 @@ +exports.hide = function() { + if (!global.WIDGETS||!WIDGETS["msggrid"]) return; + WIDGETS["msggrid"].hide(); +} +exports.show = function() { + if (!global.WIDGETS||!WIDGETS["msggrid"]) return; + WIDGETS["msggrid"].show(); +} \ No newline at end of file diff --git a/apps/widmsggrid/metadata.json b/apps/widmsggrid/metadata.json index c9ed5bbe0..68c2c3771 100644 --- a/apps/widmsggrid/metadata.json +++ b/apps/widmsggrid/metadata.json @@ -1,16 +1,17 @@ { "id": "widmsggrid", "name": "Messages Grid Widget", - "version": "0.02", - "description": "Widget that display notification icons in a grid", + "version": "0.03", + "description": "Widget that displays notification icons in a grid", "icon": "widget.png", "type": "widget", - "dependencies": {"messages":"app"}, "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], - "dependencies" : { "messageicons":"module" }, + "dependencies" : { "messages":"module" }, + "provides_modules" : ["messagewidget"], "readme": "README.md", "storage": [ + {"name":"messagewidget","url":"lib.js"}, {"name":"widmsggrid.wid.js","url":"widget.js"} ], "screenshots": [{"url":"screenshot.png"}] diff --git a/apps/widmsggrid/widget.js b/apps/widmsggrid/widget.js index 431adf479..786f590b5 100644 --- a/apps/widmsggrid/widget.js +++ b/apps/widmsggrid/widget.js @@ -24,7 +24,7 @@ clearTimeout(w.t); delete w.t; } - if (!w.width) return; + if (!w.width || this.hidden) return; const b = w.flash && w.status === "new" && ((Date.now() / 1000) & 1), // Blink(= inverse colors) on this second? // show multiple icons in a grid, by scaling them down cols = Math.ceil(Math.sqrt(w.srcs.length - 0.1)); // cols===rows, -0.1 to work around rounding error @@ -57,9 +57,10 @@ .drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9); } if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking - }, show: function () { + }, show: function (m) { + delete w.hidden; w.width = 24; - w.srcs = require("messages").getMessages() + w.srcs = require("messages").getMessages(m) .filter(m => !['call', 'map', 'music'].includes(m.id)) .filter(m => m.new || w.showRead) .map(m => m.src); @@ -68,6 +69,7 @@ Bangle.drawWidgets(); Bangle.setLCDPower(1); // turns screen on }, hide: function () { + w.hidden = true; w.width = 0; w.srcs = []; w.total = 0; @@ -82,13 +84,16 @@ } // Bangle.js 2: open app when touching the widget else if (c.x < w.x || c.x > w.x + w.width || c.y < w.y || c.y > w.y + 24) return; - load("messages.app.js"); - }, listener: function () { - w.status = require("messages").status(); - if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(); + require("messages").openGUI(); + }, listener: function (t,m) { + if (this.hidden) return; + w.status = require("messages").status(m); + if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(m); else w.hide(); + delete w.hidden; // always set by w.hide(), but we checked it wasn't there before } }; delete s; const w = WIDGETS["msggrid"]; + Bangle.on("message", w.listener); })(); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 7ee07bebc..0a4765d9c 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -92,7 +92,8 @@ const INTERNAL_FILES_IN_APP_TYPE = { // list of app types and files they SHOULD }; /* These are warnings we know about but don't want in our output */ var KNOWN_WARNINGS = [ -"App gpsrec data file wildcard .gpsrc? does not include app ID" +"App gpsrec data file wildcard .gpsrc? does not include app ID", +"App widmessages storage file messagewidget is also listed as storage file for app widmsggrid", ]; function globToRegex(pattern) {