initial commit

main
Alina Marquardt 2023-04-04 23:17:35 +02:00
commit 173597e212
16 changed files with 990 additions and 0 deletions

574
app/index.js Normal file
View File

@ -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;
}

79
common/utils.js Normal file
View File

@ -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]);
}

68
companion/index.js Normal file
View File

@ -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);
}
}

26
package.json Normal file
View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

BIN
resources/img/icon-cal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

BIN
resources/img/icon-date.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
resources/img/icon-dist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

BIN
resources/img/icon-elev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

50
resources/index.gui Normal file
View File

@ -0,0 +1,50 @@
<svg viewport-fill="black">
<g transform="translate(100%-20,50%)">
<!--<g transform="rotate(12)">
<text class="time" />
</g>-->
<g transform="rotate(8)">
<text class="time" />
</g>
<g transform="rotate(4)">
<text class="time" />
</g>
<g>
<text class="time" />
</g>
<g transform="rotate(-4)">
<text class="time" id="mainTime" />
</g>
</g>
<use href="#healthText" x="100%-70" y="185" id="mainHealthText">
<set href="healthTextIcon" attributeName="href" to="img/icon-steps.png" />
</use>
<g id="overlay">
<use href="#overlayShade" id="overlayShadeInstance" />
<use href="#healthText" x="35%" y="20%" class="healthTextInstance" id="hrText">
</use>
<use href="#healthText" x="35%" y="50%" class="healthTextInstance" id="stepsText">
<set href="healthTextIcon" attributeName="href" to="img/icon-steps.png" />
</use>
<use href="#healthText" x="35%" y="80%" class="healthTextInstance" id="calText">
<set href="healthTextIcon" attributeName="href" to="img/icon-cal.png" />
</use>
<use href="#healthText" x="80%" y="20%" class="healthTextInstance" id="distText">
<set href="healthTextIcon" attributeName="href" to="img/icon-dist.png" />
</use>
<use href="#healthText" x="80%" y="50%" class="healthTextInstance" id="elevText">
<set href="healthTextIcon" attributeName="href" to="img/icon-elev.png" />
</use>
<use href="#healthText" x="80%" y="80%" class="healthTextInstance" id="actminText">
<set href="healthTextIcon" attributeName="href" to="img/icon-actmin.png" />
</use>
</g>
<rect x="0" y="0" width="100%" height="100%" opacity="0" id="touchArea" pointer-events="visible" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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;
}

0
resources/styles.css Normal file
View File

View File

@ -0,0 +1,3 @@
.time {
font-size: 120;
}

47
resources/widgets.gui Normal file
View File

@ -0,0 +1,47 @@
<svg>
<defs>
<link rel="stylesheet" href="styles-common.css" />
<link rel="stylesheet" href="styles.css" />
<link rel="import" href="/mnt/sysassets/widgets_common.gui" />
<symbol id="healthText">
<g opacity="0">
<g transform="rotate(-4)">
<text x="0" y="14" class="healthTextLabel" id="healthTextLabel" />
</g>
<image x="15" y="-14" width="30" height="30" href="" fill="white" id="healthTextIcon" />
<g transform="scale(1),translate(26, 3)" id="healthTextHrIcon">
<animateTransform attributeType="scale" begin="load" from="1.1" to="0.95" dur="0.9" final="restore" repeatCount="indefinite" easing="ease-out"/>
<svg width="27" height="27">
<circle cx="-24%" cy="-24%" r="26%" fill="white" />
<circle cx="24%" cy="-24%" r="26%" fill="white" />
<g transform="translate(0, 40%), rotate(-135)">
<rect x="0" y="0" width="62%" height="38%" fill="white" />
<rect x="0" y="0" width="38%" height="62%" fill="white" />
</g>
</svg>
</g>
<animate attributeName="opacity" begin="enable" from="0" to="1" dur="0.25" final="freeze" />
<animateTransform attributeType="translate" begin="enable" from="0, -10" to="0, 0" dur="0.25" final="freeze" easing="ease-out" />
<animate attributeName="opacity" begin="disable" from="1" to="0" dur="0.1" final="freeze" />
<animateTransform attributeType="translate" begin="disable" from="0, 0" to="0, 10" dur="0.1" final="freeze" easing="ease-out" />
</g>
</symbol>
<symbol id="overlayShade">
<rect id="overlayShadeColor" width="100%" height="100%" opacity="0.85" />
<animate attributeName="opacity" begin="enable" from="0" to="1" dur="0.25" final="freeze" />
<animate attributeName="opacity" begin="disable" from="1" to="0" dur="0.1" final="freeze" />
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

110
settings/index.jsx Normal file
View File

@ -0,0 +1,110 @@
function Echocentric(props) {
return (
<Page>
<Section
title={<Text bold align="center">Time Settings</Text>}>
<Toggle
settingsKey="leadingZero"
label={`Hour has leading Zero`}
/>
<Select
label={`Colors`}
settingsKey="colors"
options={[
{name:"Custom color", value:"manual"},
{name:"Random colors", value:"random"},
{name:"Based on activity goal", value:"auto"}
]}
/>
{ (props.settings.colors && JSON.parse(props.settings.colors).values[0].value === 'manual' || false) &&
<ColorSelect
settingsKey="customcolor"
centered={true}
colors={[
{color: '#00b0e7', value: ['0.702','1.0','0.4']}, // hue, saturation, sat modifier
{color: '#2084c0', value: ['0.729','1.0','0.5']},
{color: '#2040c0', value: ['0.799','1.0','0.5']},
{color: '#5700ad', value: ['0.883','1.0','0.0']},
{color: '#685098', value: ['0.898','0.5','0.3']},
{color: '#8820c0', value: ['0.933','1.0','0.5']},
{color: '#c020c0', value: ['0.987','1.0','0.5']},
{color: '#806480', value: ['0.011','0.3','0.3']},
{color: '#e800d0', value: ['0.013','1.0','0.0']},
{color: '#a04890', value: ['0.016','0.8','0.7']},
{color: '#985070', value: ['0.085','0.5','0.3']},
{color: '#c02058', value: ['0.108','1.0','0.5']},
{color: '#e80040', value: ['0.113','1.0','0.0']},
{color: '#e83800', value: ['0.203','1.0','0.0']},
{color: '#986850', value: ['0.216','0.5','0.3']},
{color: '#e89c01', value: ['0.275','1.0','0.0']},
{color: '#c8e802', value: ['0.348','1.0','0.0']},
{color: '#788f4b', value: ['0.372','0.5','0.3']},
{color: '#42bb1b', value: ['0.444','1.0','0.5']},
{color: '#00e12d', value: ['0.525','1.0','0.0']},
{color: '#01e890', value: ['0.600','1.0','0.0']},
{color: '#608078', value: ['0.613','0.3','0.3']},
{color: '#48a090', value: ['0.632','0.8','0.7']},
{color: '#508c98', value: ['0.689','0.5','0.3']}
]}
/>
}
{ (props.settings.colors && JSON.parse(props.settings.colors).values[0].value === 'auto' || false) &&
<Select
label={`Activity for goal color`}
settingsKey="colorgoal"
options={[
{name:"Steps", value:"steps"},
{name:"Calories", value:"cal"},
{name:"Distance", value:"dist"},
{name:"Floors", value:"elev"},
{name:"Active Minutes", value:"actmin"},
{name:"Heart Rate", value:"hr"}
]}
/>
}
{ (props.settings.colors && JSON.parse(props.settings.colors).values[0].value === 'auto' || false) &&
<Text>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.</Text>
}
</Section>
<Section
title={<Text bold align="center">Secondary Information</Text>}>
<Select
label={`Second line`}
settingsKey="secondary"
options={[
{name:"nothing", value:"-"},
{name:"Date", value:"date"},
{name:"Steps", value:"steps"},
{name:"Calories", value:"cal"},
{name:"Distance", value:"dist"},
{name:"Floors", value:"elev"},
{name:"Active Minutes", value:"actmin"},
{name:"Heart Rate", value:"hr"}
]}
/>
<Select
label={`Date format`}
settingsKey="dateformat"
options={[
{name:"MM/DD", value:"md/"},
{name:"MM-DD", value:"md-"},
{name:"DD.MM.", value:"dm."},
{name:"DD/MM", value:"dm/"},
{name:"DD-MM", value:"dm-"}
]}
/>
<Select
label={`Tap action`}
settingsKey="tap"
options={[
{name:"nothing", value:"-"},
{name:"Activity overlay", value:"overlay"},
{name:"Changes secondary info", value:"cycle"}
]}
/>
</Section>
</Page>
);
}
registerSettingsPage(Echocentric);