Skip to content

Commit 65d19d2

Browse files
author
thyttan
committed
Merge branch 'messagegui' into app-loader
2 parents 754704e + 426e6f5 commit 65d19d2

File tree

3 files changed

+177
-51
lines changed

3 files changed

+177
-51
lines changed

apps/messagegui/ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,4 @@
113113
0.82: Stop buzzing when a message is removed (e.g. call answered)
114114
0.83: Add option to not open the first unread message
115115
0.84: Fix: Assign show message entry to the settings menu and not the message itself.
116+
0.85: (WIP) refactor to display a scroller with three messages loaded. When scrolling to the top or end new/older messages are loaded in.

apps/messagegui/app.js

Lines changed: 175 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ if (Graphics.prototype.setFontIntl) {
4545
fontVLarge = noScale?"Intl":"Intl:3";
4646
}
4747

48-
var active; // active screen (undefined/"list"/"music"/"map"/"message"/"scroller"/"settings")
48+
var active; // active screen (undefined/"list"/"music"/"map"/"overview"/"scroller"/"settings")
4949
var openMusic = false; // go back to music screen after we handle something else?
5050
var replying = false; // If we're replying to a message, don't interrupt
51+
var persist = "messagegui.new.js"!==globalThis.__FILE__;
52+
5153
// hack for 2v10 firmware's lack of ':size' font handling
5254
try {
5355
g.setFont("6x8:2");
@@ -90,7 +92,7 @@ var onMessagesModified = function(type,msg) {
9092
}
9193
if (msg && msg.id=="nav" && msg.t=="modify" && active!="map")
9294
return; // don't show an updated nav message if we're just in the menu
93-
showMessage(msg&&msg.id, false);
95+
showMessageRouter(msg, persist, "dependsOnActive");
9496
};
9597
Bangle.on("message", onMessagesModified);
9698

@@ -99,6 +101,39 @@ function saveMessages() {
99101
}
100102
E.on("kill", saveMessages);
101103

104+
function showMessageRouter(msg, persist, explicitDestnation) {
105+
//explicitDestnation (undefined/"scroller"/"overview"/"dependsOnActive")
106+
107+
////var active; // active screen (undefined/"list"/"music"/"map"/"overview"/"scroller"/"settings")
108+
//if (active==undefined) { } else if (active=="list") ... //and so on.
109+
110+
if (persist) {cancelReloadTimeout();} else if (Bangle.isLocked()) {resetReloadTimeout();}
111+
112+
if (msg.id=="music") {
113+
cancelReloadTimeout(); // don't auto-reload to clock now
114+
return showMusicMessage(msg);
115+
}
116+
if (msg.id=="nav") {
117+
cancelReloadTimeout(); // don't auto-reload to clock now
118+
return showMapMessage(msg);
119+
}
120+
if (msg.id=="call") {
121+
return showMessageOverview(msg.id);
122+
}
123+
if ("scroller"===explicitDestnation) {
124+
return showMessagesScroller(msg);
125+
}
126+
if ("overview"===explicitDestnation) {
127+
return showMessageOverview(msg.id);
128+
}
129+
if ("dependsOnActive"===explicitDestnation) {
130+
if ("scroller"===active) {return showMessagesScroller(msg);} // reinit scroller with updated messages list.
131+
if ("list"===active) {return returnToMain();}
132+
if ("settings"===active || "overview"===active) {return;}
133+
}
134+
//if (false) {showMessageSettings(msg);}
135+
}
136+
102137
function showMapMessage(msg) {
103138
active = "map";
104139
require("messages").stopBuzz(); // stop repeated buzzing while the map is showing
@@ -246,44 +281,137 @@ function showMusicMessage(msg) {
246281
}, 400);
247282
}
248283

249-
function showMessageScroller(msg) {
250-
cancelReloadTimeout();
284+
function showMessagesScroller(msg) {
285+
const MSG_IDX = msg ? MESSAGES.findIndex((m)=>m.id==msg.id) : undefined;
286+
287+
if (replying) { return; }
251288
active = "scroller";
289+
290+
const WU = require("widget_utils");
291+
WU.hide();
292+
const APP_RECT = Bangle.appRect;
293+
252294
var bodyFont = fontBig;
253295
g.setFont(bodyFont);
254-
var lines = [];
255-
if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10);
256-
var titleCnt = lines.length;
257-
if (titleCnt) lines.push(""); // add blank line after title
258-
lines = lines.concat(g.wrapString(msg.body, g.getWidth()-10),["",/*LANG*/"< Back"]);
296+
const FONT_HEIGHT = g.getFontHeight();
297+
let initScroll;
298+
var titleLines = [];
299+
let allLines = [];
300+
let firstTitleLinePerMsg = [];
301+
for (let i=0 ; i<MESSAGES.length ; i++) {
302+
if (MSG_IDX === i) {initScroll = allLines.length*FONT_HEIGHT;}
303+
let msgIter = MESSAGES[i];
304+
305+
var lines = [];
306+
const TITLE_STRING = msgIter.title||msgIter.sender||msgIter.subject||msgIter.src||/*LANG*/"No Title";
307+
lines = g.wrapString(TITLE_STRING, APP_RECT.w-10);
308+
firstTitleLinePerMsg.push(allLines.length);
309+
for (let i=0; i<lines.length; i++) {
310+
titleLines.push(i + allLines.length);
311+
}
312+
lines = lines.concat(g.wrapString(msgIter.body, APP_RECT.w-10),
313+
["-".repeat(12)]);
314+
allLines = allLines.concat(lines);
315+
}
316+
317+
if (allLines.length == 0) {
318+
cancelReloadTimeout();
319+
returnToClockIfEmpty();
320+
}
321+
322+
let shownScrollIdxFirst = allLines.length;
323+
let shownScrollIdxLast = 0;
324+
259325
E.showScroller({
260-
h : g.getFontHeight(), // height of each menu item in pixels
261-
c : lines.length, // number of menu items
326+
scroll : initScroll,
327+
h : FONT_HEIGHT, // height of each menu item in pixels
328+
c : allLines.length, // number of menu items
262329
// a function to draw a menu item
263-
draw : function(idx, r) {
264-
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
265-
g.setBgColor(idx<titleCnt ? g.theme.bg2 : g.theme.bg).
266-
setColor(idx<titleCnt ? g.theme.fg2 : g.theme.fg).
267-
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
268-
g.setFont(bodyFont).setFontAlign(0,-1).drawString(lines[idx], r.x+r.w/2, r.y);
269-
}, select : function(idx) {
270-
if (idx>=lines.length-2)
271-
showMessage(msg.id, true);
330+
draw : function(scrollIdx, r) {"ram";
331+
//print(scrollIdx)
332+
g.setBgColor(titleLines.find(e=>e==scrollIdx)!==undefined ? g.theme.bg2 : g.theme.bg).
333+
setColor(titleLines.find(e=>e==scrollIdx)!==undefined ? g.theme.fg2 : g.theme.fg).
334+
clearRect(r);
335+
g.setFont(bodyFont).setFontAlign(0,-1).drawString(allLines[scrollIdx], r.x+r.w/2, r.y);
336+
if (scrollIdx<shownScrollIdxFirst) {shownScrollIdxFirst = scrollIdx;}
337+
if (scrollIdx>shownScrollIdxLast) {shownScrollIdxLast = scrollIdx;}
272338
},
273-
back : () => showMessage(msg.id, true)
339+
select : function(scrollIdx, touch) {
340+
for (let i=firstTitleLinePerMsg.length-1; i>=0 ; i--) {
341+
if (scrollIdx>=firstTitleLinePerMsg[i]) {
342+
if (!touch || touch.type===0) {
343+
WU.show();
344+
showMessageRouter(MESSAGES[i], true, "overview");
345+
updateReadMessages();
346+
}
347+
break;
348+
}
349+
}
350+
}
274351
});
352+
353+
// If Bangle.js 2 add an external select hw button handler.
354+
if (2===process.env.HWVERSION) {
355+
setWatch(()=>{
356+
if ("scroller"!==active) {return;}
357+
Bangle.emit("drag", {dy:0}); // Compatibility with `kineticscroll`, stopping the scroller so it doesn't continue scrolling when the `showMessageOverview` screen is loaded.
358+
// Zero ms timeout as to not move on before the scroller has registered the emitted drag event.
359+
setTimeout(()=>{
360+
if (!persist) {return load();}
361+
Bangle.emit("touch", 1, {x:APP_RECT.x2/2, y:APP_RECT.y2/2, type:0});
362+
},0);
363+
}, BTN);
364+
}
365+
366+
function updateReadMessages() {
367+
let shownMsgIdxFirst, shownMsgIdxLast;
368+
const LINES_PER_SCREEN = APP_RECT.h/FONT_HEIGHT;
369+
//print(firstTitleLinePerMsg)
370+
//print(shownIdxFirst, shownIdxLast)
371+
372+
for (let i=0; i<firstTitleLinePerMsg.length-1 ; i++) {
373+
const FIRST_LINE_OF_MSG = firstTitleLinePerMsg[i];
374+
const LAST_LINE_OF_MSG = firstTitleLinePerMsg[i+1]-1;
375+
376+
if (
377+
shownScrollIdxFirst
378+
<= FIRST_LINE_OF_MSG && FIRST_LINE_OF_MSG
379+
< shownScrollIdxFirst+LINES_PER_SCREEN
380+
) {
381+
shownMsgIdxFirst = i;
382+
}
383+
384+
if (
385+
shownScrollIdxLast-LINES_PER_SCREEN
386+
< LAST_LINE_OF_MSG && LAST_LINE_OF_MSG
387+
<= shownScrollIdxLast
388+
) {
389+
shownMsgIdxLast = i;
390+
//print(i)
391+
}
392+
}
393+
if (shownScrollIdxLast===allLines.length-1) {shownMsgIdxLast = MESSAGES.length-1;}
394+
395+
//print(shownIdxFirst, shownIdxLast)
396+
//print(shownMsgIdxFirst, shownMsgIdxLast)
397+
//print(MESSAGES)
398+
for (let i=shownMsgIdxFirst; i<shownMsgIdxLast+1; i++) {
399+
MESSAGES[i].new = false;
400+
}
401+
//print(MESSAGES)
402+
}
275403
}
276404

277405
function showMessageSettings(msg) {
278406
active = "settings";
279407
var menu = {"":{
280408
"title":/*LANG*/"Message",
281-
back:() => showMessage(msg.id, true)
409+
back:() => showMessageOverview(msg.id)
282410
},
283411
};
284412

285413
if (msg.id!="music")
286-
menu[/*LANG*/"View Message"] = () => showMessageScroller(msg);
414+
menu[/*LANG*/"View Message"] = () => showMessagesScroller(msg);
287415

288416
if (msg.reply && reply) {
289417
menu[/*LANG*/"Reply"] = () => {
@@ -292,11 +420,11 @@ function showMessageSettings(msg) {
292420
.then(result => {
293421
Bluetooth.println(JSON.stringify(result));
294422
replying = false;
295-
showMessage(msg.id);
423+
showMessageOverview(msg.id);
296424
})
297425
.catch(() => {
298426
replying = false;
299-
showMessage(msg.id);
427+
showMessageOverview(msg.id);
300428
});
301429
};
302430
}
@@ -340,25 +468,16 @@ function showMessageSettings(msg) {
340468
E.showMenu(menu);
341469
}
342470

343-
function showMessage(msgid, persist) {
471+
function showMessageOverview(msgid) {
344472
if (replying) { return; }
345-
if(!persist) resetReloadTimeout();
346473
let idx = MESSAGES.findIndex(m=>m.id==msgid);
347474
var msg = MESSAGES[idx];
348475
if (updateLabelsInterval) {
349476
clearInterval(updateLabelsInterval);
350477
updateLabelsInterval=undefined;
351478
}
352479
if (!msg) return returnToClockIfEmpty(); // go home if no message found
353-
if (msg.id=="music") {
354-
cancelReloadTimeout(); // don't auto-reload to clock now
355-
return showMusicMessage(msg);
356-
}
357-
if (msg.id=="nav") {
358-
cancelReloadTimeout(); // don't auto-reload to clock now
359-
return showMapMessage(msg);
360-
}
361-
active = "message";
480+
active = "overview";
362481
// Normal text message display
363482
var title=msg.title, titleFont = fontLarge, lines;
364483
var body=msg.body, bodyFont = fontLarge;
@@ -426,7 +545,7 @@ function showMessage(msgid, persist) {
426545
.catch(() => {
427546
replying = false;
428547
layout.render();
429-
showMessage(msg.id);
548+
showMessageOverview(msg.id);
430549
});
431550
}; footer.push({type:"img",src:atob("QRABAAAAAAAH//+AAAAABgP//8AAAAADgf//4AAAAAHg4ABwAAAAAPh8APgAAAAAfj+B////////geHv///////hf+f///////GPw///////8cGBwAAAAAPx/gDgAAAAAfD/gHAAAAAA8DngOAAAAABwDHP8AAAAADACGf4AAAAAAAAM/w=="),col:"#0f0", cb:posHandler});
432551
}
@@ -456,16 +575,16 @@ function showMessage(msgid, persist) {
456575
]},
457576
{type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2, cb:()=>{
458577
// allow tapping to show a larger version
459-
showMessageScroller(msg);
578+
showMessagesScroller(msg);
460579
} },
461580
{type:"h",fillx:1, c: footer}
462581
]},{back:goBack});
463582

464583
Bangle.swipeHandler = (lr,ud) => {
465584
if (lr>0 && posHandler) posHandler();
466585
if (lr<0 && negHandler) negHandler();
467-
if (ud>0 && idx<MESSAGES.length-1) showMessage(MESSAGES[idx+1].id, true);
468-
if (ud<0 && idx>0) showMessage(MESSAGES[idx-1].id, true);
586+
if (ud>0 && idx<MESSAGES.length-1) showMessageOverview(MESSAGES[idx+1].id);
587+
if (ud<0 && idx>0) showMessageOverview(MESSAGES[idx-1].id);
469588
};
470589
Bangle.on("swipe", Bangle.swipeHandler);
471590
g.reset().clearRect(Bangle.appRect);
@@ -502,20 +621,20 @@ function checkMessages(options) {
502621
// If we have a new message, show it
503622
if (!options.ignoreUnread && newMessages.length) {
504623
delete newMessages[0].show; // stop us getting stuck here if we're called a second time
505-
showMessage(newMessages[0].id, false);
506-
// buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
507-
if (global.BUZZ_ON_NEW_MESSAGE) {
624+
showMessagesScroller(newMessages[0]);
625+
// buzz after showMessagesScroller, so being busy during scroller setup doesn't affect the buzz pattern
626+
if (globalThis.BUZZ_ON_NEW_MESSAGE) {
508627
// this is set if we entered the messages app by loading `messagegui.new.js`
509628
// ... but only buzz the first time we view a new message
510-
global.BUZZ_ON_NEW_MESSAGE = false;
629+
globalThis.BUZZ_ON_NEW_MESSAGE = false;
511630
// messages.buzz respects quiet mode - no need to check here
512631
require("messages").buzz(newMessages[0].src);
513632
}
514633
return;
515634
}
516635
// no new messages: show playing music? Only if we have playing music, or state=="show" (set by messagesmusic)
517636
if (options.openMusic && MESSAGES.some(m=>m.id=="music" && ((m.track && m.state=="play") || m.state=="show")))
518-
return showMessage('music', true);
637+
return showMessageOverview('music');
519638
// no new messages - go to clock?
520639
if (options.clockIfAllRead && newMessages.length==0)
521640
return load();
@@ -524,7 +643,7 @@ function checkMessages(options) {
524643
E.showScroller({
525644
h : 48,
526645
c : Math.max(MESSAGES.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
527-
draw : function(idx, r) {"ram"
646+
draw : function(idx, r) {"ram";
528647
var msg = MESSAGES[idx];
529648
if (msg && msg.new) g.setBgColor(g.theme.bgH).setColor(g.theme.fgH);
530649
else g.setBgColor(g.theme.bg).setColor(g.theme.fg);
@@ -564,13 +683,13 @@ function checkMessages(options) {
564683
},
565684
select : idx => {
566685
if (idx < MESSAGES.length)
567-
showMessage(MESSAGES[idx].id, true);
686+
showMessageRouter(MESSAGES[idx], true, "overview");
568687
},
569688
back : () => load()
570689
});
571690
}
572691

573-
function returnToCheckMessages(clock) {
692+
function returnToCheckMessages() {
574693
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,ignoreUnread:settings.ignoreUnread,openMusic});
575694
}
576695

@@ -611,8 +730,14 @@ setTimeout(() => {
611730
}, 10); // if checkMessages wants to 'load', do that
612731

613732
/* If the Bangle is unlocked by the user, treat that
614-
as a queue to stop repeated buzzing */
733+
as a queue to stop repeated buzzing.
734+
Also suspend the reload timeout while the watch is unlocked. */
615735
Bangle.on('lock',locked => {
616-
if (!locked)
736+
if (!locked) {
617737
require("messages").stopBuzz();
738+
cancelReloadTimeout();
739+
}
740+
if (locked) {
741+
if (!persist) {resetReloadTimeout();}
742+
}
618743
});

apps/messagegui/metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "messagegui",
33
"name": "Message UI",
44
"shortName": "Messages",
5-
"version": "0.84",
5+
"version": "0.85",
66
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
77
"icon": "app.png",
88
"type": "app",

0 commit comments

Comments
 (0)