From 98a6bd7d43eed3f57b10d1cdbeab7eedc0150d02 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 11:47:23 +0100 Subject: [PATCH] Adding BETA messages app --- apps.json | 20 ++- apps/messages/README.md | 21 +++ apps/messages/app-icon.js | 1 + apps/messages/app.js | 273 ++++++++++++++++++++++++++++++++++++++ apps/messages/app.png | Bin 0 -> 917 bytes apps/messages/boot.js | 36 +++++ apps/messages/lib.js | 0 apps/messages/widget.js | 20 +++ 8 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 apps/messages/README.md create mode 100644 apps/messages/app-icon.js create mode 100644 apps/messages/app.js create mode 100644 apps/messages/app.png create mode 100644 apps/messages/boot.js create mode 100644 apps/messages/lib.js create mode 100644 apps/messages/widget.js diff --git a/apps.json b/apps.json index d6dc915e9..a372476a0 100644 --- a/apps.json +++ b/apps.json @@ -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", diff --git a/apps/messages/README.md b/apps/messages/README.md new file mode 100644 index 000000000..c243ec06a --- /dev/null +++ b/apps/messages/README.md @@ -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 diff --git a/apps/messages/app-icon.js b/apps/messages/app-icon.js new file mode 100644 index 000000000..6ed3c1141 --- /dev/null +++ b/apps/messages/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA=")) diff --git a/apps/messages/app.js b/apps/messages/app.js new file mode 100644 index 000000000..d369ee175 --- /dev/null +++ b/apps/messages/app.js @@ -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=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{ + 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 { 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(); diff --git a/apps/messages/app.png b/apps/messages/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/messages/boot.js b/apps/messages/boot.js new file mode 100644 index 000000000..dce3979da --- /dev/null +++ b/apps/messages/boot.js @@ -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(); + }; +})() diff --git a/apps/messages/lib.js b/apps/messages/lib.js new file mode 100644 index 000000000..e69de29bb diff --git a/apps/messages/widget.js b/apps/messages/widget.js new file mode 100644 index 000000000..eda4a85a5 --- /dev/null +++ b/apps/messages/widget.js @@ -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 +}};