BangleApps/apps/messages/lib.js

235 lines
7.5 KiB
JavaScript

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 (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
{t:"remove",id:int} // remove
{t:"modify",id:int, title:string} // modified
*/
exports.pushMessage = function(event) {
// now modify/delete as appropriate
if (event.t==="remove") {
if (event.id==="music") exports.music = {};
} else { // add/modify
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);
}
// 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);
}
}
// 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); // add at the beginning
} 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 (!global.WIDGETS || !WIDGETS["messages"]) return; // widget is missing!
const method = WIDGETS["messages"][show ? "show" : "hide"];
/* if (typeof(method)!=="function") return; // widget must always have show()+hide(), fail hard rather than hide problems */
method.apply(WIDGETS["messages"]);
};
/**
* Replace all stored messages
* @param {array} messages Messages to save
*/
exports.write = function(messages) {
if (!messages.length) require("Storage").erase("messages.json");
else 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() {
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(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(withMessage) {
try {
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 callers with errors
}
};
/**
* 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
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) return Promise.resolve();
let repeat = msgSettings.repeat;
if (repeat===undefined) repeat = 4; // repeat may be zero
if (repeat) {
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);
};
/**
* Stop buzzing
*/
exports.stopBuzz = function() {
if (exports.buzzTimeout) clearTimeout(exports.buzzTimeout);
delete exports.buzzTimeout;
if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
delete exports.stopTimeout;
};