From fd9a15042736b4bd63d7ce7d0aa5cdfcdb4dd515 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 23 May 2025 13:58:45 +0100 Subject: [PATCH] messages: Default to showing message scroller (with title, bigger icon) --- apps/messagegui/ChangeLog | 3 +- apps/messagegui/README.md | 28 ++-- apps/messagegui/app.js | 268 +++++++++++++++++++------------- apps/messagegui/metadata.json | 2 +- apps/messagegui/screenshot.png | Bin 3882 -> 3784 bytes apps/messagegui/screenshot1.png | Bin 1758 -> 3521 bytes apps/messages/ChangeLog | 3 +- apps/messages/README.md | 1 + apps/messages/settings.js | 4 + 9 files changed, 175 insertions(+), 134 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 455f96343..cf1bfaa98 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -114,4 +114,5 @@ 0.83: Add option to not open the first unread message 0.84: Fix: Assign show message entry to the settings menu and not the message itself. 0.85: Use new Rebble fonts if available - Remove workaround for 2v10 (>3 years ago) - assume everyone is on never firmware now \ No newline at end of file + Remove workaround for 2v10 (>3 years ago) - assume everyone is on never firmware now +0.86: Default to showing message scroller (with title, bigger icon) \ No newline at end of file diff --git a/apps/messagegui/README.md b/apps/messagegui/README.md index 60b302364..1e1e75c60 100644 --- a/apps/messagegui/README.md +++ b/apps/messagegui/README.md @@ -1,6 +1,6 @@ # Messages app -Default app to handle the display of messages and message notifications. It allows +Default app to handle the display of messages and message notifications. It allows them to be listed, viewed, and responded to. It is installed automatically if you install `Android Integration` or `iOS Integration`. @@ -10,21 +10,8 @@ It is a replacement for the old `notify`/`gadgetbridge` apps. ## Settings You can change settings by going to the global `Settings` app, then `App Settings` -and `Messages`: - -* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received -* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received -* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds -* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and -buzz every `Repeat` seconds. This is how long we continue to do that. -* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. -If there is no user input for this amount of time then the app will exit and return -to the clock where a ringing bell will be shown in the Widget bar. -* `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font -is chosen if there isn't much message text, but this specifies the smallest the font should get before -it starts getting clipped. -* `Auto-Open Music` - Should the app automatically open when the phone starts playing music? -* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? +and `Messages`. See the [Messages App Readme](https://banglejs.com/apps/?id=messages&readme) +for more information. ## New Messages @@ -36,9 +23,12 @@ When a new message is received: When a message is shown, you'll see a screen showing the message title and text. * The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. -* The top-left icon shows more options, for instance deleting the message of marking unread -* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) -- On Bangle.js 2 swipe up/down to show newer/older message +* Tapping the title bar shows more options, for instance deleting the message of marking unread +* On Bangle.js 2: + * Dragging up/down will show more or the current message + * Swipe up/down at the beginning/end of a message to show newer/older message +* On Bangle.js 1: + * Pressing top/bottom buttons will show more or the current message * If shown, the 'tick' button: * **Android** opens the notification on the phone * **iOS** responds positively to the notification (accept call/etc) diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 31b7c95dc..c9643234e 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -13,9 +13,13 @@ /* For example for maps: // a message -require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going?",positive:1,negative:1}) +require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1}) +require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1}) +require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1}) +require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1}) +require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1}) // maps -GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:"966yd",action:"continue",eta:"08:39"}) +GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:"966m",action:"continue",eta:"08:39"}) GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:"12km",action:"left_slight",eta:"08:39"}) GB({t:"nav",src:"maps",title:"Navigation",instr:"Main St / I-29 ALT / Centerpoint Dr",distance:12345,action:"left_slight",eta:"08:39"}) // call @@ -87,6 +91,35 @@ function saveMessages() { } E.on("kill", saveMessages); +/* Listens to drag events to allow the user to swipe up/down to change message on Bangle.js 2 +returns dragHandler which should then be removed with Bangle.removeListener("drah", dragHandler); on exit */ +function addDragHandlerToChangeMessage(idx, scroller) { + // save the scroll pos when finger pressed + let lastTouched=false, lastScrollPos=0, scrollY=0; + let dragHandler = (e) => { + let scrollPos = scroller?scroller.scroll:0; + if (e.b) { + if (!lastTouched) lastScrollPos = scrollPos; + scrollY += e.dy; + } + lastTouched = e.b; + // swipe up down to prev/next but ONLY when finger released and if we're already at the top/bottom => scroller hasn't moved + if (!e.b && scrollPos==lastScrollPos) { + if (scrollY<-50 && idx50 && idx>0) { + Bangle.buzz(30); + showMessage(MESSAGES[idx-1].id, true); + } + scrollY = 0; + } + }; + Bangle.on("drag", dragHandler); + return dragHandler; +} + function showMapMessage(msg) { active = "map"; require("messages").stopBuzz(); // stop repeated buzzing while the map is showing @@ -129,26 +162,28 @@ function showMapMessage(msg) { street?{type:"h", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, c: [ {type:"txt", font:fontSmall, label:"Towards" }, {type:"txt", font:fontLarge, label:street } - ]}:{}, + ]}:{type:""}, {type:"h",fillx:1, filly:1, c: [ - img?{type:"img",src:atob(img), scale:2, pad:6}:{}, + img?{type:"img",src:atob(img), scale:2, pad:6}:{type:""}, {type:"v", fillx:1, c: [ {type:"txt", font:fontVLarge, label:distance||"" } ]}, ]}, {type:"txt", font:fontMedium, label:msg.eta?`ETA ${msg.eta}`:"" } - ]}); - g.reset().clearRect(Bangle.appRect); - layout.render(); - function back() { // mark as not new and return to menu + ]}, { back : function() { // mark as not new and return to menu msg.new = false; layout = undefined; checkMessages({clockIfNoMsg:1,clockIfAllRead:1,ignoreUnread:settings.ignoreUnread,openMusic:0}); - } - Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back + }, remove : function() { + Bangle.removeListener("drag", dragHandler); + }}); + g.reset().clearRect(Bangle.appRect); + layout.render(); + // handle up/down to drag to new message + let dragHandler = addDragHandlerToChangeMessage(MESSAGES.findIndex(m=>m==msg)); } -let updateLabelsInterval; + function showMusicMessage(msg) { active = "music"; @@ -163,6 +198,7 @@ function showMusicMessage(msg) { var trackName = ''; var artistName = ''; var albumName = ''; + var updateLabelsInterval; function fmtTime(s) { var m = Math.floor(s/60); @@ -174,8 +210,10 @@ function showMusicMessage(msg) { return text.substr(offset, sliceLength).padEnd(maxLen, " "); } function unload() { - clearInterval(updateLabelsInterval); + if (updateLabelsInterval) + clearInterval(updateLabelsInterval); updateLabelsInterval = undefined; + Bangle.removeListener("drag", dragHandler); } function back() { unload(); @@ -221,10 +259,14 @@ function showMusicMessage(msg) { {type:"btn", pad:8, label:atob("ABISgQDAAfgAf4Af8Af/Af/gf/wf/8f/+f/+f/8f/wf/gf/Af8Af4AfgAfAAcA=="), cb:()=>Bangle.musicControl("next")}, // next ]}:{}, {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" } - ]}, { back : back }); + ]}, { back : back, remove : unload + }); g.reset().clearRect(Bangle.appRect); layout.render(); + // handle up/down to drag to new message + let dragHandler = addDragHandlerToChangeMessage(MESSAGES.findIndex(m=>m==msg)); + updateLabelsInterval = setInterval(function() { updateLabels(); layout.artist.label = artistName; @@ -234,34 +276,6 @@ function showMusicMessage(msg) { }, 400); } -function showMessageScroller(msg) { - cancelReloadTimeout(); - active = "scroller"; - var bodyFont = fontBig; - g.setFont(bodyFont); - var lines = []; - if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10); - var titleCnt = lines.length; - if (titleCnt) lines.push(""); // add blank line after title - lines = lines.concat(g.wrapString(msg.body, g.getWidth()-10),["",/*LANG*/"< Back"]); - E.showScroller({ - h : g.getFontHeight(), // height of each menu item in pixels - c : lines.length, // number of menu items - // a function to draw a menu item - draw : function(idx, r) { - // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 - g.setBgColor(idx=lines.length-2) - showMessage(msg.id, true); - }, - back : () => showMessage(msg.id, true) - }); -} - function showMessageSettings(msg) { active = "settings"; var menu = {"":{ @@ -270,10 +284,12 @@ function showMessageSettings(msg) { }, }; - if (msg.id!="music") - menu[/*LANG*/"View Message"] = () => showMessageScroller(msg); + /* Bangle.js 1 can't press a button to go back from + showMessage to the message list, so add the option here */ + if (process.env.BOARD=="BANGLEJS") + menu[/*LANG*/"Message List"] = () => { returnToMain(); }; - if (msg.reply && reply) { + if (msg.reply && reply) menu[/*LANG*/"Reply"] = () => { replying = true; reply.reply({msg: msg}) @@ -287,14 +303,11 @@ function showMessageSettings(msg) { showMessage(msg.id); }); }; - } - menu = Object.assign(menu, { - /*LANG*/"Delete" : () => { - MESSAGES = MESSAGES.filter(m=>m.id!=msg.id); - returnToMain(); - }, - }); + menu[/*LANG*/"Delete"] = () => { + MESSAGES = MESSAGES.filter(m=>m.id!=msg.id); + returnToMain(); + }; if (Bangle.messageIgnore && msg.src) menu[/*LANG*/"Ignore"] = () => { @@ -332,11 +345,7 @@ function showMessage(msgid, persist) { if (replying) { return; } if(!persist) resetReloadTimeout(); let idx = MESSAGES.findIndex(m=>m.id==msgid); - var msg = MESSAGES[idx]; - if (updateLabelsInterval) { - clearInterval(updateLabelsInterval); - updateLabelsInterval=undefined; - } + let msg = MESSAGES[idx]; if (!msg) return returnToClockIfEmpty(); // go home if no message found if (msg.id=="music") { cancelReloadTimeout(); // don't auto-reload to clock now @@ -348,15 +357,18 @@ function showMessage(msgid, persist) { } active = "message"; // Normal text message display - var title=msg.title, titleFont = fontLarge, lines; - var body=msg.body, bodyFont = fontLarge; + let src=msg.src||/*LANG*/"Message", srcFont = fontSmall; + let title=msg.title, titleFont = fontLarge, lines; + let body=msg.body, bodyFont = fontLarge; // If no body, use the title text instead... if (body===undefined) { body = title; title = undefined; } + if (g.setFont(srcFont).stringWidth(src) > g.getWidth()-52) + srcFont = "4x6"; if (title) { - var w = g.getWidth()-48; + let w = g.getWidth()-52; if (g.setFont(titleFont).stringWidth(title) > w) { titleFont = fontBig; if (settings.fontSize!=1 && g.setFont(titleFont).stringWidth(title) > w) @@ -368,37 +380,27 @@ function showMessage(msgid, persist) { } } if (body) { // Try and find a font that fits... - var w = g.getWidth()-2, h = Bangle.appRect.h-60; + let w = g.getWidth()-2, h = Bangle.appRect.h-60; if (g.setFont(bodyFont).wrapString(body, w).length*g.getFontHeight() > h) { bodyFont = fontBig; if (settings.fontSize!=1 && g.setFont(bodyFont).wrapString(body, w).length*g.getFontHeight() > h) { bodyFont = fontMedium; } } - // Now crop, given whatever font we have available lines = g.setFont(bodyFont).wrapString(body, w); - var maxLines = Math.floor(h / g.getFontHeight()); - if (lines.length>maxLines) // if too long, wrap with a bit less spae so we have room for '...' - body = g.setFont(bodyFont).wrapString(body, w-10).slice(0,maxLines).join("\n")+"..."; - else - body = lines.join("\n"); + if (lines.length<3) + lines.unshift(""); // if less lines, pad them out a bit at the top! } - function goBack() { - layout = undefined; - msg.new = false; // read mail - cancelReloadTimeout(); // don't auto-reload to clock now - returnToClockIfEmpty(); - } - var negHandler,posHandler,footer = [ ]; + let negHandler,posHandler,rowLeftDraw,rowRightDraw; if (msg.negative) { negHandler = ()=>{ msg.new = false; cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,false); returnToCheckMessages(); - }; footer.push({type:"img",src:atob("PhAB4A8AAAAAAAPAfAMAAAAAD4PwHAAAAAA/H4DwAAAAAH78B8AAAAAA/+A/AAAAAAH/Af//////w/gP//////8P4D///////H/Af//////z/4D8AAAAAB+/AfAAAAAA/H4DwAAAAAPg/AcAAAAADwHwDAAAAAA4A8AAAAAAAA=="),col:"#f00",cb:negHandler}); + }; + rowLeftDraw = function(r) {g.setColor("#f00").drawImage(atob("PhAB4A8AAAAAAAPAfAMAAAAAD4PwHAAAAAA/H4DwAAAAAH78B8AAAAAA/+A/AAAAAAH/Af//////w/gP//////8P4D///////H/Af//////z/4D8AAAAAB+/AfAAAAAA/H4DwAAAAAPg/AcAAAAADwHwDAAAAAA4A8AAAAAAAA=="),r.x+2,r.y+2);}; } - footer.push({fillx:1}); // push images to left/right if (msg.reply && reply) { posHandler = ()=>{ replying = true; @@ -408,56 +410,98 @@ function showMessage(msgid, persist) { .then(result => { Bluetooth.println(JSON.stringify(result)); replying = false; - layout.render(); returnToCheckMessages(); }) .catch(() => { replying = false; - layout.render(); showMessage(msg.id); }); - }; footer.push({type:"img",src:atob("QRABAAAAAAAH//+AAAAABgP//8AAAAADgf//4AAAAAHg4ABwAAAAAPh8APgAAAAAfj+B////////geHv///////hf+f///////GPw///////8cGBwAAAAAPx/gDgAAAAAfD/gHAAAAAA8DngOAAAAABwDHP8AAAAADACGf4AAAAAAAAM/w=="),col:"#0f0", cb:posHandler}); - } - else if (msg.positive) { + }; + rowRightDraw = function(r) {g.setColor("#0f0").drawImage(atob("QRABAAAAAAAH//+AAAAABgP//8AAAAADgf//4AAAAAHg4ABwAAAAAPh8APgAAAAAfj+B////////geHv///////hf+f///////GPw///////8cGBwAAAAAPx/gDgAAAAAfD/gHAAAAAA8DngOAAAAABwDHP8AAAAADACGf4AAAAAAAAM/w=="),r.x+r.w-67,r.y+2);}; + } else if (msg.positive) { posHandler = ()=>{ msg.new = false; cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,true); returnToCheckMessages(); - }; footer.push({type:"img",src:atob("QRABAAAAAAAAAAOAAAAABgAAA8AAAAADgAAD4AAAAAHgAAPgAAAAAPgAA+AAAAAAfgAD4///////gAPh///////gA+D///////AD4H//////8cPgAAAAAAPw8+AAAAAAAfB/4AAAAAAA8B/gAAAAAABwB+AAAAAAADAB4AAAAAAAAABgAA=="),col:"#0f0",cb:posHandler}); + }; + rowRightDraw = function(r) {g.setColor("#0f0").drawImage(atob("QRABAAAAAAAAAAOAAAAABgAAA8AAAAADgAAD4AAAAAHgAAPgAAAAAPgAA+AAAAAAfgAD4///////gAPh///////gA+D///////AD4H//////8cPgAAAAAAPw8+AAAAAAAfB/4AAAAAAA8B/gAAAAAABwB+AAAAAAADAB4AAAAAAAAABgAA=="),r.x+r.w-64,r.y+2);}; } + let fontHeight = g.setFont(bodyFont).getFontHeight(); + let lineHeight = (fontHeight>25)?fontHeight:25; + if (title.includes("\n")) lineHeight=25; // ensure enough room for 2 lines of title in header + let linesPerRow = 2; + if (fontHeight<17) { + lineHeight = 16; + linesPerRow = 3; + } + let rowHeight = lineHeight*linesPerRow; + let textLineOffset = -(linesPerRow + ((rowLeftDraw||rowRightDraw)?1:0)); + let msgIcon = require("messageicons").getImage(msg); + let msgCol = require("messageicons").getColor(msg, {settings, default:g.theme.fg2}); + Bangle.setUI(); // force last UI to be removed (will call require("widget_utils").show(); if last displaying a message) + if (!settings.showWidgets) require("widget_utils").hide(); + let scroller = E.showScroller({ + h : rowHeight, // height of each menu item in pixels + c : Math.ceil((lines.length-textLineOffset) / linesPerRow), // number of menu items + // a function to draw a menu item + draw : function(idx, r) { "ram"; + if (idx) { // message body + let lidx = idx*linesPerRow+textLineOffset; + g.setBgColor(g.theme.bg).setColor(g.theme.fg).clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + g.setFont(bodyFont).setFontAlign(0,-1).drawString(lines[lidx++]||"", r.x+r.w/2, r.y).drawString(lines[lidx++]||"", r.x+r.w/2, r.y+lineHeight); + if (linesPerRow==3) g.drawString(lines[lidx++]||"", r.x+r.w/2, r.y+lineHeight*2); + if (idx!=1) return; + if (rowLeftDraw) rowLeftDraw(r); + if (rowRightDraw) rowRightDraw(r); + } else { // idx==0 => header + g.setBgColor(g.theme.bg2).setColor(g.theme.fg).clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!settings.showWidgets && Bangle.isLocked()) g.drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), r.x+1,r.y+4); // locked symbol + var mid = (r.w-48)/2; + g.setColor(g.theme.fg2).setFont(srcFont).setFontAlign(0,-1).drawString(src, mid, r.y+2); + let srcHeight = g.getFontHeight(); + g.setFont(titleFont).setFontAlign(0,0).drawString(title, mid, r.y+ (r.h+srcHeight+2)/2); + //g.setColor(g.theme.bgH).fillRect({x:r.x+r.w-47, y:r.y+3, w:44, h:44, r:6}); + g.setColor(msgCol).drawImage(msgIcon, r.x+r.w-24, r.y + rowHeight/2, {rotate:0/*center*/}); + } + }, select : function(idx) { + if (idx==0) { // the title + cancelReloadTimeout(); // don't auto-reload to clock now + showMessageSettings(msg); + } + }, + remove : function() { + Bangle.removeListener("drag", dragHandler); + Bangle.removeListener("swipe", swipeHandler); + Bangle.removeListener("lock", lockHandler); + if (!settings.showWidgets) require("widget_utils").show(); + }, + back : function() { + msg.new = false; // read mail + cancelReloadTimeout(); // don't auto-reload to clock now + returnToClockIfEmpty(); + } + }); - layout = new Layout({ type:"v", c: [ - {type:"h", fillx:1, bgCol:g.theme.bg2, col: g.theme.fg2, c: [ - { type:"v", fillx:1, c: [ - {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, - title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, - ]}, - { type:"btn", - src:require("messageicons").getImage(msg), - col:require("messageicons").getColor(msg, {settings, default:g.theme.fg2}), - pad: 3, cb:()=>{ - cancelReloadTimeout(); // don't auto-reload to clock now - showMessageSettings(msg); - } - }, - ]}, - {type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2, cb:()=>{ - // allow tapping to show a larger version - showMessageScroller(msg); - } }, - {type:"h",fillx:1, c: footer} - ]},{back:goBack}); - - Bangle.swipeHandler = (lr,ud) => { - if (lr>0 && posHandler) posHandler(); - if (lr<0 && negHandler) negHandler(); - if (ud>0 && idx0) showMessage(MESSAGES[idx-1].id, true); + let dragHandler = addDragHandlerToChangeMessage(idx, scroller); + // handle swipes + let swipeHandler = (lr,ud) => { + // left/right accept/reject + if (lr>0 && posHandler) { + Bangle.buzz(30); + posHandler(); + } + if (lr<0 && negHandler) { + Bangle.buzz(30); + negHandler(); + } + /* handle up/down in drag handler because we want to + move message only when the finger is released, or subsequent + finger movement will end up dragging the new message */ }; - Bangle.on("swipe", Bangle.swipeHandler); - g.reset().clearRect(Bangle.appRect); - layout.render(); + Bangle.on("swipe", swipeHandler); + let lockHandler = () => scroller.draw(); + Bangle.on("lock",lockHandler); // redraw when we lock/unlock } @@ -603,4 +647,4 @@ as a queue to stop repeated buzzing */ Bangle.on('lock',locked => { if (!locked) require("messages").stopBuzz(); -}); +}); \ No newline at end of file diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index e0f47c6bc..1f804ad56 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.85", + "version": "0.86", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/messagegui/screenshot.png b/apps/messagegui/screenshot.png index bd8662c8a5ba22bb6aeff830651eae5a3ae89f30..66739e57ee7cdf5ef992451b06225dd044f3532a 100644 GIT binary patch delta 3771 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 diff --git a/apps/messagegui/screenshot1.png b/apps/messagegui/screenshot1.png index 3b92174643024c4f5a55491c22915636a0bffe90..e026b7c9c57884830a940a225fb7a94041885a52 100644 GIT binary patch literal 3521 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 v ? v :/*LANG*/"Hide", onchange: v => updateSetting("maxMessages", v) }, + /*LANG*/'Show Widgets': { + value: !!settings.showWidgets, + onchange: v => updateSetting("showWidgets", v) + }, /*LANG*/'Icon color mode': { value: Math.max(0,iconColorModes.indexOf(settings.iconColorMode)), min: 0, max: iconColorModes.length - 1,