forked from FOSS/BangleApps
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 <jt-git@nti.me.uk>master
parent
5c110a89fb
commit
5fa96ee5f7
|
@ -0,0 +1 @@
|
|||
0.01: Initial Medical Information application!
|
|
@ -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))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwg+7kUiCykCC4MgFykgDIIXUAQgAMiMRiREBC4YABkILBCxEBC4pHCC4kQFxIXEAAgXCGBERif/+QXHl//mIXJj//+YXHn//+IXL/8yCwsjBIIXNABIX/C63d7oDB+czmaPPC7hHR/oWBAAPfC65HRC7qnXX/4XDABAXkIIQAFI5wXXL/5f/L/5fvC9sTC5cxC5IAOC48BCxsQC44wOCxAArA"))
|
|
@ -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" });
|
Binary file not shown.
After Width: | Height: | Size: 713 B |
|
@ -0,0 +1,135 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css" />
|
||||
|
||||
<style type="text/css">
|
||||
.alert {
|
||||
padding: 20px;
|
||||
background-color: #f44336; /* Red */
|
||||
color: white;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="info"></div>
|
||||
|
||||
<button id="btnReload" class="btn btn-primary">Reload from watch</button>
|
||||
<button id="btnUpload" class="btn btn-primary">Upload to watch</button>
|
||||
<button id="btnDownload" class="btn btn-primary">Download</button>
|
||||
|
||||
<pre id="medicalinfo" contenteditable></pre>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
const medicalInfoFile = "medicalinfo.json";
|
||||
|
||||
function errorFormat() {
|
||||
var date = new Date();
|
||||
var error =
|
||||
'<p class="alert">' +
|
||||
date.toUTCString() +
|
||||
" : Wrong format, it should be JSON" +
|
||||
"</p>";
|
||||
return error;
|
||||
}
|
||||
|
||||
function getEditableContent() {
|
||||
return document.getElementById("medicalinfo").innerHTML.replace(/<[^>]*>/g, '');;
|
||||
}
|
||||
|
||||
function isJsonString(str) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
console.log(str)
|
||||
console.log(e)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function uploadFile(fileid, contents) {
|
||||
Puck.write(
|
||||
`\x10(function() {
|
||||
require("Storage").write("${fileid}",'${contents}');
|
||||
Bluetooth.print("OK");
|
||||
})()\n`,
|
||||
(ret) => {
|
||||
console.log("uploadFile", ret);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* Load settings JSON file from the watch.
|
||||
*/
|
||||
function loadMedicalInfo() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Loading...");
|
||||
Puck.eval(`require('Storage').readJSON("${medicalInfoFile}")`, (data) => {
|
||||
document.getElementById("medicalinfo").innerHTML = JSON.stringify(
|
||||
data,
|
||||
null,
|
||||
2
|
||||
);
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
/* Save settings as a JSON file on the watch.
|
||||
*/
|
||||
function uploadMedicalInfo() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Uploading...");
|
||||
let medicalInfoJson = getEditableContent();
|
||||
if (isJsonString(medicalInfoJson)) {
|
||||
let shortMedicalInfoJson = JSON.stringify(JSON.parse(medicalInfoJson));
|
||||
uploadFile(medicalInfoFile, shortMedicalInfoJson);
|
||||
} else {
|
||||
document.getElementById("info").innerHTML = errorFormat();
|
||||
}
|
||||
Util.hideModal();
|
||||
}
|
||||
|
||||
function downloadMedicalInfo() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Downloading...");
|
||||
let medicalInfoJson = getEditableContent();
|
||||
if (isJsonString(medicalInfoJson)) {
|
||||
var a = document.createElement("a"),
|
||||
file = new Blob([medicalInfoJson], { type: "application/json" });
|
||||
var url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = medicalInfoFile;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
} else {
|
||||
document.getElementById("info").innerHTML = errorFormat();
|
||||
}
|
||||
Util.hideModal();
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("btnUpload")
|
||||
.addEventListener("click", function () {
|
||||
uploadMedicalInfo();
|
||||
});
|
||||
document
|
||||
.getElementById("btnDownload")
|
||||
.addEventListener("click", function () {
|
||||
downloadMedicalInfo();
|
||||
});
|
||||
document
|
||||
.getElementById("btnReload")
|
||||
.addEventListener("click", function () {
|
||||
loadMedicalInfo();
|
||||
});
|
||||
function onInit() {
|
||||
loadMedicalInfo();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -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;
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"bloodType": "",
|
||||
"height": "",
|
||||
"weight": "",
|
||||
"medicalAlert": [ "" ]
|
||||
}
|
|
@ -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"}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -1 +1,2 @@
|
|||
0.01: Initial Medical Alert Widget!
|
||||
0.02: Use Medical Information app for medical alert text, and to display details
|
||||
|
|
|
@ -11,12 +11,12 @@ Implemented:
|
|||
- Basic medical alert logo and message
|
||||
- Only display bottom widget on clocks
|
||||
- High contrast colours depending on theme
|
||||
- Configure medical alert text (using Medical Information app)
|
||||
- Show details when touched (using Medical Information app)
|
||||
|
||||
Future:
|
||||
|
||||
- Configure when to show bottom widget (always/never/clocks)
|
||||
- Configure medical alert text
|
||||
- Show details when touched
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
{ "id": "widmeda",
|
||||
"name": "Medical Alert Widget",
|
||||
"shortName":"Medical Alert",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Display a medical alert in the bottom widget section.",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"dependencies" : { "medicalinfo":"app" },
|
||||
"tags": "health,medical,tools,widget",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
(() => {
|
||||
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");
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue