Adding BETA messages app

master
Gordon Williams 2021-10-22 11:47:23 +01:00
parent 1942d5d684
commit 98a6bd7d43
8 changed files with 370 additions and 1 deletions

View File

@ -31,6 +31,23 @@
],
"sortorder": -10
},
{
"id": "messages",
"name": "Messages",
"version": "0.01",
"description": "App to display notifications from iOS and Gadgetbridge (BETA)",
"icon": "app.png",
"tags": "tool,system",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"messages.app.js","url":"app.js"},
{"name":"messages.img","url":"app-icon.js","evaluate":true},
{"name":"messages.boot.js","url":"boot.js"},
{"name":"messages.wid.js","url":"widget.js"}
],
"sortorder": -9
},
{
"id": "health",
"name": "Health Tracking",
@ -46,8 +63,9 @@
{"name":"health.boot.js","url":"boot.js"},
{"name":"health","url":"lib.js"}
],
"sortorder": -10
"sortorder": -8
},
{
"id": "moonphase",
"name": "Moonphase",

21
apps/messages/README.md Normal file
View File

@ -0,0 +1,21 @@
# Messages app
**THIS APP IS CURRENTLY BETA**
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.
It is a replacement for the old `notify`/`gadgetbridge` apps.
## Usage
...
## Requests
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app
## Creator
Gordon Williams

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA="))

273
apps/messages/app.js Normal file
View File

@ -0,0 +1,273 @@
/* MESSAGES is a list of:
{id:int,
src,
title,
subject,
body,
sender,
tel:string,
new:true // not read yet
}
*/
/* For example for maps:
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."})
GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."})
GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"})
GB({"t":"notify-","id":1})
GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="})
GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"})
GB({"t":"notify~","id":1,"title":"High St"})
GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"})
GB({"t":"notify~","id":1,"title":"0 yd - High St"})
GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"})
*/
var Layout = require("Layout");
var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
if (!Array.isArray(MESSAGES)) MESSAGES=[];
var onMessagesModified = function(msg) {
// TODO: if new, show this new one
if (msg.new) Bangle.buzz();
showMessage(msg.id);
};
function saveMessages() {
require("Storage").writeJSON("messages.json",MESSAGES)
}
function showMapMessage(msg) {
var m;
var distance, street, target, eta;
m=msg.title.match(/(.*) - (.*)/);
if (m) {
distance = m[1];
street = m[2];
} else street=msg.title;
m=msg.body.match(/(.*) - (.*)/);
if (m) {
target = m[1];
eta = m[2];
} else target=msg.body;
layout = new Layout({
type:"v", c: [
{type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 },
{type:"h", bgCol:"#0f0", fillx:1, c: [
{type:"txt", font:"6x8", label:"Towards" },
{type:"txt", font:"6x15:2", label:street }
]},
{type:"h",fillx:1, filly:1, c: [
{type:"img",src:atob(msg.img)},
{type:"v", fillx:1, c: [
{type:"txt", font:"6x15:2", label:distance||"" }
]},
]},
{type:"txt", font:"6x8:2", label:eta }
]
});
g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1);
layout.render();
Bangle.setUI("updown",function() {
// any input to mark as not new and return to menu
msg.new = false;
saveMessages();
checkMessages();
});
}
function showMessage(msgid) {
var msg = MESSAGES.find(m=>m.id==msgid);
if (!msg) return checkMessages(); // go home if no message found
if (msg.src=="Maps") return showMapMessage(msg);
var m = msg.title+"\n"+msg.body;
E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => {
if (chosen=="read") {
// any input to mark as not new and return to menu
msg.new = false;
saveMessages();
checkMessages();
} else {
checkMessages(true);
}
});
}
// Show a single menu item for the message
function showMessageMenuItem(y, idx) {
var msg = MESSAGES[idx];
var W = g.getWidth(), H=48;
if (msg.new) g.setBgColor("#4F4");
else g.setBgColor("#CFC");
g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg);
var m = msg.title+"\n"+msg.body;
if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2);
if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2);
if (msg.body) {
g.setFontAlign(-1,-1).setFont("6x8");
var l = g.wrapString(msg.body, W-14);
if (l.length>3) {
l = l.slice(0,3);
l[l.length-1]+="...";
}
g.drawString(l.join("\n"), 12,y+20);
}
}
//test
//g.clear(1); showMessageMenuItem(MESSAGES[0],24)
if (process.env.HWVERSION==1) { // Bangle.js 1
showBigMenu = function(options) {
/* options = {
h = height
items = # of items
draw = function(y, idx)
onSelect = function(idx)
}*/
var selected = 0;
var menuScroll = 0;
var menuShowing = false;
var w = g.getWidth();
var h = g.getHeight();
var m = w/2;
var n = Math.floor((h-48)/options.h);
function drawMenu() {
g.reset();
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
if (selected<menuScroll) menuScroll = selected;
// draw
g.setColor(g.theme.fg);
for (var i=0;i<n;i++) {
var idx = i+menuScroll;
if (idx<0 || idx>=options.items) break;
var y = 24+i*options.h;
options.draw(y, idx);
// border for selected
if (i+menuScroll==selected) {
g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2);
}
}
// arrows
g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
g.fillPoly([m,6,m-14,20,m+14,20]);
g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg);
g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
}
g.clearRect(0,24,w-1,h-1);
drawMenu();
Bangle.setUI("updown",dir=>{
if (dir) {
selected += dir;
if (selected<0) selected = options.items-1;
if (selected>=options.items) selected = 0;
drawMenu();
} else {
options.onSelect(selected);
}
});
}
} else { // Bangle.js 2
showBigMenu = function(options) {
/* options = {
h = height
items = # of items
draw = function(y, idx)
onSelect = function(idx)
}*/
var menuScroll = 0;
var menuShowing = false;
var w = g.getWidth();
var h = g.getHeight();
var n = Math.ceil((h-24)/options.h);
var menuScrollMax = options.h*options.items - (h-24);
function drawItem(i) {
var y = 24+i*options.h-menuScroll;
if (i<0 || i>=options.items || y<-options.h || y>=h) return;
options.draw(y, i);
}
function drawMenu() {
g.reset().clearRect(0,24,w-1,h-1);
g.setClipRect(0,24,w-1,h-1);
for (var i=0;i<n;i++) drawItem(i);
g.setClipRect(0,0,w-1,h-1);
}
drawMenu();
g.flip(); // force an update now to make this snappier
Bangle.dragHandler = e=>{
var dy = e.dy;
if (menuScroll - dy < 0)
dy = menuScroll;
if (menuScroll - dy > menuScrollMax)
dy = menuScroll - menuScrollMax;
if (!dy) return;
g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
g.scroll(0,dy);
menuScroll -= dy;
if (e.dy < 0) {
drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1);
if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2);
} else {
drawItem(Math.floor((menuScroll+24)/options.h));
if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1);
}
g.setClipRect(0,0,w-1,h-1);
};
Bangle.on('drag',Bangle.dragHandler);
Bangle.touchHandler = (_,e)=>{
if (e.y<20) return;
var i = Math.floor((e.y+menuScroll-24) / options.h);
if (i>=0 && i<options.items)
options.onSelect(i);
};
Bangle.on("touch", Bangle.touchHandler);
}
}
function checkMessages(forceShowMenu) {
// If no messages, just show 'no messages' and return
if (!MESSAGES.length)
return E.showPrompt("No Messages",{
title:"Messages",
img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
buttons : {"Ok":1}
}).then(() => { load() });
// we have >0 messages
// TODO: IF A NEW MESSAGE, SHOW IT
if (!forceShowMenu) {
var newMessages = MESSAGES.filter(m=>m.new);
if (newMessages.length)
return showMessage(newMessages[0].id);
}
// Otherwise show a menu
var m = {
"":{title:"Messages"},
"< Back": ()=>load()
};
/*g.setFont("6x8");
MESSAGES.forEach(msg=>{
// "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"
var title = g.wrapString(msg.title, g.getWidth())[0];
m[title] = function() {
showMessage(msg.id);
}
});
E.showMenu(m);*/
showBigMenu({
h : 48,
items : MESSAGES.length,
draw : showMessageMenuItem,
onSelect : idx => showMessage(MESSAGES[idx].id)
});
}
Bangle.loadWidgets();
Bangle.drawWidgets();
checkMessages();

BIN
apps/messages/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

36
apps/messages/boot.js Normal file
View File

@ -0,0 +1,36 @@
(function() {
var _GB = global.GB;
global.GB = (event) => {
if (_GB) setTimeout(_GB,0,event);
// call handling?
if (!event.t.startsWith("notify")) return;
/* event is:
{t:"notify",id:int, src,title,subject,body,sender,tel:string}
{t:"notify~",id:int, title:string} // modified
{t:"notify-",id:int} // remove
*/
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)||[];
// now modify/delete as appropriate
var mIdx = messages.findIndex(m=>m.id==event.id);
if (event.t=="notify-") {
if (mIdx>=0) messages.splice(mIdx, 1); // remove item
mIdx=-1;
} else { // add/modify
if (event.t=="notify") event.new=true; // new message
if (mIdx<0) mIdx=messages.push(event)-1;
else Object.assign(messages[mIdx], event);
}
require("Storage").writeJSON("messages.json",messages);
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
// ok, saved now - we only care if it's new
if (event.t!="notify") return;
// if we're in a clock, go straight to messages app
if (Bangle.CLOCK) return load("messages.app.js");
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
WIDGETS.messages.newMessage();
};
})()

0
apps/messages/lib.js Normal file
View File

20
apps/messages/widget.js Normal file
View File

@ -0,0 +1,20 @@
WIDGETS["messages"]={area:"tl",width:0,draw:function() {
if (!this.width) return;
var c = (Date.now()-this.t)/1000;
g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff");
g.clearRect(this.x,this.y,this.x+this.width,this.y+23);
g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12);
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
if (c<120 && (Date.now()-this.l)>4000) {
this.l = Date.now();
Bangle.buzz(); // buzz every 4 seconds
}
setTimeout(()=>WIDGETS["messages"].draw(), 1000);
},newMessage:function() {
WIDGETS["messages"].t=Date.now(); // first time
WIDGETS["messages"].l=Date.now()-10000; // last buzz
if (WIDGETS["messages"].c!==undefined) return; // already called
WIDGETS["messages"].width=64;
Bangle.drawWidgets();
Bangle.setLCDPower(1);// turns screen on
}};