commit 29713fdad4cf7610bffa1ad9106afb857a53bacc Author: Alina Marquardt Date: Tue Apr 4 23:18:00 2023 +0200 initial commit diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..2d0269f --- /dev/null +++ b/app/index.js @@ -0,0 +1,298 @@ +import document from "document"; +import clock from "clock"; +import * as messaging from "messaging"; +import * as fs from "fs"; +import { HeartRateSensor } from "heart-rate"; +import { today } from "user-activity"; +import { goals } from "user-activity"; +import { vibration } from "haptics"; +import { display } from "display"; +import { preferences } from "user-settings"; +import { me as device } from "device"; + +if (!device.screen) device.screen = { width: 348, height: 250 }; + +const placeholder = 'ยทยทยท'; + +const colorSchemes = { + blues: ["#343375", "#6795b1"], + aurora: ["#320c72", "#24c2bf"], + roast: ["#ba8867", "#3c2710"], + blush: ["#d09593", "#c71d6f"], + ponyhair: ["#e9438b", "#9acccd"], + lime: ["#dde170", "#06a33e"], + dawn: ["#3b93c5", "#9f59a9"], + peach: ["#fcbd95", "#f795a4"], + violet: ["#9d25eb", "#4e6cc3"], +} + +const weekdays = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat' +]; + +let progress = 0; +let progressXPos = 1; + +let progressEl = document.getElementById('progress'); +let timeEl = document.getElementById('time'); +let dateEl = document.getElementById('date'); +let hrEl = document.getElementById('hr'); +let heartEl = document.getElementById('heartbeat'); +let textElements = document.getElementsByClassName('text'); + +let textOffset = 20; +let hrOffset = 42; + +let hrm = new HeartRateSensor; +let hrTimer; + +var colorDef = [ + colorSchemes['violet'][0], + colorSchemes['violet'][1] +]; + +var storedColorDef; +var fileSuccess = true; +console.log('reading color def'); +try { + storedColorDef = fs.readFileSync("colordef.txt", "cbor"); +} +catch(err) { + fileSuccess = false; + console.log('color def not found'); +} +if (fileSuccess) { + colorDef = storedColorDef; +} + +hrEl.text = placeholder; + +function zeroPad(i) { + if (i < 10) { + i = "0" + i; + } + return i; +} + +function updateClock() { + let currDate = new Date(); + let hours = currDate.getHours(); + let displayHours = hours; + if (preferences.clockDisplay === '12h') { + displayHours = displayHours % 12; + displayHours = displayHours ? displayHours : 12; + } + let minutes = zeroPad(currDate.getMinutes()); + let day = currDate.getDate(); + //let month = currDate.getMonth()+1; + let weekday = weekdays[currDate.getDay()]; + + let timeString = `${displayHours}:${minutes}`; + timeEl.text = timeString; + let dateString = `${weekday} ${day}`; + dateEl.text = dateString; + + updateProgress(); + progressEl.width = progressXPos; + + if (progress <= 0.5) { + for(let i=0; i < textElements.length; i++) { + textElements[i].x = progressXPos + textOffset; + textElements[i].textAnchor = 'start'; + } + heartEl.x = progressXPos + textOffset - 8; + hrEl.x = progressXPos + textOffset + hrOffset; + hrEl.textAnchor = 'start'; + } else { + for(let i=0; i < textElements.length; i++) { + textElements[i].x = progressXPos - textOffset; + textElements[i].textAnchor = 'end'; + } + heartEl.x = progressXPos - textOffset - 38; + hrEl.x = progressXPos - textOffset - hrOffset + 4; + hrEl.textAnchor = 'end'; + } +} + +function updateProgress() { + let steps = (today.adjusted.steps || null); + if (steps !== null) { + let stepGoal = (goals.steps || 0); + progress = Math.min(steps/stepGoal, 1); + progressXPos = Math.max(device.screen.width*progress, 10); + } else { + progress = 1; + progressXPos = 10; + } +} + +function updateColor() { + progressEl.gradient.colors.c1 = colorDef[0]; + progressEl.gradient.colors.c2 = colorDef[1]; +} + +hrm.onreading = () => { + if (display.on) { + let heartRate = (hrm.heartRate || placeholder); + hrEl.text = heartRate; + } + clearTimeout(hrTimer); + hrTimer = setTimeout(() => { + hrEl.text = placeholder; + }, 5000); + hrm.stop(); +} + +clock.granularity = "minutes"; +clock.ontick = () => updateClock(); +updateColor(); + +setInterval(() => { + hrm.start(); +}, 2000); + +messaging.peerSocket.onmessage = e => { + console.log("Message received -> "+e.data.key+": "+e.data.newValue); + display.poke(); + vibration.start("bump"); + switch (e.data.key) { + case 'progresscolor': + colorDef[0] = colorSchemes[e.data.newValue][0]; + colorDef[1] = colorSchemes[e.data.newValue][1]; + break; + }; + updateColor(); + updateClock(); + fs.writeFileSync("colordef.txt", colorDef, "cbor"); +} + +messaging.peerSocket.onopen = () => { + console.log("Clockface opened socket"); +}; + +messaging.peerSocket.close = () => { + console.log("Clockface closed socket"); +}; + +messaging.peerSocket.onerror = (err) => { + console.log("Clockface socket error: " + err.code + " - " + err.message); +} + + +function addcommas(nStr) { + if (nStr < 1000) { return nStr; } + nStr += ''; + let x = nStr.split('.'); + let x1 = x[0]; + let x2 = x.length > 1 ? '.' + x[1] : ''; + let rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; +} + +function getActivityNumber(activity) { + let values = {raw: 0, display: 0}; + + switch (activity) { + case 'steps': + values.raw = (today.adjusted.steps || 0); + values.display = addcommas(values.raw); + break; + case 'cal': + values.raw = (today.adjusted.calories || 0); + values.display = addcommas(values.raw); + break; + case 'dist': + values.raw = (today.adjusted.distance || 0); + values.display = (Math.round(values.raw/10)/100 || 0); + break; + case 'elev': + values.raw = values.display = (today.adjusted.elevationGain || 0); + break; + case 'actmin': + values.raw = (today.adjusted.activeMinutes || 0); + values.display = addcommas(values.raw); + break; + }; + return values; +} + +function getActivityText(activity) { + let label = placeholder; + switch (activity) { + case 'hr': + label = hrLabel; + break; + case 'steps': + case 'cal': + case 'actmin': + case 'dist': + case 'elev': + label = getActivityNumber(activity).display; + break; + default: + break; + }; + return label; +} + + +let touchArea = document.getElementById('touchArea'); +let overlay = document.getElementById('overlay'); +let healthTextInstances = document.getElementsByClassName('healthTextInstance'); +let timeElementsNum = timeElements.length; + +let hasOverlay = false; +let overlayTimer; + +touchArea.onclick = (e) => { + if (hasOverlay == false) { + vibration.start("bump"); + addOverlay(); + } else { + removeOverlay(); + } +} + +function updateOverlay() { + document.getElementById('stepsText').getElementById('healthTextLabel').text = getActivityText('steps'); + document.getElementById('calText').getElementById('healthTextLabel').text = getActivityText('cal'); + document.getElementById('distText').getElementById('healthTextLabel').text = getActivityText('dist'); + document.getElementById('elevText').getElementById('healthTextLabel').text = getActivityText('elev'); + document.getElementById('actminText').getElementById('healthTextLabel').text = getActivityText('actmin'); +} + +function addOverlay() { + updateOverlay(); + overlay.style.display = 'inline'; + hasOverlay = true; + overlayTimer = setTimeout(() => { + removeOverlay(); + },6000); + document.getElementById('overlayShadeInstance').animate('enable'); + for(let i in healthTextInstances) { + setTimeout(() => { + healthTextInstances[i].animate('enable'); + }, 200+i*100); + } +} + +function removeOverlay() { + clearTimeout(overlayTimer); + document.getElementById('overlayShadeInstance').animate('disable'); + for(var i = 0; i < healthTextInstances.length; i++) { + healthTextInstances[i].animate('disable'); + } + setTimeout(() => { + overlay.style.display = 'none'; + }, 100); + hasOverlay = false; +} \ No newline at end of file diff --git a/companion/index.js b/companion/index.js new file mode 100644 index 0000000..dee841d --- /dev/null +++ b/companion/index.js @@ -0,0 +1,36 @@ +import * as messaging from "messaging"; +import { settingsStorage } from "settings"; + +//console.log("Companion Started"); + +messaging.peerSocket.onopen = () => { +}; + +messaging.peerSocket.close = () => { +}; + +settingsStorage.onchange = evt => { + let data = {} + switch(evt.key) { + case 'progresscolor': + data = { + key: evt.key, + newValue: JSON.parse(evt.newValue).values[0].value + }; + break; + default: + data = { + key: evt.key, + newValue: evt.newValue + }; + break; + } + + sendVal(data); +}; + +function sendVal(data) { + if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) { + messaging.peerSocket.send(data); + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6429e92 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "fitbit": { + "appUUID": "8dc25404-83b5-4c54-b526-0358433453ff", + "appType": "clockface", + "appDisplayName": "Progress", + "iconFile": "resources/icon.png", + "wipeColor": "#607d8b", + "requestedPermissions": [ + "access_heart_rate", + "access_activity" + ], + "buildTargets": [ + "higgs", + "meson" + ], + "i18n": { + "en": { + "name": "Progress" + } + } + } +} \ No newline at end of file diff --git a/resources/img/dithernoise.png b/resources/img/dithernoise.png new file mode 100644 index 0000000..bced591 Binary files /dev/null and b/resources/img/dithernoise.png differ diff --git a/resources/img/icon-actmin.png b/resources/img/icon-actmin.png new file mode 100644 index 0000000..19dc3a5 Binary files /dev/null and b/resources/img/icon-actmin.png differ diff --git a/resources/img/icon-cal.png b/resources/img/icon-cal.png new file mode 100644 index 0000000..082d912 Binary files /dev/null and b/resources/img/icon-cal.png differ diff --git a/resources/img/icon-date.png b/resources/img/icon-date.png new file mode 100644 index 0000000..5cb0137 Binary files /dev/null and b/resources/img/icon-date.png differ diff --git a/resources/img/icon-dist.png b/resources/img/icon-dist.png new file mode 100644 index 0000000..26dafd6 Binary files /dev/null and b/resources/img/icon-dist.png differ diff --git a/resources/img/icon-elev.png b/resources/img/icon-elev.png new file mode 100644 index 0000000..ad3bd7e Binary files /dev/null and b/resources/img/icon-elev.png differ diff --git a/resources/img/icon-steps.png b/resources/img/icon-steps.png new file mode 100644 index 0000000..92fe7f3 Binary files /dev/null and b/resources/img/icon-steps.png differ diff --git a/resources/index.gui b/resources/index.gui new file mode 100644 index 0000000..dbf7b60 --- /dev/null +++ b/resources/index.gui @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/styles.css b/resources/styles.css new file mode 100644 index 0000000..bb008b3 --- /dev/null +++ b/resources/styles.css @@ -0,0 +1,35 @@ +.text { + font-family: Colfax-Regular; +} + +#date { + text-length: 8; + font-size: 33; +} + +#hr { + text-length: 6; + font-size: 28; +} + +#time { + font-family: Fabrikat-Regular; + font-size: 60; + text-length: 5; +} + +#overlay { + display: none; +} + +#overlayShadeColor { + fill: black; +} + +.healthTextLabel { + font-size: 28; + font-family: Fabrikat-Regular; + text-length: 8; + text-anchor: end; + fill: white; +} \ No newline at end of file diff --git a/resources/widgets.gui b/resources/widgets.gui new file mode 100644 index 0000000..9ae73ee --- /dev/null +++ b/resources/widgets.gui @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/index.jsx b/settings/index.jsx new file mode 100644 index 0000000..e858221 --- /dev/null +++ b/settings/index.jsx @@ -0,0 +1,34 @@ +function Progress(props) { + return ( + +
Color Settings}> +