diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 48d5234fe9..26db73da16 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -119,3 +119,4 @@ 0.87: Make choosing of font size more repeatable 0.88: Adjust padding calculation so messages are spaced out properly even when using international fonts 0.89: Fix bugs related to empty titles and bodies +0.90: Persist music info; Fix old music info copying into new music messages diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 5b30957304..acf3864c66 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -67,19 +67,52 @@ if (Bangle.MESSAGES) { delete Bangle.MESSAGES; } +// Update or add music message to MESSAGES array at front +function updateMusicMessage(musicMsg) { + var existingMusicIdx = MESSAGES.findIndex(m => m.id === "music"); + if (existingMusicIdx >= 0) { + MESSAGES[existingMusicIdx] = musicMsg; + if (existingMusicIdx !== 0) MESSAGES.unshift(MESSAGES.splice(existingMusicIdx, 1)[0]); + } else { + MESSAGES.unshift(musicMsg); + } +} + +// Load music message from storage if it exists +var musicMsg = require("messages").getMusic(); +if (musicMsg && (musicMsg.track || musicMsg.artist)) { + updateMusicMessage(musicMsg); +} + var onMessagesModified = function(type,msg) { if (msg.handled) return; msg.handled = true; - require("messages").apply(msg, MESSAGES); + + if (msg.id === "music") { + // For music messages, get the complete state from messages module + updateMusicMessage(require("messages").getMusic()); + var musicMsg = MESSAGES[0]; // music is now at front + + if (musicMsg.state && musicMsg.state=="play") { + openMusic = true; + } else if (musicMsg.state && musicMsg.state!="play") { + openMusic = false; // no longer playing music to go back to + } + if ((active!=undefined) && (active!="list") && (active!="music")) return; // don't open music over other screens (but do if we're in the main menu) + } else { + require("messages").apply(msg, MESSAGES); + // Move music back to front since apply() may have pushed it down + var musicIdx = MESSAGES.findIndex(m => m.id === "music"); + if (musicIdx > 0) { + MESSAGES.unshift(MESSAGES.splice(musicIdx, 1)[0]); + } + } + // TODO: if new, show this new one if (msg && msg.id!=="music" && msg.id!=="nav" && msg.new && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { 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 - if ((active!=undefined) && (active!="list") && (active!="music")) return; // don't open music over other screens (but do if we're in the main menu) - } if (msg && msg.id=="nav" && msg.t=="modify" && active!="map") return; // don't show an updated nav message if we're just in the menu showMessage(msg&&msg.id, false); @@ -518,6 +551,8 @@ function showMessage(msgid, persist) { */ function checkMessages(options) { options=options||{}; + // Remove/ignore stale music messages if they haven't played recently + checkMusicExpired(); // If there's been some user interaction, it's time to stop repeated buzzing if (!options.dontStopBuzz) require("messages").stopBuzz(); @@ -617,6 +652,20 @@ function returnToClockIfEmpty() { checkMessages({clockIfNoMsg:1,clockIfAllRead:0,ignoreUnread:1,openMusic}); } +function checkMusicExpired() { + var musicTimeout = (settings && settings.musicTimeoutMinutes) ? settings.musicTimeoutMinutes : 5; + if (!isFinite(musicTimeout) || musicTimeout <= 0) return; + + var now = Date.now(); + MESSAGES = MESSAGES.filter(function(m) { + if (!m || m.id!="music") return true; + if (m.state=="play" || m.state=="show") return true; + if (m._lastPlayed && (now - m._lastPlayed) <= musicTimeout*60000) return true; + // otherwise drop the stale music message + return false; + }); +} + function cancelReloadTimeout() { if (!unreadTimeout) return; clearTimeout(unreadTimeout); diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js index 43141531f3..02c07867b4 100644 --- a/apps/messagegui/lib.js +++ b/apps/messagegui/lib.js @@ -37,8 +37,13 @@ exports.listener = function(type, msg) { const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; let loadMessages = (Bangle.CLOCK || msg.important); // should we load the messages app? if (type==="music") { - if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true; - else return; + // Music persistence is handled by messages module via pushMessage + msg.handled = true; + if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) { + loadMessages = true; + } else { + return; // handled + } } // Write the message to Bangle.MESSAGES. We'll deal with it in messageTimeout below if (!Bangle.MESSAGES) Bangle.MESSAGES = []; @@ -48,7 +53,9 @@ exports.listener = function(type, msg) { // save messages from RAM to flash if we decide not to launch app // We apply all of Bangle.MESSAGES here in one write if (!Bangle.MESSAGES || !Bangle.MESSAGES.length) return; - let messages = require("messages").getMessages(msg); + // Load saved messages without applying the current msg to avoid + // applying it twice (getMessages(msg) would apply it already). + let messages = require("messages").getMessages(); (Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, messages)); require("messages").write(messages); delete Bangle.MESSAGES; @@ -104,4 +111,4 @@ exports.open = function(msg) { } Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js"); -}; +}; \ No newline at end of file diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 8ef38bd063..a1752355d8 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.89", + "version": "0.90", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 1ea1fd9678..4be47db0c1 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -12,3 +12,4 @@ 0.66: Fix 'Auto-Open Unread Msg' polarity - previously checking the box would ignore unread messages 0.67: Ensure default vibration pattern is longer Add Option to show widgets (Message GUI 0.86 removes them by default) +0.68: Fix perserving music info between messages diff --git a/apps/messages/lib.js b/apps/messages/lib.js index e22e89cc46..7a3eca47d5 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -1,4 +1,10 @@ exports.music = {}; + +// Track if music has been modified and needs saving +let musicLoaded = false; +let musicDirty = false; +let killHandlerSet = false; + /** * Emit "message" event with appropriate type from Bangle * @param {object} msg @@ -24,14 +30,18 @@ exports.pushMessage = function(event) { 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); + // For non-music messages, merge with stored message + // For music, skip merging to avoid old info + if (event.id !== "music") { + const old = exports.getMessages().find(m => m.id===event.id); + if (old) event = Object.assign(old, event); + } } // 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); + setMusic(event); + event = getMusic(); } } // reset state (just in case) @@ -72,7 +82,7 @@ exports.apply = function(event, messages) { messages.splice(mIdx, 1); } else if (event.t==="add") { if (mIdx>=0) messages.splice(mIdx, 1); // duplicate ID! erase previous version - messages.unshift(event); // add at the beginning + messages.unshift(Object.assign({}, event)); // add a copy at the beginning } else if (event.t==="modify") { if (mIdx>=0) messages[mIdx] = Object.assign(messages[mIdx], event); else messages.unshift(event); @@ -235,3 +245,98 @@ exports.stopBuzz = function() { if (exports.stopTimeout) clearTimeout(exports.stopTimeout); delete exports.stopTimeout; }; + +/** + * Lazy-load music from messages.music.json if not already loaded + */ +function loadMusic() { + if (musicLoaded) return; + const stored = require("Storage").readJSON("messages.music.json", true); + if (stored) { + exports.music = stored; + } + musicLoaded = true; +} + +/** + * Save music info to messages.music.json if dirty + */ +function saveMusicToFlash() { + if (!musicDirty) return; + + // Debug counter to track saves + incrementSaveCounter("music"); + + // Only save if we have actual music data + if (Object.keys(exports.music).length > 0) { + require("Storage").writeJSON("messages.music.json", exports.music); + } else { + require("Storage").erase("messages.music.json"); + } + + musicDirty = false; +} + +/** + * Set music info - merges music message data into exports.music, saves to flash on kill + * @param {object} msg Music message + */ +function setMusic(msg) { + // Lazy-load existing music data + loadMusic(); + + // Merge in artist/track/album/dur if provided + if (msg.artist !== undefined) exports.music.artist = msg.artist; + if (msg.track !== undefined) exports.music.track = msg.track; + if (msg.album !== undefined) exports.music.album = msg.album; + if (msg.dur !== undefined) exports.music.dur = msg.dur; + + // Merge in state and update _lastPlayed timestamp when playing + if (msg.state !== undefined) { + exports.music.state = msg.state; + if (msg.state === "play") { + exports.music._lastPlayed = Date.now(); + } + } + + // If this is a musicstate message and we don't have track info yet, + // set track to "Music" so we can trigger displaying music controls + if (msg.state && !exports.music.track) { + exports.music.track = "Music"; + exports.music.title = exports.music.title || "Music"; + } + + // Mark as dirty so it gets saved on kill + musicDirty = true; + + // Set up kill handler only once + if (!killHandlerSet) { + E.on("kill", saveMusicToFlash); + killHandlerSet = true; + } +} + +/** + * Get current music info with event metadata + * @returns {object} Music message object + */ +function getMusic() { + loadMusic(); + const event = Object.assign({ id: "music" }, exports.music); + if (event.state === "play") event.new = true; // new track, or playback (re)started + return event; +} + +/** + * Get current music message + * @returns {object} Music message object with id, state, track, artist, etc. + */ +exports.getMusic = getMusic; + +function incrementSaveCounter(name) { + const storage = require("Storage"); + let stats = storage.readJSON("message_stats.json", true) || {}; + if (!stats[name]) stats[name] = 0; + stats[name]++; + storage.writeJSON("message_stats.json", stats); +} \ No newline at end of file diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 4b25d8398e..85a67c92d7 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.67", + "version": "0.68", "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", "type": "module", diff --git a/apps/messages/settings.js b/apps/messages/settings.js index 5690dde79a..b37d899881 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -67,6 +67,12 @@ value: !!settings.openMusic, onchange: v => updateSetting("openMusic", v) }, + /*LANG*/'Music Msg Timeout': { + value: (settings && settings.musicTimeoutMinutes!=null) ? settings.musicTimeoutMinutes : 5, + min: 0, max: 240, step: 1, + format: v => v ? v+/*LANG*/"m" : /*LANG*/"Off", + onchange: v => updateSetting("musicTimeoutMinutes", v) + }, /*LANG*/'Unlock Watch': { value: !!settings.unlockWatch, onchange: v => updateSetting("unlockWatch", v)