mirror of https://github.com/espruino/BangleApps
ability to depend on a specific app ID
Layout can display images in buttons iOS and Android integration appspull/878/head
parent
47ba763a9d
commit
935d409f4c
|
@ -230,6 +230,7 @@ and which gives information about the app for the Launcher.
|
|||
"tags": "", // comma separated tag list for searching
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
|
|
43
apps.json
43
apps.json
|
@ -32,17 +32,50 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.01",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge (BETA)",
|
||||
"version": "0.02",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
"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"}
|
||||
{"name":"messages.wid.js","url":"widget.js"},
|
||||
{"name":"messages","url":"lib.js"}
|
||||
],
|
||||
"sortorder": -9
|
||||
},
|
||||
{
|
||||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"version": "0.01",
|
||||
"description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications",
|
||||
"dependencies": {"messages":"app"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"sortorder": -9
|
||||
},
|
||||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.01",
|
||||
"description": "(BETA) App to display notifications from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
"dependencies": {"messages":"app"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"ios.app.js","url":"app.js"},
|
||||
{"name":"ios.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"ios.boot.js","url":"boot.js"}
|
||||
],
|
||||
"sortorder": -9
|
||||
},
|
||||
|
@ -216,7 +249,7 @@
|
|||
"id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"version": "0.24",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android. This will eventually be replaced by the 'Android' app.",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
"tags": "tool,system,android,widget",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4MA///xF9FstggwFDuEOAoc//gFJv/+AoZHBAgUB8/nwAFCBYIFCgYFB4AFHABdjCIPGAoPzAoPPAvpHFMpYFFPosAnk8NgYFdjEYfMo="))
|
|
@ -0,0 +1,2 @@
|
|||
// Config app not implemented yet
|
||||
load("messages.app.js");
|
Binary file not shown.
After Width: | Height: | Size: 636 B |
|
@ -0,0 +1,55 @@
|
|||
(function() {
|
||||
function gbSend(message) {
|
||||
Bluetooth.println("");
|
||||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},event));
|
||||
|
||||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
"notify" : function() { event.t="add";require("messages").pushMessage(event); },
|
||||
// {t:"notify~",id:int, title:string} // modified
|
||||
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
|
||||
// {t:"notify-",id:int} // remove
|
||||
"notify-" : function() { event.t="remove";require("messages").pushMessage(event); },
|
||||
// {t:"find", n:bool} // find my phone
|
||||
"find" : function() {
|
||||
if (Bangle.findDeviceInterval) {
|
||||
clearInterval(Bangle.findDeviceInterval);
|
||||
delete Bangle.findDeviceInterval;
|
||||
}
|
||||
if (event.n) // Ignore quiet mode: we always want to find our watch
|
||||
Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000);
|
||||
},
|
||||
// {t:"musicstate", state:"play/pause",position,shuffle,repeat}
|
||||
"musicstate" : function() {
|
||||
require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state});
|
||||
},
|
||||
// {t:"musicinfo", artist,album,track,dur,c(track count),n(track num}
|
||||
"musicinfo" : function() {
|
||||
require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"}));
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
if (h) h(); else console.log("GB Unknown",event);
|
||||
};
|
||||
|
||||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
gbSend({ t: "act", stp: health.steps, hrm: health.bpm });
|
||||
});
|
||||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play/pause/next/previous/volumeup/volumedown
|
||||
gbSend({ t: "music", m:cmd });
|
||||
}
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwZC/AGEB/4AGwARHv4RH/wQGj4QHAAP4CIoQJAAIRWg4RL8ARVn4RL/gR/CJv9BIP934DFEZH+v/0AgMv+wRK+YCBz/7C4PfCJOfAQO//JHMCIX3/d/CJ//t4RJF4JlCCIP/koRKEYh+DCIxlBCIQADCJQgCn4DCCJSbBHIIDBXYQRI/+Sp4DB7ZsCfdQRzg4RL8ARVgARLCAgRSj4QJ/ARFgF/CA/+CA0AgIRHwARHAH4AnA"))
|
|
@ -0,0 +1,2 @@
|
|||
// Config app not implemented yet
|
||||
load("messages.app.js");
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,129 @@
|
|||
bleServiceOptions.ancs = true;
|
||||
Bangle.ancsMessageQueue = [];
|
||||
|
||||
/* Handle ANCS events coming in, and fire off 'notify' events
|
||||
when we actually have all the information we need */
|
||||
E.on('ANCS',msg=>{
|
||||
/* eg:
|
||||
{
|
||||
event:"add",
|
||||
uid:42,
|
||||
category:4,
|
||||
categoryCnt:42,
|
||||
silent:true,
|
||||
important:false,
|
||||
preExisting:true,
|
||||
positive:false,
|
||||
negative:true
|
||||
} */
|
||||
|
||||
//console.log("ANCS",msg.event,msg.id);
|
||||
// don't need info for remove events - pass these on
|
||||
if (msg.event=="remove")
|
||||
return E.emit("notify", msg);
|
||||
|
||||
// not a remove - we need to get the message info first
|
||||
function ancsHandler() {
|
||||
var msg = Bangle.ancsMessageQueue[0];
|
||||
NRF.ancsGetNotificationInfo( msg.uid ).then( info => {
|
||||
E.emit("notify", Object.assign(msg, info));
|
||||
Bangle.ancsMessageQueue.shift();
|
||||
if (Bangle.ancsMessageQueue.length)
|
||||
ancsHandler();
|
||||
});
|
||||
}
|
||||
Bangle.ancsMessageQueue.push(msg);
|
||||
// if this is the first item in the queue, kick off ancsHandler,
|
||||
// otherwise ancsHandler will handle the rest
|
||||
if (Bangle.ancsMessageQueue.length==1)
|
||||
ancsHandler();
|
||||
});
|
||||
|
||||
// Handle ANCS events with all the data
|
||||
E.on('notify',msg=>{
|
||||
/* Info from ANCS event plus
|
||||
"uid" : int,
|
||||
"appId" : string,
|
||||
"title" : string,
|
||||
"subtitle" : string,
|
||||
"message" : string,
|
||||
"messageSize" : string,
|
||||
"date" : string,
|
||||
"posAction" : string,
|
||||
"negAction" : string,
|
||||
"name" : string,
|
||||
*/
|
||||
var appNames = {
|
||||
"com.netflix.Netflix" : "Netflix",
|
||||
"com.google.ios.youtube" : "YouTube",
|
||||
"com.google.hangouts" : "Hangouts"
|
||||
// could also use NRF.ancsGetAppInfo(msg.appId) here
|
||||
};
|
||||
var unicodeRemap = {
|
||||
'2019':"'"
|
||||
};
|
||||
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
|
||||
if (appNames[msg.appId]) msg.a
|
||||
require("messages").pushMessage({
|
||||
t : msg.event,
|
||||
id : msg.uid,
|
||||
src : appNames[msg.appId] || msg.appId,
|
||||
title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
|
||||
subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
|
||||
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer)
|
||||
});
|
||||
// TODO: posaction/negaction?
|
||||
});
|
||||
|
||||
// Apple media service
|
||||
E.on('AMS',a=>{
|
||||
function push(m) {
|
||||
var msg = { t : "modify", id : "music", title:"Music" };
|
||||
if (a.id=="artist") msg.artist = m;
|
||||
else if (a.id=="album") msg.artist = m;
|
||||
else if (a.id=="title") msg.tracl = m;
|
||||
else return; // duration? need to reformat
|
||||
require("messages").pushMessage(msg);
|
||||
}
|
||||
if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push)
|
||||
else push(a.value);
|
||||
});
|
||||
|
||||
// Music control
|
||||
Bangle.musicControl = cmd => {
|
||||
// play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark
|
||||
NRF.amsCommand(cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
// For testing...
|
||||
|
||||
NRF.ancsGetNotificationInfo = function(uid) {
|
||||
print("ancsGetNotificationInfo",uid);
|
||||
return Promise.resolve({
|
||||
"uid" : uid,
|
||||
"appId" : "Hangouts",
|
||||
"title" : "Hello",
|
||||
"subtitle" : "There",
|
||||
"message" : "Lots and lots of text",
|
||||
"messageSize" : 100,
|
||||
"date" : "...",
|
||||
"posAction" : "ok",
|
||||
"negAction" : "cancel",
|
||||
"name" : "Fred",
|
||||
});
|
||||
};
|
||||
|
||||
E.emit("ANCS", {
|
||||
event:"add",
|
||||
uid:42,
|
||||
category:4,
|
||||
categoryCnt:42,
|
||||
silent:true,
|
||||
important:false,
|
||||
preExisting:true,
|
||||
positive:false,
|
||||
negative:true
|
||||
});
|
||||
|
||||
*/
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Add 'messages' library
|
|
@ -12,15 +12,10 @@
|
|||
|
||||
/* 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"})
|
||||
// a message
|
||||
{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}
|
||||
// maps
|
||||
{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="}
|
||||
|
||||
*/
|
||||
|
||||
|
@ -37,6 +32,21 @@ function saveMessages() {
|
|||
require("Storage").writeJSON("messages.json",MESSAGES)
|
||||
}
|
||||
|
||||
function getBackImage() {
|
||||
return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA==");
|
||||
}
|
||||
function getMessageImage(msg) {
|
||||
if (msg.img) return atob(msg.img);
|
||||
var s = (msg.src||"").toLowerCase();
|
||||
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
|
||||
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
||||
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
||||
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
||||
if (msg.id=="back") return getBackImage();
|
||||
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
|
||||
}
|
||||
|
||||
|
||||
function showMapMessage(msg) {
|
||||
var m;
|
||||
var distance, street, target, eta;
|
||||
|
@ -50,48 +60,98 @@ function showMapMessage(msg) {
|
|||
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 }
|
||||
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: [
|
||||
msg.img?{type:"img",src:atob(msg.img), scale:2}:{},
|
||||
{type:"v", fillx:1, c: [
|
||||
{type:"txt", font:"6x15:2", label:distance||"" }
|
||||
]},
|
||||
{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);
|
||||
]},
|
||||
{type:"txt", font:"6x8:2", label:eta }
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
Bangle.setUI("updown",function() {
|
||||
// any input to mark as not new and return to menu
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
layout = undefined;
|
||||
checkMessages();
|
||||
});
|
||||
}
|
||||
|
||||
function showMusicMessage(msg) {
|
||||
function fmtTime(s) {
|
||||
var m = Math.floor(s/60);
|
||||
s = (s%60).toString().padStart(2,0);
|
||||
return m+":"+s;
|
||||
}
|
||||
|
||||
function back() {
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
layout = undefined;
|
||||
checkMessages();
|
||||
}
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"h", fillx:1, bgCol:"#0f0", c: [
|
||||
{ type:"btn", src:getBackImage, cb:back },
|
||||
{ type:"v", fillx:1, c: [
|
||||
{ type:"txt", font:"6x15:2", label:msg.artist, pad:2 },
|
||||
{ type:"txt", font:"6x15", label:msg.album, pad:2 }
|
||||
]}
|
||||
]},
|
||||
{type:"txt", font:"6x15:2", label:msg.track, fillx:1, filly:1, pad:2 },
|
||||
Bangle.musicControl?{type:"h",fillx:1, c: [
|
||||
{type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play
|
||||
{type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause
|
||||
{type:"btn", pad:8, label:"\0"+atob("EhKBAMAB+AB/gB/wB/8B/+B//B//x//5//5//x//B/+B/8B/wB/gB+AB8ABw"), cb:()=>Bangle.musicControl("next")}, // next
|
||||
]}:{},
|
||||
{type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" }
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
if (msg.id=="music") return showMusicMessage(msg);
|
||||
// Normal text message display
|
||||
var title=msg.title, titleFont = "6x15:2";
|
||||
if (title) {
|
||||
var w = g.getWidth()-40;
|
||||
if (g.setFont(titleFont).stringWidth(title) > w)
|
||||
titleFont = "6x15";
|
||||
if (g.setFont(titleFont).stringWidth(title) > w)
|
||||
title = g.wrapString(title, w).join("\n");
|
||||
}
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"h", fillx:1, bgCol:"#0f0", c: [
|
||||
{ type:"img", src:getMessageImage(msg), pad:2 },
|
||||
{ type:"v", fillx:1, c: [
|
||||
{type:"txt", font:"6x15", label:msg.src||"Message", bgCol:"#0f0", fillx:1, pad:2 },
|
||||
title?{type:"txt", font:titleFont, label:title, bgCol:"#0f0", fillx:1, pad:2 }:{},
|
||||
]},
|
||||
]},
|
||||
{type:"txt", font:"6x15", label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 },
|
||||
{type:"h",fillx:1, c: [
|
||||
{type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back
|
||||
msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{
|
||||
msg.new = false; // read mail
|
||||
saveMessages();
|
||||
checkMessages();
|
||||
}}:{}
|
||||
]}
|
||||
]});
|
||||
g.clearRect(Bangle.appRect);
|
||||
layout.render();
|
||||
}
|
||||
|
||||
function checkMessages(forceShowMenu) {
|
||||
|
@ -112,24 +172,35 @@ function checkMessages(forceShowMenu) {
|
|||
// Otherwise show a menu
|
||||
E.showScroller({
|
||||
h : 48,
|
||||
c : MESSAGES.length,
|
||||
c : MESSAGES.length+1,
|
||||
draw : function(idx, r) {"ram"
|
||||
var msg = MESSAGES[idx-1];
|
||||
if (msg && msg.new) g.setBgColor("#4F4");
|
||||
else g.setBgColor((idx&1) ? "#CFC" : "#9F9");
|
||||
g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg);
|
||||
if (idx==0) msg = {title:"< Back"};
|
||||
if (idx==0) msg = {id:"back", title:"< Back"};
|
||||
if (!msg) return;
|
||||
var x = r.x+2, title = msg.title, body = msg.body;
|
||||
var img = getMessageImage(msg);
|
||||
if (msg.id=="music") {
|
||||
title = msg.artist || "Music";
|
||||
body = msg.track;
|
||||
}
|
||||
if (img) {
|
||||
g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering
|
||||
x += 50;
|
||||
}
|
||||
var m = msg.title+"\n"+msg.body;
|
||||
if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2);
|
||||
if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2);
|
||||
if (msg.body) {
|
||||
if (title) g.setFontAlign(-1,-1).setFont("12x20").drawString(title, x,r.y+2);
|
||||
if (body) {
|
||||
g.setFontAlign(-1,-1).setFont("6x8");
|
||||
var l = g.wrapString(msg.body, r.w-14);
|
||||
var l = g.wrapString(body, r.w-14);
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
g.drawString(l.join("\n"), r.x+12,r.y+20);
|
||||
g.drawString(l.join("\n"), x+10,r.y+20);
|
||||
}
|
||||
},
|
||||
select : idx => {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
(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,0 +1,37 @@
|
|||
exports.pushMessage = function(event) {
|
||||
/* event is:
|
||||
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new
|
||||
{t:"add",id:int, id:"music", state, artist, track, etc} // add new
|
||||
{t:"remove-",id:int} // remove
|
||||
{t:"modify",id:int, title:string} // modified
|
||||
*/
|
||||
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=="remove") {
|
||||
if (mIdx>=0) messages.splice(mIdx, 1); // remove item
|
||||
mIdx=-1;
|
||||
} else { // add/modify
|
||||
if (event.t=="add") 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 in app, process immediately
|
||||
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
|
||||
// ok, saved now - we only care if it's new
|
||||
if (event.t!="add") return;
|
||||
// otherwise load after a delay, 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 (Bangle.CLOCK || event.important) return load("messages.app.js");
|
||||
if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
|
||||
WIDGETS.messages.newMessage();
|
||||
}, 500);
|
||||
}
|
2
core
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 5ef454a1acce54f6420015b519a7ecf461f9bc37
|
||||
Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e
|
|
@ -29,7 +29,9 @@ layoutObject has:
|
|||
* `undefined` - blank, can be used for padding
|
||||
* `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required
|
||||
* `"btn"` - a button, with value `label` and callback `cb`
|
||||
* `"img"` - an image where `src` is an image, or a function which is called to return an image to draw
|
||||
optional `src` specifies an image (like img) in which case label is ignored
|
||||
* `"img"` - an image where `src` is an image, or a function which is called to return an image to draw.
|
||||
optional `scale` specifies if image should be scaled up or not
|
||||
* `"custom"` - a custom block where `render(layoutObj)` is called to render
|
||||
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
|
||||
* `"v"` - Veritical layout, `c` is an array of more `layoutObject`
|
||||
|
@ -85,6 +87,7 @@ function Layout(layout, options) {
|
|||
this.lazy = options.lazy || false;
|
||||
|
||||
var btnList;
|
||||
Bangle.setUI(); // remove all existing input handlers
|
||||
if (process.env.HWVERSION!=2) {
|
||||
// no touchscreen, find any buttons in 'layout'
|
||||
btnList = [];
|
||||
|
@ -157,6 +160,7 @@ function Layout(layout, options) {
|
|||
}
|
||||
}
|
||||
if (process.env.HWVERSION==2) {
|
||||
|
||||
// Handler for touch events
|
||||
function touchHandler(l,e) {
|
||||
if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) {
|
||||
|
@ -245,10 +249,8 @@ Layout.prototype.render = function (l) {
|
|||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||
}
|
||||
}, "btn":function(l){
|
||||
var x = l.x+(0|l.pad);
|
||||
var y = l.y+(0|l.pad);
|
||||
var w = l.w-(l.pad<<1);
|
||||
var h = l.h-(l.pad<<1);
|
||||
var x = l.x+(0|l.pad), y = l.y+(0|l.pad),
|
||||
w = l.w-(l.pad<<1), h = l.h-(l.pad<<1);
|
||||
var poly = [
|
||||
x,y+4,
|
||||
x+4,y,
|
||||
|
@ -259,10 +261,12 @@ Layout.prototype.render = function (l) {
|
|||
x+4,y+h-1,
|
||||
x,y+h-5,
|
||||
x,y+4
|
||||
];
|
||||
g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
||||
], bg = l.selected?g.theme.bgH:g.theme.bg2;
|
||||
g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly);
|
||||
if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad));
|
||||
else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
||||
}, "img":function(l){
|
||||
g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad));
|
||||
g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined);
|
||||
}, "custom":function(l){
|
||||
l.render(l);
|
||||
},"h":function(l) { l.c.forEach(render); },
|
||||
|
@ -363,12 +367,13 @@ Layout.prototype.update = function() {
|
|||
l._w = m.width; l._h = m.height;
|
||||
}
|
||||
}, "btn": function(l) {
|
||||
l._h = 32;
|
||||
l._w = 20 + l.label.length*12;
|
||||
var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label);
|
||||
l._h = 16 + m.height;
|
||||
l._w = 20 + m.width;
|
||||
}, "img": function(l) {
|
||||
var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image
|
||||
l._w = m.width;
|
||||
l._h = m.height;
|
||||
var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image
|
||||
l._w = m.width*s;
|
||||
l._h = m.height*s;
|
||||
}, "": function(l) {
|
||||
// size should already be set up in width/height
|
||||
l._w = 0;
|
||||
|
|
Loading…
Reference in New Issue