From 3c57eeeffd1bde8307a570bbf908ee273e9a8dae Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 21 Oct 2022 17:32:53 +0200 Subject: [PATCH] new widget: widmsggrid: Messages Grid Widget Displays multiple notification icons in a grid. --- apps/widmsggrid/ChangeLog | 1 + apps/widmsggrid/README.md | 23 +++++++++ apps/widmsggrid/metadata.json | 16 ++++++ apps/widmsggrid/screenshot.png | Bin 0 -> 10727 bytes apps/widmsggrid/widget.js | 92 +++++++++++++++++++++++++++++++++ apps/widmsggrid/widget.png | Bin 0 -> 10457 bytes 6 files changed, 132 insertions(+) create mode 100644 apps/widmsggrid/ChangeLog create mode 100644 apps/widmsggrid/README.md create mode 100644 apps/widmsggrid/metadata.json create mode 100644 apps/widmsggrid/screenshot.png create mode 100644 apps/widmsggrid/widget.js create mode 100644 apps/widmsggrid/widget.png diff --git a/apps/widmsggrid/ChangeLog b/apps/widmsggrid/ChangeLog new file mode 100644 index 000000000..4be6afb16 --- /dev/null +++ b/apps/widmsggrid/ChangeLog @@ -0,0 +1 @@ +0.01: New widget! \ No newline at end of file diff --git a/apps/widmsggrid/README.md b/apps/widmsggrid/README.md new file mode 100644 index 000000000..7c85d5c31 --- /dev/null +++ b/apps/widmsggrid/README.md @@ -0,0 +1,23 @@ +# Messages Grid Widget + +Widget that displays multiple notification icons in a grid. +The widget has a fixed size: if there are multiple notifications it uses smaller +icons. +It shows a single icon per application, so if you have two SMS messages, the +grid only has one SMS icon. +If there are multiple messages waiting, the total number is shown in the +bottom-right corner. + +Example: one SMS, one Signal, and two WhatsApp messages: +![screenshot](screenshot.png) + +## Installation +This widget needs the [`messages`](/?id=messages) app to handle notifications. + +## Settings +This widget uses the `Widget` settings from the `messages` app: + +### Widget +* `Flash icon` Toggle flashing of the widget icons. + +* `Widget messages` Not used by this widget, but you should select `Hide` to hide the default widget. \ No newline at end of file diff --git a/apps/widmsggrid/metadata.json b/apps/widmsggrid/metadata.json new file mode 100644 index 000000000..b624f5c23 --- /dev/null +++ b/apps/widmsggrid/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widmsggrid", + "name": "Messages Grid Widget", + "version": "0.01", + "description": "Widget that display notification icons in a grid", + "icon": "widget.png", + "type": "widget", + "dependencies": {"messages":"app"}, + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widmsggrid.wid.js","url":"widget.js"} + ], + "screenshots": [{"url":"screenshot.png"}] +} diff --git a/apps/widmsggrid/screenshot.png b/apps/widmsggrid/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b1cdb2a5af45dc0ef9f2460ed747f5e7ac376f19 GIT binary patch literal 10727 zcmeHtXIN9)+AX2?4$=uhks^?Uj&zVFO*%*mAwYo8dk5*#K}AG*2LX|)G(iz*(tDFG z(z`Uj;C7$0_r2%-IL~vxe>-_X)_Ui7$DHq&W6p=QR-&~ult>5}2r)1)NK}*+w9(&H z*B^Xb^xw+*Wit#6+8tkALw9YM7tq<&$r|Z^0J{4)BY+5Rq%{VH_taLpk?Tz{&wD># z6Y>IvJF1vst*1hsR=QDG=_B+!OUe+?VWpJku@(az%-$W$nUQyY9%YSR;+fwnT&*6} z_vOm0oN-Hdbh|I8rSqKLKTvvp{NjwN_2=Q+^S#Kf`_Z2cf7CvyTxvBZlh)Yvp_6zs z|FZa1rF3uB&w+#SAJeQF)Y4=sJVOVU9=TT@xzy4@_~y6ivQR&U9$q5t*A5z5y@sTI zz6@MGKB)eA=y%I^x#X0X_9ZLBAVcuM zvV5IvaQvsA*E^>#o0iLR#XaMNE%p1EE=-6tk8i7n#(PhGc5-c;Z~Kds`aU3>=8y5& zZ;DjbckF^iQaFn4xP*VCbd%uI&-~D!oNne&(#c|=eO2Ak5%+{?d!*~TFY*i5mEnEw zXIp`dUbUT1ycP!NM2)meFJ_%iyyl%NjoGm013$!gd@QzjLyXXn7K@DyJEzh016*BQ zEj`;$@5<)twoB5mSX-kWZg)~nG|Q#JV@SwiA@&G*8BFeighSYp0M)d~;gtSyuOnoD zj06mKBnz2t)8kvayy;5Q;a)DhXPRC=vP&9A>od70ZlTC=hvuxjq(@o5U-5EerZ@-o zx%xRx*;x5BXUVcu!OkQESX|1u{t5?V3#&(THJg@4;d>JK=v2Bg{n~%r*SRdJw z_Dgu5ECfTWGmxuD$EQX%F9PE4{U~p8c%oZ2GY9Bf_ZynzawA$i%6?AsRXhdXg^q5~qyy~ipp>pgjAOnfGPr zY+PBdUS-sqBT$bc#(E}-h-h@P)>~d&A|?lqzj^w%G+{0tHi(>i^qgh#*3_+TNU1lM zjJ^-WHCQsXUum(ETAE2-OY=L%99hW~2s8O2Sp;y%Y{;jT(=5xtir_u6A0zT~l@38{ z`eAyf=})k~G_zM(-RXTHv$A23MbTSlyd;{rBOvU@-tTbJ{A2R`h3WN}gXbMQ+wzZ=H} z9KB_F#^QD4hssoJUQs;){HT@4X(NoH6I2&^`F730=A(q8(cLFLa`Z{OnUbHUGV_z| zVgf%1=O-KvT&?Dv-*swuz8*I{5j0ueyCWCbuXWBB&#|1~w=4r0w0?J@9%i$dzp>oz znf!WPjNj!8vVvLDQN%5Z+b6QoqGXHPH0`AHB}SpqXKM8&d)CW#xly+aiM^$8nKkQIUCK6k1nNr*qD*weDyU5s~zF;Z(LGH-_?vuE2jDyI4 z^7>kXis3P~x{=bsyxK<=cjKIE;tYa{V+W$AG%6zBcG1xpd6qmfSbJNrC91fQZB*2K zgU6nu`(Y+-$b{$0$VbchCSiyEO$N?I{t1sM5bX=O39sw~Uv88M`%ry6@rXx_TGTfU zCyX1usdlPwJOpS7WHSXq625A-VI@AY_+;DJzsL4sWMBGd{b~~H=Y{fCriL8>{nqNb zWGVCJdg7@akZMN2o|8}jbI~vq%FucXN+<-R=6I2>$BD~s&}Q27iQvf+Cf{4UOq%*M znmserQL=unren5UBjGKAX$F=N!^W4beN(NPhd+-MIOCu4H-Lld zUOG(1)9KjTe^skqkZF!isn#4LH2v88DOey5SBl2wJp01`1cT;hhjyPdWf^kH=Kn^KtJrLy*j6J%3J|XBGAK34MLegq&G$_9zFlM*skk$HH4+fo!@)X)!r$+~H6qAW~=i|4ZBW~NnL%8j%) zrShhnBF{Bb!f#=#FQXzH#3_ojSnh{;CO)P%evHl1*NS(Qxn$wK%qbOS9V}ZZ|Baz{ z^AlZafq52E$r~qHs(5*zLf@&_3^Uvr_}s4ujJMmIr$$8kJ||fSurbIE%`PEa;r&Q7 zsUV!NJa!K^+vY&?%$1y-a)823@(YMx+mB5<_x+5;EAO#(&(jpI(JO07SPC+K$x10g zN{ayk4aVZYdoOI@9&zg<&%OC&hWv| z-f)>HE!qe;bDR8WawOQpX1{5bt>Gelil1Nn^}ca?%#`}(V?&MymXG@VwYD^nd&f)@ z4vCH*b98AF!LpD=sqcT@XRqIVu51Kz60#tdZNqhBZ1bwN=Ye%7h3gNP05sNP0Z^U=gc zfhFkbh*7i?!c|!EU$PZtObYKL1><5*kkdD0RqT(562*~7=vfYpmuv%wounurMsr*g zH$_lWAGFSBY{CnlrUNb~>1yL;y1G8Q-W)aJ_%80aEhtb0WPTZ(sZ{P84{()PSX;DqD%BzgkB#HtdyUJN8-!sl6p({M|ItovqN(KN z`{VVMRBY#(&nq{`Y-0;M^K;8x6U%1xSn$77Es{P4Mv8Jzzq`Ztbnrwc<&Ai@GwT9A z-Tor&%jV24s=;GkLN!c2cjfLkD3EYYXea3pq;`#0vsw>pY_d2jrMyUtOOqVH(M!m1 zNtQLc$vHGMZhIb=*nzBw6E9Le_7q#n=D{_%3dfk8D-b$J+gIbXNX!Gl0fwbEao@gj zHSv`%K6sPJto9((>mh<M_jJu3NO)PRr_6!;<_p3d&eM_x*oca`<@6s-7 zM8f=M7hc>kwPVC%-|9M-^!qk&oNGL(olgTB<$%7@mFPQ3#H2B9YSb953e(9}%vEGa z7iyOq4X&oT!= zHgwXI#1+$Skk~4H?m62IU5vjjw&DeMOYs)IX43>Xqe?_wO+T@yUnyN;z#+Yg2JDjO zv`grdND7NKcrO}CXN2V#pZp2=!kba5KhafpSZ$w*eRK7-xeo!0J=d*jOxX=CI?;xY z(VY*mccX3R4pn88LgMdwROp9V!zqYoblu{#I5DbzAlNqKv1Ptvvs`#oodAG!$mu{IpCg!aNC1|>BNfGT9`Ysbj~wisQ(Lz$qR-2lSxSvsd)goK-b znG_s}#cCHBmJ&{ar9G22=^N>zl_GDISC-rO8LrNoqsJyO;@t6o;iP#AIlEk8HHK_S zgnX3N#1%rUh#ncW+3?SD-nz)V5SQq+U|KDY3~zhqdcpuVS0YQ!bfj$1*MbF0U1Sgb zr%;hl46bI*S9I$)Oqfb0bk~jZS%^Q3R4h@pmydo*4qeF0Au&$YQn7CBnk7?3fc0_BuOvPw$a4X| zug2<00tI8g$BulX^wz)CPXj}ePW`F@YQka*AEbG*7->u*uf_z8TIPCUXj14gEgOP@ zVQR1B;=>|dMdu|26{9x2Dj#E0ebE9Or^SB_k&RI`;ddp_p(3v||F~;PEoc!wj_-SD za{2W{K}fkF7sFsen$%k)MfAgLC40UMOFR{C@_87z#(U}ogCx_?Vb^WmA2+1lUrupe zz`SKs@Vdn^ML-P6oowy$EBg3q!DqC0O-UrzpEJkc*S;Zz;!{+BJr26qNV|vD$*kl9 zWj+O?w)S;`-7il|dgKeHmW!%%sJ|Gm!Hax-Jxj#|`yM6A^;B>nOOv#KqNXAVT*U;F z9D?09@qYl}IGnHa`l5<-$@O-gl>sizf;&v*Fb`jSj}X2WnQ=4iYf0Gr&}mr1Pu-{a z4c4=q)p!oYdj}(;)Y`~31IA_DTtrp^VeV|5W7CCEPwUAewe554lCOh@p2f&F>0hQF z3@B`S(R}RQc{31M!|o&Km(|6J?U!QtQCFgX0n3-|2W1J4tyV_D#=_9T8Tmcn8!IGF z=r%19cTwik4YxXh@{m|k&xyx3N^*3GCZ+IcvvKs?+S2rsdLPHcW}6l|6)hW(MxlT{ zjoE%wO3|I(sk_^}!88~tUs)oWKg(MB6(Ru)dY5S{%(40_D-Bd2!|{~;lBB% z;OY*>L^0xd&92D6Xyc=K4q zXfmV1fnFX&MSF`1KA%$t*>8#FgQjY~(PFn!vRDy9Ig@PVcOT4fs#w`pka{s}$Vuz&2yy4K|fuQT17P19h5-n0Z&xu5;RQST5C>tK$`n zm%~y1RzTjAplqu}ks_5E1Mjp~Gab$%w5+y0;7e5y!=xZ0yD&x0(^j@lbqj!#;-oj8 ze`O%Cq#&3&ZcYnKYtIVNP-sWd&kGLTZj*2c+FDzO~wUjl7ZQ5e+O zocFyf@fh5BW_CM%aiuZ2F=j)`pgy(zJBS>AJA|Pawj1I60~8+5nrPG)K{ceNGUtrc zeq2DBF%=Ej844@T5K$hGymR&}hDHfH$JG>>8#p+YgBg@aWBJ*i!SJT)rtQt;JAwg* zo5Z9XhWDR<1_y-tX(Vxm2Sfu0b)u9Jv+lH#$m}?#QEIPTJE7-7cDBv0Z6DT)MBl1w zSig1b>AnUf5p7^n5WQlh<-j_I20J$s`4Ep=l-I$kLTgz2Ta@zNr+~{;J^Dn7E3_ZR zsoxU9O6KwnHa}Tb;~0Mb)*XO}#U%ToYK4|oH2xweee31(F(%VA&zq-suD!~s+vuAd zo(yr~4Ey7nyZ7Zj&TE~rhJr(NpfI0a*R&VbAEq0lGTH*TFCmN{-s^k2g+_Vg0dB`0 z+~iAgP9roi+RB!Sq4J5W%T6BvxJxtB2@S*^ax27_=j?e>?Gf@wsS3xJlOb!z<#&N6;7F9e#95SgSm0$~(c*`R=|)05#HjwVi2i6Y`Jl(E-b8Ah} zZaj9p%(FiNFmZAiEvxjcn^T5|wzIEeVe4f{nI!LquGl+sOkd*n(!d9>aYAS*C&G>4 zw~N6W#M%_|6)O*w!5w$ z1lOR(gFnpF=e9<~oZ%1paz9NI_0nyBB0JIk;3{exYhNl*$KybvV&g5D!C(s($A0!P z=fscFyRJSyeM^+#VgwqC1Tu(a)s3kr7Jm^Q)Y%<&8)q3y#|}ZY_^EoDN-{+sXwehQ z#3+67%RbPY#qH6%tr;Tm%K{Ux7oTzz6qmKQybFCLTkt}r@y#2I;IXdp&q~9};!JXW zcFVClX91(%r7$nw1($p?XipEj&~2Z`xzKB7I@vgx9rcY{xvK3yd-YoJR4eQJ(=hi? z#bL z3S?6}`X)Gf$QUc*v8nAQ9(IV-*P5_;U=~p3G6~NQ(BVJRWx`CBdgoNV+08$=a^eQAP>-9&biZy+A@={v4I^11|S?M zFR!H{FaPJQZ}biCjDRFb zd91l9ZU{`$Kz3t*XXJ`x5mwgpZ<15P)=u9>CRBPsGFJk&zYM-wy@PwIut;y6MziR# zSkf2s-X}}6J*HaKUqixf2q&7gv7+IkvvHxSO@Q56Tu6zDjg|;iUIXDtXV|;xqC4u| zeFMr@mQgQiU%+iYm(mJO9e(z#LBoxl;En|ar2=QMCDtun@;trUALU|>msqXRJ;}?_4lXvqE^9SNO+6 zzG)8G^YZ#)ht-EX{{FOZ6Y%V=%+D0I7uD=Pkc2-Zb^{gXQ*nHqLKaG=AG)y?k;$aL zRh`4Yi)>R4sUDY@n;o5-Zna6jyu`4scFnD^I*>;{qhLUypG6p|tBJ#%P<${eCrbpM zH_93PtO5f=LfYFI2DeAJ11%9YNJmM~&bw9+5NRa|G7wS+t2@ghY>~>ot_U4p4PCge zJzUHRBrQcK;Vq6PKq1^=KyQ?Tqno(5BtLbn6|93RSAUHxD5 zj&8qIKOgG4$1%0i3%H>5k#`rl&x zZEV*qzsmXRK+x&`;{6-?&)R>1(I|CwaRn#1$Mx`36eK~{{fk>U!I4(tzYc}rU_peS zfF-Z6B^1IdXf1}|6-B^Bc`YGOD+o-~5@Kly{R@?fqnkU-5stW~LX-0$(L8W#5d>Hi zCd6wcEGEb+C}auY6|+L0LZE_zR)QihF@%WFUnn$Pk!V-K9R510Ybq-=m9VG?T*Mk? z&5N*xB6tObVV1lwC`5=?03rsp28%+igdwnBR90|tMJHDj3_YDl6wC&}@9b#vtKnL3 zaak=DNf49|{O^dC1I*nTogfKPLppkR|GPmKi9+bO!>-kY2n!1f2nz{`iiin|2}2N+_&NP@111ia4t zWpALwACsbtbVDP2t{wl|^EwEZKhFMG0uIPuO+er;+ls^Be{|vo^F&zv>Ij|pM-$u@ z=4gXJukYW6`ZJFF50eEG5E6zXpm1JmurLfg-!K7QF-u`MuL#Uiz!Gi+2U}Vq{?6{^ zWbN(+b4AG7pesdJgEr8wYJi--q~!WL+RGMktrHl%wjp3%C`1<`A`TG~7Zl+EL&d>h z5dZIj`L9>?pA}2+|6iI&`~v)C8$jp%5kqe;=-rC{U)$Ah&8}tqUwr+Zi~q$Q(A58Q z@?Yuuk6i!A^Wg|BX3?i!Q4*(-QgAN_Ub5~Knhqp>dLq^5FFf;ZM0|TpF zML|{<{iCkguZ~k->fk}q?(^2_6Pw~xT=0kS*Gq2sCBwoV?|(FSV9pLz-w|7DN>%3K z9Z0Da?A`Z%qgQ}x6I**0QkeR*RY!b2py?+}by#%4fMVyE~^eHw#g1eiYIP z^*6Or(mDb2pt+_O0`Ekhj9Ntu5M;PrO>*S#(`B_DQ5vf+yi_r%E~4QNP+xfW zj`Us150wvxHfky&Z$3xR=!%+EFeN57?X*X`FiCCCvLACSNmM zB*fOZDJmCfb`U#qh9f>5C>IlB^c16?AnMa07tmHfWN{>I)vjkw+%(F8esSF=5 zjKAG6*|&*TY>D@^v>Eu$5aenym;hsjfqIV-7rjD(^(^yD2Fv>J=5~dKA?!_`I6sLx z)T*;zdP|*^ES2|IIO<2kM@Dc6Z>e)6>FI_Yiq$M2vkb@h`^tR!_hphT4)`haqb~b6 zF05lppLbF0jy;odX)UJ2A=W#J9aN15!#ED-141=-+q+zCGxzjj?5g-^AIP2fnU8+1D9g2j)8c1Cq#XILg?{T zr{)b8f9jEed2Oq6i~VD34>`KY0xaPG9EUTI#p+cI?T^CaQA;YHPrx&Yat+D{9XTI= zZY4cMe3n$NVsN<^#H#;oezc=??sRL($-}`+RXCtX>IUB;-C1;CW3Xvc-x0PEFZ%ao zW%WVPYl8)IWDaMYQhNy%P9IWWFSc$DNG*5Oo-osgh0j>pN;H!ZvpX}A{Bp?OOz_(w xfA!3tCircr{{;VSs9zTQKj6Ro7g2eM6$B`-_ltGVM=x*;m3tZr6>=7#{{vG6cNhQw literal 0 HcmV?d00001 diff --git a/apps/widmsggrid/widget.js b/apps/widmsggrid/widget.js new file mode 100644 index 000000000..7c5882e6c --- /dev/null +++ b/apps/widmsggrid/widget.js @@ -0,0 +1,92 @@ +(function () { + if (global.MESSAGES) return; // don't load widget while in the app + let settings = require('Storage').readJSON("messages.settings.json", true) || {}; + const s = { + flash: (settings.flash === undefined) ? true : !!settings.flash, + showRead: !!settings.showRead, + }; + delete settings; + WIDGETS["msggrid"] = { + area: "tl", width: 0, + flash: s.flash, + showRead: s.showRead, + init: function() { + // runs on first draw + delete w.init; // don't run again + Bangle.on("touch", w.touch); + Bangle.on("message", w.listener); + w.listener(); // update status now + }, + draw: function () { + if (w.init) w.init(); + // If we had a setTimeout queued from the last time we were called, remove it + if (w.t) { + clearTimeout(w.t); + delete w.t; + } + if (!w.width) return; + const b = w.flash && w.status === "new" && ((Date.now() / 1000) & 1), // Blink(= inverse colors) on this second? + // show multiple icons in a grid, by scaling them down + cols = Math.ceil(Math.sqrt(w.srcs.length - 0.1)); // cols===rows, -0.1 to work around rounding error + g.reset().clearRect(w.x, w.y, w.x + w.width - 1, w.y + 24) + .setClipRect(w.x, w.y, w.x + w.width - 1, w.y + 24); // guard against oversized icons + let r = 0, c = 0; // row, column + const offset = pos => Math.floor(pos / cols * 24); // pixel offset for position in row/column + w.srcs.forEach(src => { + const appColor = require("messages").getMessageImageCol(src, require("messages").getMessageImageCol("alert")); + let colors = [g.theme.bg, g.setColor(appColor).getColor()]; + if (b) { + if (colors[1] == g.theme.fg) colors = colors.reverse(); + else colors[1] = g.theme.fg; + } + g.setColor(colors[1]).setBgColor(colors[0]); + g.drawImage(require("messages").getMessageImage(src, "alert"), w.x+offset(c), w.y+offset(r), { scale: 1 / cols }); + if (++c >= cols) { + c = 0; + r++; + } + }); + if (w.total > 1) { + // show total number of messages in bottom-right corner + g.reset(); + if (w.total < 10) g.fillCircle(w.x + w.width - 5, w.y + 20, 4); // single digits get a round background, double digits fill their rectangle + g.setColor(g.theme.bg).setBgColor(g.theme.fg) + .setFont('6x8').setFontAlign(1, 1) + .drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9); + } + if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking + }, show: function () { + w.width = 24; + w.srcs = require("messages").getMessages() + .filter(m => !['call', 'map', 'music'].includes(m.id)) + .filter(m => m.new || w.showRead) + .map(m => m.src); + w.total = w.srcs.length; + w.srcs = w.srcs.filter((src, i, uniq) => uniq.indexOf(src) === i); // keep unique entries only + Bangle.drawWidgets(); + Bangle.setLCDPower(1); // turns screen on + }, hide: function () { + w.width = 0; + w.srcs = []; + w.total = 0; + Bangle.drawWidgets(); + }, touch: function (b, c) { + if (!w || !w.width) return; // widget not shown + if (process.env.HWVERSION < 2) { + // Bangle.js 1: open app when on clock we touch the side with widget + if (!Bangle.CLOCK) return; + const m = Bangle.appRect / 2; + if ((w.x < m && b !== 1) || (w.x > m && b !== 2)) return; + } + // Bangle.js 2: open app when touching the widget + else if (c.x < w.x || c.x > w.x + w.width || c.y < w.y || c.y > w.y + 24) return; + load("messages.app.js"); + }, listener: function () { + w.status = require("messages").status(); + if (w.status === "new" || (w.status === "old" && w.showRead)) w.show(); + else w.hide(); + } + }; + delete s; + const w = WIDGETS["msggrid"]; +})(); \ No newline at end of file diff --git a/apps/widmsggrid/widget.png b/apps/widmsggrid/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6e7b7ac81d667283a308096d19a04e822d1724 GIT binary patch literal 10457 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 literal 0 HcmV?d00001