From 173597e212c8609b6bb64302af12315076aefabb Mon Sep 17 00:00:00 2001 From: Alina Marquardt Date: Tue, 4 Apr 2023 23:17:35 +0200 Subject: [PATCH] initial commit --- app/index.js | 574 ++++++++++++++++++++++++++++++++++ common/utils.js | 79 +++++ companion/index.js | 68 ++++ package.json | 26 ++ resources/img/icon-actmin.png | Bin 0 -> 342 bytes resources/img/icon-cal.png | Bin 0 -> 413 bytes resources/img/icon-date.png | Bin 0 -> 574 bytes resources/img/icon-dist.png | Bin 0 -> 367 bytes resources/img/icon-elev.png | Bin 0 -> 258 bytes resources/img/icon-steps.png | Bin 0 -> 446 bytes resources/index.gui | 50 +++ resources/styles-common.css | 33 ++ resources/styles.css | 0 resources/styles~300x300.css | 3 + resources/widgets.gui | 47 +++ settings/index.jsx | 110 +++++++ 16 files changed, 990 insertions(+) create mode 100644 app/index.js create mode 100644 common/utils.js create mode 100644 companion/index.js create mode 100644 package.json create mode 100644 resources/img/icon-actmin.png create mode 100644 resources/img/icon-cal.png create mode 100644 resources/img/icon-date.png create mode 100644 resources/img/icon-dist.png create mode 100644 resources/img/icon-elev.png create mode 100644 resources/img/icon-steps.png create mode 100644 resources/index.gui create mode 100644 resources/styles-common.css create mode 100644 resources/styles.css create mode 100644 resources/styles~300x300.css create mode 100644 resources/widgets.gui create mode 100644 settings/index.jsx diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..7ed2746 --- /dev/null +++ b/app/index.js @@ -0,0 +1,574 @@ +import document from "document"; +import clock from "clock"; +import * as messaging from "messaging"; +import * as fs from "fs"; +import { me } from "appbit"; +import { me as device } from "device"; +import { Accelerometer } from "accelerometer"; +import { HeartRateSensor } from "heart-rate"; +import { today } from "user-activity"; +import { goals } from "user-activity"; +import { user } from "user-profile"; +import { vibration } from "haptics"; +import { display } from "display"; +import { preferences } from "user-settings"; + +if (!device.screen) device.screen = { width: 348, height: 250 }; + +import * as util from "../common/utils"; + +const DEBUG = false; +const COLORDEBUG = false; + +let accel = new Accelerometer({ frequency: 4 }); +let hrm = new HeartRateSensor; + +let touchArea = document.getElementById('touchArea'); +let overlay = document.getElementById('overlay'); +let mainHealthText = document.getElementById('mainHealthText'); +let timeElements = document.getElementsByClassName('time'); +let healthTextInstances = document.getElementsByClassName('healthTextInstance'); +let timeElementsNum = timeElements.length; + +let averageAccelZ = 10.0; +let lastAccelZ = 10.0; +let hasOverlay = false; +let overlayTimer; +let sublineTimer; +let colorTimer; +let saveTimer; +let hrTimer; + +let currentColor; + +let hiddenSubline = false; + +const smoothingFactor = 0.2; + +const colorGreen = [0.525,1.0,0.0]; +const colorYellow = [0.3,1.0,0.0]; +const colorOrange = [0.213,1.0,0.0]; +const colorRed = [0.108,1.0,0.5]; +const colorNeutral = [0.632,0.8,0.7]; + +const placeholdertext = '· · · '; +let hrLabel = placeholdertext; + +const sublines = ['-', 'date', 'steps', 'cal', 'dist', 'elev', 'actmin', 'hr']; + +const defaults = {leadingZero: false, secondary: 'steps', tap: 'overlay', colors: 'manual', customcolor:['0.632','0.8','0.7'], dateformat: 'md/', colorgoal: 'steps'}; + +let userSettings; + +try { + //console.log("Reading settings"); + userSettings = fs.readFileSync("echocentric_settings.json", "json"); +} catch (e) { + //console.log("No settings found. Using default settings"); + userSettings = defaults; +} + +function writeSettings() { + //console.log("Writing settings"); + fs.writeFileSync("echocentric_settings.json", userSettings, "json"); +} + +me.onunload = () => { + //console.log("Unloading. Good Bye"); + writeSettings(); +} + +messaging.peerSocket.onmessage = e => { + //console.log("Message received -> "+e.data.key+": "+e.data.newValue); + display.poke(); + vibration.start("bump"); + let doClockUpdate = false; + let doSublineChange = false; + let doSublineUpdate = false; + let doColorUpdate = false; + switch (e.data.key) { + case 'leadingZero': + userSettings.leadingZero = (e.data.newValue == "true"); + doClockUpdate = true; + break; + case 'secondary': + let oldval = userSettings.secondary; + let newval = JSON.parse(e.data.newValue).values[0].value; + if (oldval != newval) { + userSettings.secondary = newval; + doSublineChange = true; + } + break; + case 'tap': + userSettings.tap = JSON.parse(e.data.newValue).values[0].value; + break; + case 'colors': + userSettings.colors = JSON.parse(e.data.newValue).values[0].value; + doColorUpdate = true; + break; + case 'customcolor': + userSettings.customcolor = e.data.newValue.replace(/["']/g, "").split(','); + doColorUpdate = true; + break; + case 'colorgoal': + userSettings.colorgoal = JSON.parse(e.data.newValue).values[0].value; + doColorUpdate = true; + if (userSettings.colorgoal === 'hr') { + doClockUpdate = true; + } + break; + case 'dateformat': + userSettings.dateformat = JSON.parse(e.data.newValue).values[0].value; + doSublineUpdate = true; + break; + }; + + clearTimeout(saveTimer); + saveTimer = setTimeout(() => { + writeSettings(); + }, 8000); + + if (doColorUpdate) { updateColors(false); } + if (doClockUpdate) { updateClock(); } + if (doSublineChange) { changeSubline(); } + if (doSublineUpdate) { updateSubline(); } +} + +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 sendSetting() { + if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) { + messaging.peerSocket.send(userSettings); + } +} + +display.onchange = () => { + if (display.on) { + accel.start(); + averageAccelZ = lastAccelZ = Math.max(accel.z,5); + callback(); + if (hiddenSubline === false) { + mainHealthText.animate('enable'); + } + } else { + accel.stop(); + if (hiddenSubline === false) { + mainHealthText.animate('disable'); + } + } +} + +function updateClock() { + let currDate = new Date(); + let hours = currDate.getHours(); + let displayHours = hours; + if (preferences.clockDisplay === '12h') { + displayHours = displayHours % 12; + displayHours = displayHours ? displayHours : 12; + } + if (userSettings.leadingZero === true) { + displayHours = util.zeroPad(displayHours); + } + let mins = util.zeroPad(currDate.getMinutes()); + let timeString = `${displayHours}:${mins}`; + for(var i = 0; i < timeElementsNum; i++) { + timeElements[i].text = timeString; + } + if (userSettings.colors === 'random') { + updateColors(false); + } + updateSubline(); + switch (userSettings.secondary) { + case 'steps': + case 'cal': + case 'dist': + case 'elev': + case 'actmin': + case 'hr': + clearInterval(sublineTimer); + sublineTimer = setInterval(() => { + updateSubline(); + },2000); + } + if (userSettings.colorgoal === 'hr') { + clearInterval(colorTimer); + colorTimer = setInterval(() => { + updateColors(false); + },2000); + } + if (userSettings.colors === 'auto') { + updateColors(false); + } +} + +function updateSubline() { + if (userSettings.secondary === 'hr') { + mainHealthText.getElementById('healthTextLabel').text = hrLabel; + hrm.start(); + } else { + let sublineText = getActivityText(userSettings.secondary); + if (mainHealthText.getElementById('healthTextLabel').text !== sublineText) + mainHealthText.getElementById('healthTextLabel').text = sublineText; + } + if (display.on === false) { + clearInterval(sublineTimer); + } +} + +function getActivityNumber(activity) { + let values = {raw: 0, display: 0}; + + switch (activity) { + case 'steps': + values.raw = (today.adjusted.steps || 0); + values.display = util.addCommas(values.raw); + break; + case 'cal': + values.raw = (today.adjusted.calories || 0); + values.display = util.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 = util.addCommas(values.raw); + break; + }; + return values; +} + +function getActivityText(activity) { + let label = placeholdertext; + switch (activity) { + case 'hr': + label = hrLabel; + break; + case 'steps': + case 'cal': + case 'actmin': + case 'dist': + case 'elev': + label = getActivityNumber(activity).display; + break; + case 'date': + let currDate = new Date(); + let day = currDate.getDate(); + let month = currDate.getMonth()+1; + + let zeroPadded = false; + let ddmm = false; + let delimiter = '/'; + let endsWithDelimiter = false; + + if (userSettings.dateformat[0] === 'd') { + ddmm = true; + } + delimiter = userSettings.dateformat[2]; + endsWithDelimiter = ((delimiter === '.') ? true : false); + + if (zeroPadded) { + day = util.zeroPad(day); + month = util.zeroPad(month); + } + if (ddmm) { + label = day+delimiter+month+(endsWithDelimiter ? delimiter : ''); + } else { + label = month+delimiter+day+(endsWithDelimiter ? delimiter : ''); + } + break; + default: + break; + }; + return label; +} + +function activityGoalColor(activity, heartRate) { + let level = colorNeutral; + if (heartRate !== false) { + let hrZone = (user.heartRateZone(heartRate) || 'none'); + switch (hrZone) { + case 'out-of-range': + case 'below-custom': + level = colorGreen; + break; + case 'fat-burn': + case 'custom': + level = colorYellow; + break; + case 'cardio': + level = colorOrange; + break; + case 'above-custom': + case 'peak': + level = colorRed; + break; + } + return level; + } else { + if (activity === 'hr') { + hrm.start(); + } else { + let goal = 0; + switch (activity) { + case 'steps': + goal = (goals.steps || 0); + break; + case 'cal': + goal = (goals.calories || 0); + break; + case 'dist': + goal = (goals.distance || 0); + break; + case 'elev': + goal = (Math.floor(goals.elevationGain/10) || 0); + break; + case 'actmin': + goal = (goals.activeMinutes || 0); + break; + }; + let current = getActivityNumber(activity).raw; + //console.log('Goal for '+activity+' is '+current+' of '+goal+' = '+(current/goal)); + if (goal > 0) { + let goalPercentage = current/goal; + if (goalPercentage > 1) { + level = colorGreen; + } else if (goalPercentage > 0.666) { + level = util.interpolateColors(goalPercentage, 0.666, 1, colorYellow, colorGreen); + } else if (goalPercentage > 0.333) { + level = util.interpolateColors(goalPercentage, 0.333, 0.666, colorOrange, colorYellow); + } else { + level = util.interpolateColors(goalPercentage, 0, 0.333, colorRed, colorOrange); + } + } + } + } + return level; +} + +function changeSubline() { + if (hiddenSubline === false) { + mainHealthText.animate('disable'); + } + let href = ''; + switch (userSettings.secondary) { + case 'date': + case 'steps': + case 'cal': + case 'dist': + case 'elev': + case 'actmin': + href = 'img/icon-'+userSettings.secondary+'.png'; + break; + default: + break; + }; + setTimeout(() => { + updateSubline(); + if (userSettings.secondary === 'hr') { hrm.start(); } + mainHealthText.getElementById('healthTextIcon').href = href; + if (userSettings.secondary === 'hr') { + mainHealthText.getElementById("healthTextHrIcon").style.display="inline"; + } else { + mainHealthText.getElementById("healthTextHrIcon").style.display="none"; + } + if (userSettings.secondary !== '-') { + mainHealthText.animate('enable'); + hiddenSubline = false; + } else { + hiddenSubline = true; + } + }, 200); +} + +function updateColors(color) { + let newColor = false; + if (color !== false) { + newColor = color; + } else { + switch (userSettings.colors) { + case 'auto': + let activityColor = activityGoalColor(userSettings.colorgoal, false); + if (userSettings.colorgoal !== 'hr') { + newColor = activityColor; + } + break; + case 'manual': + newColor = userSettings.customcolor; + break; + case 'random': + default: + newColor = [Math.random(),1,0.5]; + break; + } + } + if (COLORDEBUG) { + newColor = [Math.random(),1,0]; + } + //console.log('Color comparison '+newColor+' == '+currentColor+': '+(newColor == currentColor)); + if (newColor !== false && newColor != currentColor) { + //console.log('Color was changed'); + currentColor = newColor; + for(var i = 0; i < timeElementsNum-1; i++) { + let fraction = i/timeElementsNum; + timeElements[i].style.fill = util.hslToHex(newColor[0]-fraction*0.3, newColor[1]-fraction*newColor[2], fraction*0.74+0.15); + } + } else { + //console.log('Color is unchanged'); + } + if (display.on === false) { + clearInterval(colorTimer); + } +} + +function setPositions(offset) { + for(var i = 0; i < timeElementsNum; i++) { + let ypos = ((1-(offset-9))*5*(timeElementsNum-0.5-i))+20; + timeElements[i].y = ypos; + if (i == timeElementsNum-1) { + mainHealthText.y = ypos+160+((device.screen.height-250)/2); + } + } +} + +hrm.onreading = () => { + //console.log("Reading heart rate"); + if (display.on) { + let heartRate = (hrm.heartRate || 0); + hrLabel = heartRate; + document.getElementById('hrText').getElementById('healthTextLabel').text = heartRate; + if (userSettings.secondary == 'hr') { + mainHealthText.getElementById('healthTextLabel').text = heartRate; + } + if (userSettings.colorgoal === 'hr') { + let color = activityGoalColor('hr', heartRate); + //console.log('got HR color from hr '+heartRate+': '+color); + updateColors(color); + } + } + clearTimeout(hrTimer); + hrTimer = setTimeout(() => { + hrLabel = placeholdertext; + }, 1500); + hrm.stop(); +} + +function updateOverlay() { + document.getElementById('hrText').getElementById('healthTextLabel').text = hrLabel; + hrm.start(); + 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'); +} + +touchArea.onclick = (e) => { + if (COLORDEBUG) { + updateColors(false); + } else { + switch (userSettings.tap) { + case 'overlay': + if (hasOverlay == false) { + vibration.start("bump"); + addOverlay(); + } else { + removeOverlay(); + } + break; + case 'cycle': + vibration.start("bump"); + let next = sublines[0]; + let index = sublines.indexOf(userSettings.secondary); + if(index >= 0 && index < sublines.length - 1) { + next = sublines[index + 1]; + } + userSettings.secondary = next; + changeSubline(); + sendSetting(); + clearTimeout(saveTimer); + saveTimer = setTimeout(() => { + writeSettings(); + }, 8000); + break; + } + } +} + +function addOverlay() { + updateOverlay(); + overlay.style.display = 'inline'; + hasOverlay = true; + overlayTimer = setTimeout(() => { + removeOverlay(); + },6000); + document.getElementById('overlayShadeInstance').animate('enable'); + for(var i = 0; i < healthTextInstances.length; i++) { + let index = i; + setTimeout(() => { + healthTextInstances[index].animate('enable'); + }, 200+index*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; +} + +accel.onreading = () => { + //console.log(accel.z); + if (hasOverlay == false) { + lastAccelZ = Math.max(accel.z,5); + } + //accel.stop(); +} + +function callback(timestamp) { + if (hasOverlay == false) { + averageAccelZ = (lastAccelZ*smoothingFactor)+(averageAccelZ*(1.0-smoothingFactor)); + setPositions(averageAccelZ); + requestAnimationFrame(callback); + } +} + +updateColors(false); + +changeSubline(); + +clock.granularity = "minutes"; +clock.ontick = () => updateClock(); + +document.getElementById("hrText").getElementById("healthTextHrIcon").style.display="inline"; + +updateClock(); +updateOverlay(); + +requestAnimationFrame(callback); +accel.start(); + +if (DEBUG) { + display.autoOff = false; + display.on = true; +} \ No newline at end of file diff --git a/common/utils.js b/common/utils.js new file mode 100644 index 0000000..95d26bf --- /dev/null +++ b/common/utils.js @@ -0,0 +1,79 @@ +function map(value, sourceBottom, sourceTop, targetBottom, targetTop) { + let valueRatio = (value-sourceBottom)/(sourceTop-sourceBottom); + return ((targetTop-targetBottom)*valueRatio)+targetBottom; +} + +export function interpolateColors(value, sourceBottom, sourceTop, color1, color2) { + let newColor = [ + map(value, sourceBottom, sourceTop, color1[0], color2[0]), + map(value, sourceBottom, sourceTop, color1[1], color2[1]), + map(value, sourceBottom, sourceTop, color1[2], color2[2]) + ]; + return newColor; +} + +export 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; +} + +// Add zero in front of numbers < 10 +export function zeroPad(i) { + if (i < 10) { + i = "0" + i; + } + return i; +} + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ +function hslToRgb(h, s, l) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + var hue2rgb = function hue2rgb (p, q, t) { + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; +} + +function rgbToHex(r, g, b) { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); +} + +export function hslToHex(h, s, l) { + let rgb = hslToRgb(h, s, l); + return rgbToHex(rgb[0],rgb[1],rgb[2]); +} \ No newline at end of file diff --git a/companion/index.js b/companion/index.js new file mode 100644 index 0000000..66fc3ad --- /dev/null +++ b/companion/index.js @@ -0,0 +1,68 @@ +import * as messaging from "messaging"; +import { settingsStorage } from "settings"; + +//console.log("Companion Started"); + +//settingsStorage.setItem('colors', Json.stringify({})); +//settingsStorage.setItem('secondary', 'cal'); + +// Message socket opens +messaging.peerSocket.onopen = () => { + //console.log("Companion opened socket"); + //restoreSettings(); +}; + +// Message socket closes +messaging.peerSocket.close = () => { + //console.log("Companion closed Socket"); +}; + +/* +messaging.peerSocket.onmessage = (evt) => { + // Output the message to the console + console.log(JSON.stringify(evt.data)); + Object.keys(evt.data).forEach((k) => { + console.log(k + ' - ' + evt.data[k]); + //settingsStorage.setItem(k, evt.data[k]); + }); + //settingsStorage.setItem('secondary', 'cal'); +} +*/ + + +// A user changes settings +settingsStorage.onchange = evt => { + let data = { + key: evt.key, + newValue: evt.newValue + }; + switch (evt.key) { + case 'button': + // perform magic here + break; + default: + sendVal(data); + break; + } +}; + +// Restore any previously saved settings and send to the device +function restoreSettings() { + for (let index = 0; index < settingsStorage.length; index++) { + let key = settingsStorage.key(index); + if (key) { + let data = { + key: key, + newValue: settingsStorage.getItem(key) + }; + sendVal(data); + } + } +} + +// Send data to device using Messaging API +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..662fd11 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "fitbit": { + "appUUID": "753385ec-70bd-4560-baf0-5538105d67e9", + "appType": "clockface", + "appDisplayName": "Echocentric", + "iconFile": "resources/icon.png", + "wipeColor": "#009688", + "requestedPermissions": [ + "access_activity", + "access_heart_rate", + "access_user_profile" + ], + "buildTargets": [ + "higgs", + "meson" + ], + "i18n": { + "en": { + "name": "Echocentric" + } + } + }, + "devDependencies": { + "@fitbit/sdk": "~3.0.0" + } +} \ No newline at end of file diff --git a/resources/img/icon-actmin.png b/resources/img/icon-actmin.png new file mode 100644 index 0000000000000000000000000000000000000000..19dc3a5f596b624d956f32258aa0f29dbc94ca6d GIT binary patch literal 342 zcmV-c0jd6pP)s(yvQHVHC#kXG0OjCN~Hc!EM)I5S)L2FfiB*VzQb< ztbCSn)?J9?!PJOxhlJodFR$lRRHKtdd22EF61&^!I{yQ35F-uA1 zO0!<8*LF4}>9N#NVlDxC^T{aP1Dsl_58!Rg(g0x1EYAYBDU+9=Ju|TFpW>u+@L-m; o$8*9Z_1hh7w(PP-%m1L2A9(7!JJsYcKL7v#07*qoM6N<$f;YgFzW@LL literal 0 HcmV?d00001 diff --git a/resources/img/icon-cal.png b/resources/img/icon-cal.png new file mode 100644 index 0000000000000000000000000000000000000000..082d9120dbc5958a15eaa55170c78b32725fa284 GIT binary patch literal 413 zcmV;O0b>4%P)!eZ#q5QBd} zbhDWZ244fI#6}RL9lBUVEKTSlgw;?fBEC|I(0UB-`{v$iyLhI1?sI=9_Z-QlV^O8l zSm)64O{c0d{tENe?GLb7%|?m<=_FPpP#ed;(B^*od4LtK90cew>HNMVwR(C&()oq8w(d9JA?OrspTLA{ z`ksMxXOdH3R;BdjfMH3RgXd}eB@o?CZ%Fc8$HR4H_P_iB00000NkvXX Hu0mjfcPzZZ literal 0 HcmV?d00001 diff --git a/resources/img/icon-date.png b/resources/img/icon-date.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb0137799e4beb1fb84ff901843fa03d95f1fc7 GIT binary patch literal 574 zcmV-E0>S->P)e+EH8g0707t5ZQ} zHxXP!#7QVF5?n+(X$KJzCub+c!9`JuNb!eQtu;4^ZA^l-H{ZQ!n$O{06OF;mcRHNs ze1~)1@4!Db)SpP-^uFm!oqRIejql2WuMaG;L)V&k`8Xquk;$Y#^df~1U|J1pllLIKyE5^yS)?- z=8v8Z>|VZZ;)~k2j4R8jTO%A_o`<3D2{=TPK&%AdE<%Qx5r~?A*8)6L2`22(Ds5$q z^^P0mc`a<{3vo-Fr;ICWb$HS0VqPo!6K~EHWS)~vr8HOm)s6O zrLClG+-Tt^JGY04neil6q5@EBj>K(KTP7+$cEFDX-eCah0^2qP6P2y6L9y}i;eK=F z0{{_E*QaOlt%uNbLg`tTek|oR=e5rC;-yYYJE&#SVZW-Xsu~*Q(^aSRSXD}0uNr?` z!}^@_1#sm|@>f^WFk7Y&qOe%#Vh&%7>!73ZJE4tw^;gTy#Hbs=-cG z@&4kNh^4VSwYcNWI$z;8)upiZzS5A7{t%x4P`+=FoYOQU*1CQ4Av!vvPY1evW z20=Cf{EQ=M)dScH%#l)0lB5T>IyodsN*Aqj7b^wXnX`Ez#5!%%Tv#<&4zx1b#M%jX zzggAwQrEY>2-bvW)Sr%0Y1!~fv`X6m**I0=400{8toF;R?s)x&`~YTE;I-?!r1<~< N002ovPDHLkV1m(%ricIl literal 0 HcmV?d00001 diff --git a/resources/img/icon-elev.png b/resources/img/icon-elev.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3bd7e9c42a4dbbac232e2be0f23804693060b4 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^av;nBBpBqPKh^;$&H|6fVg?3oVGw3ym^DWND7eMb z#W6%1wAjbdytpzg-mdu=Z)NrZd19`p7IyN9^lNR9P-qf>6 zhk5Z-MrI&5oan=B`|t2U=|Z2wx)0XO57hr?@izT{9^0Ku_RM7om$xt^z31m|5xDYH zPK<|#>*9_tTFQ5N)`+vod^n}FWaB9#i$_yaR~%D4Eiw1VnFIL^r_b`t|G1lLzYcR< xWZV7=5+#SVP8&RY|Nnpe`f~!u^}K@_7;fz9K7U|B^E8m>JYD@<);T3K0RR_SW_klgleaQ5?rVGvgf#&slktLKJEi%)(=Vjch#1qby}7 zg^ji1k5H7-tRz_}E3#2C%40J^9(fxz3#G5cJ?Gfm``euR`F-y1ch2v0Zv_9yAL%W; z4sZ~^leCi^U|YT=NsInAD=A6ROO~&mmWi0JzO=gEm6tWYpUUi9Z<8Uj)^7bu2jw0t z*kZMMiLSIHY0sl=Yts<`Vj06#0Nh!<2|(_J<=lhMS#R`|o1+wfq76yXxc63#>pch1G*LodN?J7?$E;1} z0L8C*9H3xw+}`%Gu|F*Uqmr~7g zeUP*iwlV~iwZ*G;6DHBvUzj*|wV_a~GMN%V(Jtw0-~XcmrX)#60XBROz*SIn-jk$E ob>QX$Z+dPIv=D9b{}}-O07cdfm;Q;#ssI2007*qoM6N<$g7ccaRR910 literal 0 HcmV?d00001 diff --git a/resources/index.gui b/resources/index.gui new file mode 100644 index 0000000..0f3306c --- /dev/null +++ b/resources/index.gui @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/styles-common.css b/resources/styles-common.css new file mode 100644 index 0000000..5c10205 --- /dev/null +++ b/resources/styles-common.css @@ -0,0 +1,33 @@ +.time { + font-size: 140; + font-family: Fabrikat-Bold; + text-length: 5; + text-anchor: end; + x: 0; +} + +#mainTime +{ + fill: white; +} + +#overlay { + display: none; +} + +#overlayShadeColor { + fill: #009688; + fill: black; +} + +.healthTextLabel { + font-size: 35; + font-family: Seville-Bold-Condensed; + text-length: 8; + text-anchor: end; + fill: white; +} + +#healthTextHrIcon { + display: none; +} \ No newline at end of file diff --git a/resources/styles.css b/resources/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/resources/styles~300x300.css b/resources/styles~300x300.css new file mode 100644 index 0000000..f04a97c --- /dev/null +++ b/resources/styles~300x300.css @@ -0,0 +1,3 @@ +.time { + font-size: 120; +} \ No newline at end of file diff --git a/resources/widgets.gui b/resources/widgets.gui new file mode 100644 index 0000000..ce1ae63 --- /dev/null +++ b/resources/widgets.gui @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/index.jsx b/settings/index.jsx new file mode 100644 index 0000000..d24651f --- /dev/null +++ b/settings/index.jsx @@ -0,0 +1,110 @@ +function Echocentric(props) { + return ( + +
Time Settings}> + + + } + { (props.settings.colors && JSON.parse(props.settings.colors).values[0].value === 'auto' || false) && + You will see different colors depending on your activity. Red means you still have a long way to your goal. On your way there you will see orange and yellow, until finally when you've reached your goal you will see green. With heart rate the colors will work the other way around. Green means you are below your Fat Burn zone, your Fat Burn zone is yellow, Cardio is orange and Peak is red. + } +
+
Secondary Information}> + +