Skip to content

Instantly share code, notes, and snippets.

@v4lli
Last active September 7, 2021 22:12
Show Gist options
  • Save v4lli/b123fd0c766ad6a8f92a0df374c965bc to your computer and use it in GitHub Desktop.
Save v4lli/b123fd0c766ad6a8f92a0df374c965bc to your computer and use it in GitHub Desktop.
Scriptable.app Fit Star Capacity Status Widget Fork
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: yellow; icon-glyph: star;
// copied & adapted from: https://gist.github.com/kevinkub/46caebfebc7e26be63403a7f0587f664
class LineChart {
constructor(width, height, values, ymin, ymax, len, padding = 0) {
this.ctx = new DrawContext()
this.ctx.size = new Size(width, height)
this.values = values;
this.ymin = ymin;
this.ymax = ymax;
this.padding = padding;
this.len = len || this.values.length;
}
_calculatePath() {
let difference = this.ymax - this.ymin;
let step = (this.ctx.size.width - this.padding) / (this.len - 1);
let points = this.values.map((current, index, all) => {
let x = step*index
let y = (this.ctx.size.height - this.padding) - (current - this.ymin) / difference * (this.ctx.size.height - this.padding);
return new Point(x, y)
});
return this._getSmoothPath(points);
}
_getSmoothPath(points) {
let path = new Path()
path.move(new Point(0, this.ctx.size.height-this.padding));
path.addLine(points[0]);
for(var i = 0; i < points.length-1; i ++) {
let xAvg = (points[i].x + points[i+1].x) / 2;
let yAvg = (points[i].y + points[i+1].y) / 2;
let avg = new Point(xAvg, yAvg);
let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y);
let next = new Point(points[i+1].x, points[i+1].y);
let cp2 = new Point((xAvg + points[i+1].x) / 2, points[i+1].y);
path.addQuadCurve(avg, cp1);
path.addQuadCurve(next, cp2);
}
if (points.length === this.len) {
path.addLine(new Point(this.ctx.size.width - this.padding, this.ctx.size.height - this.padding))
path.closeSubpath();
}
return path;
}
configure(fn) {
let path = this._calculatePath()
if(fn) {
fn(this.ctx, path);
} else {
this.ctx.addPath(path);
this.ctx.fillPath(path);
}
return this.ctx;
}
}
let url = 'https://www.fit-star.de/fitnessstudio/muenchen-giesing'
const currentGymCapacity = await fetchGymStats()
const widget = new ListWidget()
await createWidget()
if (!config.runsInWidget) {
await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
async function createWidget() {
widget.backgroundColor = new Color("333333", 1)
let lastWeekPath = new LineChart(512, 512, currentGymCapacity["full"]["last_week"].map(parseFloat).map((v) => v + 7), 0, 100, 12)._calculatePath();
let chart = new LineChart(512, 512, currentGymCapacity["full"]["today"].map(parseFloat).map((v) => v + 7), 0, 100, 12).configure((ctx, path) => {
ctx.opaque = false;
ctx.setFillColor(new Color("ffffff", .35));
ctx.addPath(lastWeekPath);
ctx.fillPath(lastWeekPath);
ctx.setStrokeColor(new Color("ffff00", 0.8));
ctx.setLineWidth(8);
ctx.addPath(path);
ctx.strokePath(path);
}).getImage();
widget.setPadding(-5,8,-5,-3)
widget.backgroundImage = chart
widget.addSpacer(7)
const capacityText = widget.addText(parseFloat(currentGymCapacity["full"]["percentage"]).toFixed(0) + "%")
capacityText.font = Font.mediumRoundedSystemFont(28)
if (currentGymCapacity < 10) {
capacityText.textColor = new Color("#33cc33")
} else if (currentGymCapacity < 30){
capacityText.textColor = new Color("#ff9900")
}else{
capacityText.textColor = new Color("#ff3300")
}
const available = parseInt(currentGymCapacity["full"]["available"], 10);
const assumedCurrent = Math.round(0.8*parseFloat(currentGymCapacity["full"]["percentage"]));
const assumedTotal = assumedCurrent + available;
const toPeople = widget.addText(
${assumedCurrent} / ${assumedTotal}\nLive: ${currentGymCapacity["count"].toString()}`
);
toPeople.font = Font.regularSystemFont(12)
toPeople.textColor = Color.white()
widget.addSpacer(75)
let df = new DateFormatter()
df.dateFormat = 'HH:mm'
const lastupdate = widget.addText(`${df.string(new Date())} `);
lastupdate.font = Font.regularSystemFont(10)
lastupdate.textColor = Color.white()
lastupdate.centerAlignText();
}
async function fetchGymStats() {
const req = new Request(url + "?t=" + (new Date()).toISOString())
const result = await req.loadString()
const r1 = new RegExp("studios.*JSON.parse\\(JSON.stringify\\((.*)\\)\\)","g")
const matches1 = r1.exec(result);
if (matches1.length < 2) return null;
const r2 = new RegExp("live.*JSON.parse\\(JSON.stringify\\((.*)\\)\\)","g")
const matches2 = r2.exec(result);
if (matches2.length < 2) return null;
return {full: JSON.parse(matches1[1].replaceAll('\\"', '"'))[0], count: JSON.parse(matches2[1])[0][0]};
}
@v4lli
Copy link
Author

v4lli commented Jun 6, 2021

Still contains a few code smells (especially regarding layouting in Scriptable, which I didn't look into at all). Also, the total capacity is an educated guess.

IMG_4675

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