diff --git a/apps/drinkcounter/ChangeLog b/apps/drinkcounter/ChangeLog new file mode 100644 index 000000000..69faa7904 --- /dev/null +++ b/apps/drinkcounter/ChangeLog @@ -0,0 +1,3 @@ +0.10: Initial release - still work in progress +0.15: Added settings and calculations +0.20: Added status saving \ No newline at end of file diff --git a/apps/drinkcounter/README.md b/apps/drinkcounter/README.md new file mode 100644 index 000000000..f1700531b --- /dev/null +++ b/apps/drinkcounter/README.md @@ -0,0 +1,12 @@ +# Drink Counter + +Development still in progress. Counts drinks you had for science. Calculates BAC. + +## Usage + +Swipe left/right to select drink. Swipe up/down to add/remove drinks. + + +## Creator + +Hank - contact at http://forum.espruino.com diff --git a/apps/drinkcounter/app.js b/apps/drinkcounter/app.js new file mode 100644 index 000000000..47a4feb13 --- /dev/null +++ b/apps/drinkcounter/app.js @@ -0,0 +1,271 @@ +g.reset().clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +require("Font8x16").add(Graphics); + +const SETTINGSFILE = "drinkcounter.json"; +setting = require("Storage").readJSON("setting.json",1); +E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ +var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; +var ampm = "AM"; +let drag; + +var icoBeer = require("heatshrink").decompress(atob("lEoxH+AG2BAAoecEpAoWC4fXAAIGGAAowTDxAmJE4YGGE5QeJE5QHHE7owJE0pQKE7pQJE86fnE5QJSE5YUHBAIJQYxIpFAAvGBBAJIExYoGDgIACBBApFExonCDYoAOFSAnbFJYnE6vVDYYFHAwakQE4YaFAoQGJEIYoME7QoEE7ogFE/4neTBgntY84n/E+7HUE64mDE8IAFEw4nDTBifIE9gmId7gALE5IGCAooGDE6gASE8yaME7gmOFIgAREqIAhA==")); +var icoCocktail = require("heatshrink").decompress(atob("lEoxH+AH4AJtgABEkgmiEiXGAAIllAAiXeEAPXAQQDCFBYmTEgYqDFBZNWAIZRME6IfBEAYuEE5J2UwIAaJ5QncFBB3DB4YGCACQnKTQgoXE5bIEE6qfKPAZRFA4MUABgmNPAonBCgQnPExgpFPIgoNEyBSF4wGBFBgmSABCjJTZwoXEzwoHE0AoFE0QnCFAQmhKAonjFAInCE0Qn/E/4n/E/4n/wInDFEAhBEwQoDFLYdCEwooEFTAjHAAwoYIYgAMPDglT")); +var icoShot = require("heatshrink").decompress(atob("lEoxH+AH4A/AH4A/AH4AqwIAgE+HXADRPME8ZQM5AnSZBQkGAAYngEYonfJA5QQE8zGJFAYfKFBwmKE4iYIE7rpIeYgAJE5woEEpQKHTxhQIIpJaHJxgn/E8zGQZBAnQYxxQRFQYnlFgon5FCYmDE6LjHZRQmPE5AAOE/4njFCTGQKCwmRKAgATE54oWEyAqTDZY")); +var icoReset = require("heatshrink").decompress(atob("j0egILI8ACBh4DC/4DBh4DCv8f4ED8EPwEPEQMAvEAnkB4EA+AKBCAM8DYOA8EB//HwED/wXBg/wnAOC+EAjkDDoMgg+AJoRFCEIIAB/kHgEB/l8FwP/DYIDBC4MD/ASBgYeCAAw")); +var drawTimeout; +var activeDrink = 0; +var drinks = [0,0,0]; +const maxDrinks = 2; // 3 drinks +var firstDrinkTime = null; +var firstDrinkTimeTime = null; + +var confBeerSize; +var confSex; +var confWeight; +var confWeightUnit; + + +// Load Status =============== +var drinkStatus = require("Storage").open("drinkcounter.status.json", "r"); +var test = drinkStatus.read(drinkStatus.getLength()); +if(test!== undefined) { + drinkStatus = JSON.parse(test); + //console.log("read status: " + test); + for (let i = 0; i <= maxDrinks; i++) { + drinks[i] = drinkStatus.drinks[i]; + } + firstDrinkTime = Date.parse(drinkStatus.firstDrinkTime); + //console.log("read firstDrinkTime: " + firstDrinkTime); + if (firstDrinkTime) firstDrinkTimeTime = require("locale").time(new Date(firstDrinkTime), 1); + //console.log("read firstDrinkTimeTime: " + firstDrinkTimeTime); +} else { + drinkStatus = { + drinks: [0,0,0] + }; + //console.log("no status file - applying default"); +} +// Load Status =============== + + +var drinksAlcohol = [12,16,5.6]; // in gramm +// Beer: 0.3L 12g - 0.5L 20g +// Radler: 0.3L 6g - 0.5L 10g +// Wine: 0.2L 16g +// Jäger Shot: 0.02L 5.6g + +// sex: Women 60 - Men 70 (Percent) +// Formula: Alcohol in g /(Body weight in kg x sex) – (0,15 x Hours) = bac per mille +// Example: 5 Beer (0.3L=12g), 80KG, Male (70%), 5 hours +// (5 * 12) / (80 / 100 * 70) - (0.15 * 5) + +function drawBac(){ + if (firstDrinkTime) { + var sum_drinks = (drinks[0] * drinksAlcohol[0]) + (drinks[1] * drinksAlcohol[1]) + (drinks[2] * drinksAlcohol[2]); + + if (confSex == "male") { + sex = 70; + } else { + sex = 60; + } + var weight = confWeight; + + if (confWeightUnit == "US Pounds") { + weight = weight * 0.45359237; + } + var currentTime = new Date(); + var time_diff = Math.floor(((currentTime - firstDrinkTime) % 86400000) / 3600000); // in hours! + //console.log("currentTime: " + currentTime) + //console.log("firstDrinkTime: " + firstDrinkTime) + + //console.log("timediff: " + time_diff); + ebac = Math.round( ((sum_drinks) / (weight / 100 * sex) - (0.15 * time_diff) ) * 100) / 100; + + //console.log("BAC: " + ebac + " weight: " + confWeight + " weightInKilo: " + weight + " Unit: " + confWeightUnit); + //console.log("sum_drinks: " + sum_drinks); + g.clearRect(0,34 + 20 + 8,176,34 + 20 + 20 + 8); //Clear + g.setFontAlign(0,0).setFont("8x16").setColor(g.theme.fg).drawString("BAC: " + ebac, 90, 74); + } +} + + +// Load settings +function loadMySettings() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + confBeerSize = def(settings.beerSize, "0.3L"); + confSex = def(settings.sex, "male"); + confWeight = def(settings.weight, 80); + confWeightUnit = def(settings.weightUnit, "Kilo"); + //console.log("Read config - weight: " + confWeight); +} + + +function updateTime(){ + var d = require("locale").time(new Date(), 1); + + //console.log(d); + var time = d.split(":"); + var hours = time[0]; + var minutes = time[1]; + if (_12hour){ + //do 12 hour stuff + if (hours > 12) { + ampm = "PM"; + hours = hours - 12; + if (hours < 10) hours = doublenum(hours); + } else { + ampm = "AM"; + } + } else { + ampm = ""; + } + g.setBgColor(g.theme.bg).clearRect(0,24,176,44); //Clear + g.setFontAlign(0,0); // center font + g.setBgColor(g.theme.bg).setColor(g.theme.fg); + g.setFont("8x16").drawString("Time: " + hours + ":" + minutes + " " + ampm,90,34); + queueDrawTime(); +} + +function queueDrawTime() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + updateTime(); + }, 20000 - (Date.now() % 20000)); +} + + +function updateDrinks(){ + g.setBgColor(g.theme.bg).clearRect(0,145,176,176); //Clear + for (let i = 0; i <= maxDrinks; i++) { + if (i == activeDrink) { + g.setColor(g.theme.fg).fillRect((40 * (i + 1)) - 40 ,145,(40 * (i + 1)),176); + g.setColor(g.theme.bg); + } else { + g.setColor(g.theme.fg); + } + g.setFont("Vector",20).drawString(drinks[i], (40 * (i + 1)) - 20, 160); + g.setColor(g.theme.fg); + drinkStatus.drinks[i] = drinks[i]; + } + + g.setBgColor(g.theme.bg).setColor(g.theme.fg); + g.drawImage(icoReset,145,145); + + drinkStatus.firstDrinkTime = firstDrinkTime; + settings_file = require("Storage").open("drinkcounter.status.json", "w"); + settings_file.write(JSON.stringify(drinkStatus)); + + drawBac(); +} + +function updateFirstDrinkTime(){ + if (firstDrinkTime){ + g.setFont("8x16"); + g.setFontAlign(0,0).drawString("1st drink @ " + firstDrinkTimeTime, 90, 34 + 20 ); + } +} + +function addDrink(){ + if (!firstDrinkTime){ + firstDrinkTime = new Date(); + firstDrinkTimeTime = require("locale").time(new Date(), 1); + //console.log("init drinking! " + firstDrinkTime); + } + drinks[activeDrink] = drinks[activeDrink] + 1; + updateFirstDrinkTime(); + updateDrinks(); +} + +function removeDrink(){ + if (drinks[activeDrink] > 0) drinks[activeDrink] = drinks[activeDrink] - 1; + updateDrinks(); +} + +function previousDrink(){ + if (activeDrink > 0) activeDrink = activeDrink - 1; + updateDrinks(); +} + +function nextDrink(){ + if (activeDrink < maxDrinks) activeDrink = activeDrink + 1; + updateDrinks(); +} + +function showDrinks() { + g.setBgColor(g.theme.bg); + g.drawImage(icoBeer,0,100); + g.drawImage(icoCocktail,40,100); + g.drawImage(icoShot,80,100); +} + +function initDragEvents() { + Bangle.on("drag", e => { + if (!drag) { // start dragging + drag = {x: e.x, y: e.y}; + } else if (!e.b) { // released + const dx = e.x-drag.x, dy = e.y-drag.y; + drag = null; + if (Math.abs(dx)>Math.abs(dy)+10) { + // horizontal + if (dx < dy) { + //console.log("left " + dx + " " + dy); + previousDrink(); + } else { + //console.log("right " + dx + " " + dy); + nextDrink(); + } + } else if (Math.abs(dy)>Math.abs(dx)+10) { + // vertical + if (dx < dy) { + //console.log("down " + dx + " " + dy); + removeDrink(); + } else { + //console.log("up " + dx + " " + dy); + addDrink(); + } + } else { + //console.log("tap " + e.x + " " + e.y); + if (e.x > 145 && e.y > 145) { + g.clearRect(0,34,176,176); //Clear + resetDrinks = E.showPrompt("Reset drinks?", { + title: "Confirm", + buttons: { Yes: true, No: false }, + }); + resetDrinks.then((confirm) => { + if (confirm) { + for (let i = 0; i <= maxDrinks; i++) { + drinks[i] = 0; + } + //console.log("reset to default"); + } + //console.log("reset " + confirm); + firstDrinkTime = null; + showDrinks(); + updateDrinks(); + updateTime(); + updateFirstDrinkTime(); + }); + } + } + } +}); +} + +loadMySettings(); + +showDrinks(); + + +if (drawTimeout) clearTimeout(drawTimeout); +drawTimeout = undefined; +updateTime(); +queueDrawTime(); +initDragEvents(); +updateDrinks(); +updateFirstDrinkTime(); + diff --git a/apps/drinkcounter/drinkcounter-icon.js b/apps/drinkcounter/drinkcounter-icon.js new file mode 100644 index 000000000..e7b95f9ef --- /dev/null +++ b/apps/drinkcounter/drinkcounter-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AAWBAAomkFpAweD4fXAAIGGAAo4bExAuJF4YGGF6QmJF5QHHF8o4JF1pgSF7pgRF96/vF5QJSF6YcHBAIJQdyIxFAAvGBBAJIFyYwGEgIACBBAxFFyovCEYoAOGTAvbGKYvE6vVEYYFHAwbEYF4YiFAoQGJFIYwUF7QwEF8ooFF/4v2XBgv1d94v/F/7vsF64uDF9IAFFx4vDXBi/IF+guQR6wvCFSIvOAwQFFAwYvcACQvuXSgvcFywxEACItZAH4A/AH4AlA==")) diff --git a/apps/drinkcounter/drinkcounter.png b/apps/drinkcounter/drinkcounter.png new file mode 100644 index 000000000..91a0cd4ad Binary files /dev/null and b/apps/drinkcounter/drinkcounter.png differ diff --git a/apps/drinkcounter/metadata.json b/apps/drinkcounter/metadata.json new file mode 100644 index 000000000..3d1a167f0 --- /dev/null +++ b/apps/drinkcounter/metadata.json @@ -0,0 +1,24 @@ +{ + "id": "drinkcounter", + "name": "Drink Counter", + "shortName": "Drink Counter", + "version": "0.20", + "description": "Counts drinks you had for science. Calculates blood alcohol content (BAC)", + "allow_emulator":true, + "icon": "drinkcounter.png", + "type": "app", + "tags": "health", + "screenshots": [{"url":"screenshot_drnkcnt.png"}], + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"drinkcounter.app.js","url":"app.js"}, + {"name":"drinkcounter.img","url":"drinkcounter-icon.js","evaluate":true}, + {"name":"drinkcounter.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"drinkcounter.settings.json"}, + {"name":"drinkcounter.json"}, + {"name":"drinkcounter.status.json"} + ] +} \ No newline at end of file diff --git a/apps/drinkcounter/screenshot_drnkcnt.png b/apps/drinkcounter/screenshot_drnkcnt.png new file mode 100644 index 000000000..7547eb63f Binary files /dev/null and b/apps/drinkcounter/screenshot_drnkcnt.png differ diff --git a/apps/drinkcounter/settings.js b/apps/drinkcounter/settings.js new file mode 100644 index 000000000..336229b73 --- /dev/null +++ b/apps/drinkcounter/settings.js @@ -0,0 +1,58 @@ +(function(back) { + var FILE = "drinkcounter.json"; + var settings = Object.assign({ + secondsOnUnlock: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Helper method which uses int-based menu item for set of string values + function stringItems(startvalue, writer, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + writer(values[v]); + writeSettings(); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(settings[name], v => settings[name] = v, values); + } + + var mainmenu = { + "": { + "title": "Drink counter" + }, + "< Back": () => back(), + + "Beer size": stringInSettings("beerSize", ["0.3L", "0.5L"]), + + + "Sex": stringInSettings("sex", ["male", "female"]), + + 'Weight': { + value: 80|settings.weight, + min: 40, max: 500, + onchange: v => { + settings.weight = v; + writeSettings(); + } + }, + "Weight unit": stringInSettings("weightUnit", ["Kilo", "US Pounds"]) + + + }; + + E.showMenu(mainmenu); + +});