From 5fa96ee5f77dd617b90ffff9904882972d5ce578 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 24 Oct 2022 12:22:35 +0100 Subject: [PATCH] New medical info app Initial implementation to enable custom text in the medical alert widget Possible future updates could include better editing UI instead of raw json, more information eg. medications, ability to set info from other apps, etc. Signed-off-by: James Taylor --- apps/medicalinfo/ChangeLog | 1 + apps/medicalinfo/README.md | 27 ++++++ apps/medicalinfo/app-icon.js | 1 + apps/medicalinfo/app.js | 61 ++++++++++++ apps/medicalinfo/app.png | Bin 0 -> 713 bytes apps/medicalinfo/interface.html | 135 ++++++++++++++++++++++++++ apps/medicalinfo/lib.js | 21 ++++ apps/medicalinfo/medicalinfo.json | 6 ++ apps/medicalinfo/metadata.json | 20 ++++ apps/medicalinfo/screenshot_light.png | Bin 0 -> 3053 bytes apps/widmeda/ChangeLog | 1 + apps/widmeda/README.md | 4 +- apps/widmeda/metadata.json | 3 +- apps/widmeda/widget.js | 35 +++++-- 14 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 apps/medicalinfo/ChangeLog create mode 100644 apps/medicalinfo/README.md create mode 100644 apps/medicalinfo/app-icon.js create mode 100644 apps/medicalinfo/app.js create mode 100644 apps/medicalinfo/app.png create mode 100644 apps/medicalinfo/interface.html create mode 100644 apps/medicalinfo/lib.js create mode 100644 apps/medicalinfo/medicalinfo.json create mode 100644 apps/medicalinfo/metadata.json create mode 100644 apps/medicalinfo/screenshot_light.png diff --git a/apps/medicalinfo/ChangeLog b/apps/medicalinfo/ChangeLog new file mode 100644 index 000000000..e8739a121 --- /dev/null +++ b/apps/medicalinfo/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Medical Information application! diff --git a/apps/medicalinfo/README.md b/apps/medicalinfo/README.md new file mode 100644 index 000000000..6dd19d4c6 --- /dev/null +++ b/apps/medicalinfo/README.md @@ -0,0 +1,27 @@ +# Medical Information + +This app displays basic medical information, and provides a common way to set up the `medicalinfo.json` file, which other apps can use if required. + +## Medical information JSON file + +When the app is loaded from the app loader, a file named `medicalinfo.json` is loaded along with the javascript etc. +The file has the following contents: + +``` +{ + "bloodType": "", + "height": "", + "weight": "", + "medicalAlert": [ "" ] +} +``` + +## Medical information editor + +Clicking on the download icon of `Medical Information` in the app loader invokes the editor. +The editor downloads and displays the current `medicalinfo.json` file, which can then be edited. +The edited `medicalinfo.json` file is uploaded to the Bangle by clicking the `Upload` button. + +## Creator + +James Taylor ([jt-nti](https://github.com/jt-nti)) diff --git a/apps/medicalinfo/app-icon.js b/apps/medicalinfo/app-icon.js new file mode 100644 index 000000000..1ae7916fb --- /dev/null +++ b/apps/medicalinfo/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg+7kUiCykCC4MgFykgDIIXUAQgAMiMRiREBC4YABkILBCxEBC4pHCC4kQFxIXEAAgXCGBERif/+QXHl//mIXJj//+YXHn//+IXL/8yCwsjBIIXNABIX/C63d7oDB+czmaPPC7hHR/oWBAAPfC65HRC7qnXX/4XDABAXkIIQAFI5wXXL/5f/L/5fvC9sTC5cxC5IAOC48BCxsQC44wOCxAArA")) diff --git a/apps/medicalinfo/app.js b/apps/medicalinfo/app.js new file mode 100644 index 000000000..9c4941744 --- /dev/null +++ b/apps/medicalinfo/app.js @@ -0,0 +1,61 @@ +const medicalinfo = require('medicalinfo').load(); +// const medicalinfo = { +// bloodType: "O+", +// height: "166cm", +// weight: "73kg" +// }; + +function hasAlert(info) { + return (Array.isArray(info.medicalAlert)) && (info.medicalAlert[0]); +} + +// No space for widgets! +// TODO: no padlock widget visible so prevent screen locking? + +g.clear(); +const bodyFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; +g.setFont(bodyFont); + +const title = hasAlert(medicalinfo) ? "MEDICAL ALERT" : "Medical Information"; +var lines = []; + +lines = g.wrapString(title, g.getWidth() - 10); +var titleCnt = lines.length; +if (titleCnt) lines.push(""); // add blank line after title + +if (hasAlert(medicalinfo)) { + medicalinfo.medicalAlert.forEach(function (details) { + lines = lines.concat(g.wrapString(details, g.getWidth() - 10)); + }); + lines.push(""); // add blank line after medical alert +} + +if (medicalinfo.bloodType) { + lines = lines.concat(g.wrapString("Blood group: " + medicalinfo.bloodType, g.getWidth() - 10)); +} +if (medicalinfo.height) { + lines = lines.concat(g.wrapString("Height: " + medicalinfo.height, g.getWidth() - 10)); +} +if (medicalinfo.weight) { + lines = lines.concat(g.wrapString("Weight: " + medicalinfo.weight, g.getWidth() - 10)); +} + +lines.push(""); + +// TODO: display instructions for updating medical info if there is none! + +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 < titleCnt ? g.theme.bg2 : g.theme.bg). + setColor(idx < titleCnt ? g.theme.fg2 : g.theme.fg). + clearRect(r.x, r.y, r.x + r.w, r.y + r.h); + g.setFont(bodyFont).drawString(lines[idx], r.x, r.y); + } +}); + +// Show launcher when button pressed +setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" }); diff --git a/apps/medicalinfo/app.png b/apps/medicalinfo/app.png new file mode 100644 index 0000000000000000000000000000000000000000..16204ea894c93614f302be7e8c08c04a2c7d884f GIT binary patch literal 713 zcmV;)0yh1LP)JyIpq(iM5zZq3Z;jN z6c2hTQZMl&c=Hfb&q}={R4ix@_QTTt0p5DZWwD-&VEu|#yx8oHhbD21yO~{gHhakY zPCGmA%)C$D_t_iMI3H%}!5LWS&Q5ZUz9FZ503{2(V&Xwc`}b0I&uiVS_x7eV%jyjkY0RH|MTRzMq|* zs8|}kQJ+00`#9Z8P&ljJw z*4UNR)#ft;egI@kr&eygxFGb5&;!}28G}*Hsi)ft*styXs#d>UsS%t5`f`kIfC!kzvR8UQiYALy%wEjX(lg+s19qshwoe(F!OJIMCf5;CtK4XEY3y2ckw= z1-!IO7#`^E3b^&OUz7*_moR$TFWp@MBfTL8NRfWJBE3 + + + + + + +
+ + + + + +

+
+    
+    
+  
+
diff --git a/apps/medicalinfo/lib.js b/apps/medicalinfo/lib.js
new file mode 100644
index 000000000..683005359
--- /dev/null
+++ b/apps/medicalinfo/lib.js
@@ -0,0 +1,21 @@
+const storage = require('Storage');
+
+exports.load = function () {
+  const medicalinfo = storage.readJSON('medicalinfo.json') || {
+    bloodType: "",
+    height: "",
+    weight: "",
+    medicalAlert: [""]
+  };
+
+  // Don't return anything unexpected
+  const expectedMedicalinfo = [
+    "bloodType",
+    "height",
+    "weight",
+    "medicalAlert"
+  ].filter(key => key in medicalinfo)
+    .reduce((obj, key) => (obj[key] = medicalinfo[key], obj), {});
+
+  return expectedMedicalinfo;
+};
diff --git a/apps/medicalinfo/medicalinfo.json b/apps/medicalinfo/medicalinfo.json
new file mode 100644
index 000000000..8b49725cb
--- /dev/null
+++ b/apps/medicalinfo/medicalinfo.json
@@ -0,0 +1,6 @@
+{
+    "bloodType": "",
+    "height": "",
+    "weight": "",
+    "medicalAlert": [ "" ]
+}
diff --git a/apps/medicalinfo/metadata.json b/apps/medicalinfo/metadata.json
new file mode 100644
index 000000000..f1a0c145f
--- /dev/null
+++ b/apps/medicalinfo/metadata.json
@@ -0,0 +1,20 @@
+{ "id": "medicalinfo",
+  "name": "Medical Information",
+  "version":"0.01",
+  "description": "Provides 'medicalinfo.json' used by various health apps, as well as a way to edit it from the App Loader",
+  "icon": "app.png",
+  "tags": "health,medical",
+  "type": "app",
+  "supports" : ["BANGLEJS","BANGLEJS2"],
+  "readme": "README.md",
+  "screenshots": [{"url":"screenshot_light.png"}],
+  "interface": "interface.html",
+  "storage": [
+    {"name":"medicalinfo.app.js","url":"app.js"},
+    {"name":"medicalinfo.img","url":"app-icon.js","evaluate":true},
+    {"name":"medicalinfo","url":"lib.js"}
+  ],
+  "data": [
+    {"name":"medicalinfo.json","url":"medicalinfo.json"}
+  ]
+}
diff --git a/apps/medicalinfo/screenshot_light.png b/apps/medicalinfo/screenshot_light.png
new file mode 100644
index 0000000000000000000000000000000000000000..42970f9fc8589f7947e12e868ad1a43b3aeb6227
GIT binary patch
literal 3053
zcmeH}`#%#77soXtlaxzxm;1e#CaUo@oBRDv#&TPjO0KzOslLV#M&_FDQtqR%i7(d0
zC47~~)9uk(4m&imk8oUD%v%L?=H@g29dfxGc)
z%YQ;hkeA7{&jGx0B*x7e#@9S5&*tMh_RJP;;gJ}yhFnYf+$dW1@eq+v6Z$XYbm~H-UWCr~VCy#GrM8Rf@BF(H!>Y7%{pMFqpaJ_LyMNtd
zKA37?VD5MNVTU230f8-V`u`n~P(
zlsg8YNNx+D0h1j+j9%JnKAw>I0||ICyPq@sfj(4jBRyq0W@1*!f28{uR`N!I{6ujh
zfE2CvrT&e1T#D|t`nE(T^kZ$k6(Ob=29Be3oiz1`Rms-0(Gpl6jTGpC
zE2HDd5$M@)VSI#tY>EdKU1v5^#nZ)h020<`qb_|G7m$jGHFm1&k3XJ*dv&gfJ|;T{
zxhZ-|0SEJD*#++8!HR^ZT;Wu~-?D*U`MU&jx83uaJ@aI%yil>4>$m-17*VH<5~ii)
zG|WOr#o}5xKZH`Cs%%+KT2>rEywYs_#GSl(oywO&^6BZPrjQx-or2?p8R3+4y~M%s
zlPv(=D!o`$zq(gT#?Zl$3K?UD&dd)vxcA5&j_BgI0qbmx7e%x?BG5lt%#Q9em|oOJS!s&f2$GIeFDa|F{lO
zhdOk%493l^vY5%^{1Y^2oPMh64T2ALai;ek;E`}()0~HZD5KPib>aMd;0S$*uaF|u9MM-%gl{mvOZq}&U`8O*k=j(
ziA4&rE6f=B76aaFMTgvt&_zOEL*rVbjGbq0!=`gGS9`_oaaZ(}rH=OTx+pf&XVn23H;39xw4{zmlpu!UwRs}$
zPx~xdd0l#LRC`=Hs2r>>n)TmCPxZVWYvUZG@o&rp7(c94*eh_1olV#b?h$F5={%I^
z*e0npDP1$fPTFn{qUm7rb9)w=m47a`>IF*Iz)J4wz1&5aW{Lrp{Q7qrQOv?Rjj-H?
zIImIagsK@Wz1r23t&R5ESR1&OI`RBX-fq-|FZN~Kf#{$mue6tS@|-A8;|IsA0}-Yd
z&YjtaDImN%*j_@LaXRw~E4&pRz0B-eifC_IYUf6Bp`qD~KFnP^y0z0CrYynGuBfsG
zlP_H|ziheq@d2HL6as$E0$PRIE5HHiVE`;G1JGWA&>NQ@4f
z>WLSlXV$9ZG3r2m42OW1>MRAQauGNs?fa5?GOzIXKU0Xid2OwL)7{tAuMTbvIm$uZ
zYgba$Oo^?ItUeIlI6vvB0oJ_AT!*{Iv4g;Dy@IlJm?P4R#G)566T=}!T;EJ77dxh7
zYbZmY{a;(&b0f~#t-_G!xrz%z!I|Z&d&E+6iReSJJ)58Ar?-Nv6K9B*1lW@WR0{bR
zDp@BJdvRe+88{=DhQ^)zX!7Qd4SLSe;k`8AnFPEX`ux5g~3iomS|FqU+&+tSsp2BxP&ceIi19^l2GC@KZRbO)QQoe
zl1k>@n!`~`xuR}l&A|}`&HkrqNL_5%-TIJ-Y28IF>NV&{#k(jxMxl7R%wD6V)L|mc
z850{xv(EZzSQGDQQ#VyCy&9}Dz&@Z|T*ln0Fsy{5fHrF;`!uftm6$D|^jw4xUa|_x
zWDK2_%!7S35{lcB|32=v@9uifK?c-#_i~byMVVlJ%jt7J5vJ~~gJE~<1-uGRDz~@^
z*HOm`V90IZEoA`de)Wl@GoDjQ-k4+@!cbS|!lFmu=(EOY;BUkkIO@yLRR?bWgSW8u
zU$V~UhI~sd8Z_J0G^@g{=rUFY&}A%O8)=j)+NDm&|i_qCl4mT;MFfx8uq72xv@ur<`h}-vrH$rTJK&
zKK;3y8HrGR!ft0;jf!iXZYXfW?cb*-b8ET!V*;MHTRlEd^u(sJD?jXX_v-Rh-%3|#
zpjOIyVL-8ry(w-;>%JvRDqIrW>b+_MC76<)p&%fwrH_ds)Riz9oHa7pYyS}k_So15
zcz)}zni{ifqe9rRl2xk^2`2Ml{!SBpe(+G)U}YRk-f6*E126hv$9}C|3@d_@%F5?#
zIANLz%I`C2+RROie4~Q<{0-@DIm2mgide~u!VO?W_agT!r>_pe
zuDn@uZe?hbAdfUL+6@M^mLg}CvH3Gif}3JLWJv?Bfo6AoHFLrNn(8eQ842lzdu*7C
zfI+`e$XLVLy8q`w=t2=V!X
zPY4eqGT=)gFgkFQaC|^T6$Kc6IIS$f!^l&NiImI{37-ceqQsL1SfkL_t%Ls`{XZ;x
a=P0M>wWyHO$rrr;ozE8G1aF3+?*1ENc&b {
+  function getAlertText() {
+    const medicalinfo = require("medicalinfo").load();
+    const alertText = ((Array.isArray(medicalinfo.medicalAlert)) && (medicalinfo.medicalAlert[0])) ? medicalinfo.medicalAlert[0] : "";
+    return (g.wrapString(alertText, g.getWidth()).length === 1) ? alertText : "MEDICAL ALERT";
+  }
+
   // Top right star of life logo
-  WIDGETS["widmedatr"]={
+  WIDGETS["widmedatr"] = {
     area: "tr",
     width: 24,
-    draw: function() {
+    draw: function () {
       g.reset();
       g.setColor("#f00");
       g.drawImage(atob("FhYBAAAAA/AAD8AAPwAc/OD/P8P8/x/z/n+/+P5/wP58A/nwP5/x/v/n/P+P8/w/z/Bz84APwAA/AAD8AAAAAA=="), this.x + 1, this.y + 1);
     }
   };
 
-  // Bottom medical alert message
-  WIDGETS["widmedabl"]={
+  // Bottom medical alert text
+  WIDGETS["widmedabl"] = {
     area: "bl",
-    width: Bangle.CLOCK?Bangle.appRect.w:0,
-    draw: function() {
+    width: Bangle.CLOCK ? Bangle.appRect.w : 0,
+    draw: function () {
       // Only show the widget on clocks
       if (!Bangle.CLOCK) return;
 
       g.reset();
       g.setBgColor(g.theme.dark ? "#fff" : "#f00");
       g.setColor(g.theme.dark ? "#f00" : "#fff");
-      g.setFont("Vector",18);
-      g.setFontAlign(0,0);
+      g.setFont("Vector", 16);
+      g.setFontAlign(0, 0);
       g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23);
-      g.drawString("MEDICAL ALERT", this.width / 2, this.y + ( 23 / 2 ));
+
+      const alertText = getAlertText();
+      g.drawString(alertText, this.width / 2, this.y + (23 / 2));
     }
   };
+
+  Bangle.on("touch", (_, c) => {
+    const bl = WIDGETS.widmedabl;
+    const tr = WIDGETS.widmedatr;
+    if ((bl && c.x >= bl.x && c.x < bl.x + bl.width && c.y >= bl.y && c.y <= bl.y + 24)
+      || (tr && c.x >= tr.x && c.x < tr.x + tr.width && c.y >= tr.y && c.y <= tr.y + 24)) {
+      load("medicalinfo.app.js");
+    }
+  });
 })();