Skip to content

Instantly share code, notes, and snippets.

@DanielStefanK
Last active November 3, 2023 18:57
Show Gist options
  • Save DanielStefanK/487175b6f65ede401e37ee4848970176 to your computer and use it in GitHub Desktop.
Save DanielStefanK/487175b6f65ede401e37ee4848970176 to your computer and use it in GitHub Desktop.
//Can be obtained here https://fitx-proxy.daniel-stefan.dev/
let studioId = 1266927;
// Proxy for the utilization api
let proxyUrl = "https://fitx-proxy.daniel-stefan.dev/api/utilization/"
let param = args.widgetParameter;
if (param != null && param.length > 0) {
studioId = param;
}
const contextSize = 282;
const fitXOrange = new Color("#ff8c00");
const lightGrey = new Color("#bfbbbb");
const widget = new ListWidget();
let studioInfo;
try {
studioInfo = await fetchStoreInformation();
await createWidget();
} catch (e) {
console.log(e);
widget.addSpacer(4);
const logoImg = await getImage("logo.png");
widget.setPadding(10, 10, 10, 10);
const titleFontSize = 14;
const detailFontSize = 14;
const logoStack = widget.addStack();
logoStack.addSpacer(4);
const logoImageStack = logoStack.addStack();
logoStack.layoutHorizontally();
logoImageStack.backgroundColor = new Color("#ffffff", 1.0);
logoImageStack.cornerRadius = 8;
const wimg = logoImageStack.addImage(logoImg);
wimg.imageSize = new Size(40, 40);
wimg.rightAlignImage();
widget.addSpacer();
const row = widget.addStack();
row.layoutVertically();
const percentTitle = row.addText("Fehler beim Laden");
}
// used for debugging if script runs inside the app
if (!config.runsInWidget) {
await widget.presentSmall();
}
Script.setWidget(widget);
Script.complete();
// build the content of the widget
async function createWidget() {
var d = new Date();
const currentWeekDay = d.getDay();
const currentHour = Math.abs(d.getHours() - 2) % 24;
widget.addSpacer(4);
const logoImg = await getImage("logo.png");
widget.setPadding(10, 10, 10, 10);
const titleFontSize = 14;
const detailFontSize = 14;
const logoStack = widget.addStack();
const name = logoStack.addText(studioInfo.name);
name.font = Font.regularSystemFont(titleFontSize);
name.minimumScaleFactor = 0.5;
logoStack.addSpacer(4);
const logoImageStack = logoStack.addStack();
logoStack.layoutHorizontally();
logoImageStack.backgroundColor = new Color("#ffffff", 1.0);
logoImageStack.cornerRadius = 8;
const wimg = logoImageStack.addImage(logoImg);
wimg.imageSize = new Size(40, 40);
wimg.rightAlignImage();
widget.addSpacer();
const row = widget.addStack();
row.layoutVertically();
const percentTitle = row.addText("Aktuelle Auslastung: ");
percentTitle.font = Font.regularSystemFont(detailFontSize);
const percentage = row.addText(studioInfo.workload + "%");
percentage.font = Font.regularSystemFont(detailFontSize);
let drawContext = new DrawContext();
drawContext.size = new Size(contextSize, contextSize);
drawContext.opaque = false;
drawContext.setTextAlignedCenter();
const data = studioInfo.items.map((i) => i.percentage);
let min, max, diff;
for (let i = 0; i <= 24; i++) {
let temp = data[i];
min = temp < min || min == undefined ? temp : min;
max = temp > max || max == undefined ? temp : max;
}
diff = max - min;
let spaceBetweenDays = 10;
for (let i = 0; i < 24; i++) {
let current = data[i];
let nextHour = data[i + 1];
let delta = diff > 0 ? (current - min) / diff : 0.5;
let nextDelta = diff > 0 ? (nextHour - min) / diff : 0.5;
drawPoint(
spaceBetweenDays * i + 25,
175 - 50 * delta,
drawContext,
fitXOrange,
currentHour === i - 2 ? 15 : 3
);
if (i % 3 == 0) {
drawText(
data[i],
10,
spaceBetweenDays * i + (data[i] > 9 ? 16 : 22),
175 - 50 * delta - 25,
drawContext,
lightGrey
);
drawText(
i,
10,
spaceBetweenDays * i + (data[i] > 9 ? 16 : 22),
180,
drawContext,
lightGrey
);
}
drawLine(
spaceBetweenDays * i + 25,
175 - 50 * delta,
spaceBetweenDays * (i + 1) + 25,
175 - 50 * nextDelta,
drawContext,
4,
fitXOrange
);
}
widget.backgroundImage = drawContext.getImage();
}
// fetches information of the configured store, e.g. opening hours, address etc.
async function fetchStoreInformation() {
let url = proxyUrl + studioId;
let req = new Request(url);
let apiResult = await req.loadString();
if (req.response.statusCode == 404) {
// TODO: implement error handling
} else if (req.response.statusCode == 200) {
apiResult = JSON.parse(apiResult);
widget.url = "https://mein.fitx.de/studio/" + apiResult.uuid;
}
return apiResult;
}
// get images from local filestore or download them once
async function getImage(image) {
let fm = FileManager.local();
let dir = fm.documentsDirectory();
let path = fm.joinPath(dir, image);
if (fm.fileExists(path)) {
return fm.readImage(path);
} else {
// download once
let imageUrl;
switch (image) {
case "logo.png":
imageUrl = "https://i.imgur.com/2T54ySh.png";
break;
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let iconImage = await loadImage(imageUrl);
fm.writeImage(path, iconImage);
return iconImage;
}
}
// helper function to download an image from a given url
async function loadImage(imgUrl) {
const req = new Request(imgUrl);
return await req.loadImage();
}
function drawLine(x1, y1, x2, y2, ctx, width, color) {
const path = new Path();
path.move(new Point(x1, y1));
path.addLine(new Point(x2, y2));
ctx.addPath(path);
ctx.setStrokeColor(color);
ctx.setLineWidth(width);
ctx.strokePath();
}
function drawPoint(x, y, ctx, color, width = 2) {
const rec = new Rect(x - width / 2, y - width / 2, width, width);
ctx.setStrokeColor(color);
ctx.setFillColor(color);
ctx.setLineWidth(2);
ctx.strokeEllipse(rec);
ctx.fillEllipse(rec);
}
function drawText(text, fontSize, x, y, ctx, color = Color.black()) {
ctx.setFont(Font.boldSystemFont(fontSize));
ctx.setTextColor(color);
ctx.drawText(new String(text).toString(), new Point(x, y));
}
@DanielStefanK
Copy link
Author

For now I've build a simple proxy to retrieve the utilisation data hosted at https://fitx-proxy.daniel-stefan.dev/. This also serves a simple webpage to obtain the new studio ID for the script.

The proxy implementation can be found here.
I think the data can also be retrieved from the fitx API in the script itself, because there are public endpoints for the both the studios (https://mein.fitx.de/sponsorship/v1/public/studios/forwhitelabelportal) and utilisation (https://mein.fitx.de/nox/public/v1/studios/{magiclineid}/utilization), but I've build this proxy with the thought that it could only be obtained with an authenticated user.
At this point i am not sure if the magicline id changes over time for each studio.
Also as far as I can see the API does not predict the future utilisation so the graph is flat from the current time.
I will keep an eye on the API and will update the script/shut down the proxy when there is a better way to get the data.
For now this works for the current/past utilisation and is sufficient for me.

@krmkrx
Copy link

krmkrx commented Nov 23, 2022

This works flawlessly so far, many thanks! Wouldn’t be possible to provide for an implementation to be used in connection with this simplified widget style created by someone else?

https://gist.github.com/masselmello/6d4f4c533b98b2550ee23a7a5e6c6cff?permalink_comment_id=3500390#gistcomment-3500390

The respective (now non-working) FitX fork can be found here: https://gist.github.com/eopo/aedaf03f1f27a0c9c02f4974f3f4c9ba

It would be great to somehow have a simplified widget without past data, as shown in the example above. 🙂

@DanielStefanK
Copy link
Author

My proxy should work with the other widget. The current workload is found on the response.
I've updated the gist with the current api here: https://gist.github.com/DanielStefanK/adf0fc40d08585e4167f20463fe3b1d1

@krmkrx
Copy link

krmkrx commented Nov 23, 2022

Thank you very much for this, I appreciate this as I am not super knowledgeable with regard to GitHub and Scriptable in general.

@brusayannick
Copy link

Seeehr geiles script! Habe ich zwar nicht nach gesucht aber installiert. Danke dir <3

@9dc
Copy link

9dc commented May 8, 2023

finds geil. location service an sich wäre noch nice. ist mit scriptable ja problemlos möglich. Also immer das FitX anzeigen, was in der nähe ist. es müssten nur die locations in der proxy gespeichert werden..

@HeyMeco
Copy link

HeyMeco commented Nov 3, 2023

Habe aktuell das Problem das der Graph nicht mehr gerendert wird. Nur ich?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment