DEV Community

Cover image for Unofficial dev.to iOS widget
Matthias
Matthias

Posted on

11 2

Unofficial dev.to iOS widget

Since version 14 iOS supports different home screen widgets which can display different useful information for your installed apps.
Wouldn't it be cool if you can create your own widgets to display some custom information? Good news, with the great Scriptable iOS app you can create your own scripts and widgets for iOS using pure javascript.

The widget 📱

Reading some dev.to articles are an integral part of my daily routine, so I thought wouldn't it be cool if I get a random article directly on my home screen.

So I made a little script which fetches a random article out of the top ten articles of the day on dev.to based on the tags I have subscribed to.
After that, the article details like title, author, profile image and tags are displayed in a medium-sized widget. You can see the final widget in the following image.
unofficial dev.to widget

Get the widget 🚀

If you want the widget on your home screen you will need to download the Scriptable iOS on your device. After that, you can create a new script inside the app and copy and paste the code from the following gist into your created script.

To fetch the dev.to articles you need to generate an API key in your dev.to settings and paste it at the beginning of the created script.
Now you can preview the widget by starting the script inside the Scriptable app.

Finally, you can add the widget to your home screen by adding a medium-sized Scriptable widget, after that you can configure the widget by long-pressing the empty widget and then choose the newly created script in the widget configuration.

I hope you like my widget, feel free to write to me if you encounter any issues or want to suggest some improvements.

const API_KEY = "paste API key here";
const ARTICLE_PARAMS = [{ per_page: "10" }, { top: "1" }];
let widget = await createWidget();
// Check where the script is running
if (config.runsInWidget) {
// Runs inside a widget so add it to the homescreen widget
Script.setWidget(widget);
} else {
// Show the medium widget inside the app
widget.presentMedium();
}
Script.complete();
async function createWidget() {
// Create new empty ListWidget instance
let listwidget = new ListWidget();
// Configure a new background gradient
let gradient = new LinearGradient();
// Set the color stops at the beginning and at the end of the gradient
gradient.locations = [0, 1];
// Set the colors of the different color stops
gradient.colors = [new Color("#232526"), new Color("#414345")];
// Set the start and the end point of the gradient
// Points are defined as x and y position between 0 and 1
gradient.startPoint = new Point(0.5, 0);
gradient.endPoint = new Point(0.5, 1);
// Add the gradient to the widget
listwidget.backgroundGradient = gradient;
// Get a random artice
const article = await getArticle();
// Add the article url to the widge so it will be opened when the widget is clicked
listwidget.url = article.url;
// Add the heading of the article to the widget
let heading = listwidget.addText(article.title);
heading.font = Font.semiboldSystemFont(28);
heading.textColor = new Color("#ffffff");
heading.minimumScaleFactor = 0.5;
// Add vertical spacing between the heading and the footer stack
listwidget.addSpacer();
// Add the footer stack which holds the author information and the dev.to logo
const footerStack = listwidget.addStack();
footerStack.bottomAlignContent();
// Add a stack to the footerstack to hold the profile information
const profileStack = footerStack.addStack();
profileStack.topAlignContent();
// Get the article authors profile image and add it to the stack
const image = await getImage(article.user.profile_image_90);
let profileImage = profileStack.addImage(image);
// Configure the profile image
profileImage.imageSize = new Size(50, 50);
profileImage.cornerRadius = 25;
profileImage.borderWidth = 4;
profileImage.borderColor = new Color("#bfbfbf");
// Add spacer between the profile image and the username stack
profileStack.addSpacer(10);
// Add stack for the username and the article tags
const nameStack = profileStack.addStack();
nameStack.layoutVertically();
// Add and configure the username
const username = nameStack.addText(article.user.name);
username.textColor = new Color("#ffffff");
username.font = Font.lightSystemFont(17);
// Add and configure the article tags
const taglist = nameStack.addText(
prefixTags((tags = article.tag_list), (prefix = "#"))
);
taglist.textColor = new Color("#bfbfbf");
taglist.font = Font.semiboldSystemFont(13);
// Add spacer between the username stack and the dev.to logo
footerStack.addSpacer();
// Get the dev.to logo and add it to the logo stack
const devLogo = await getImage(
"https://d2fltix0v2e0sb.cloudfront.net/dev-ecosystem.png"
);
const logo = footerStack.addImage(devLogo);
// Configure the logo
logo.imageSize = new Size(30, 30);
// Return the created widget
return listwidget;
}
function constructQueryString(parameters) {
// Iterate over the parameters and construct the query string by joining them
return (
"?" +
parameters
.map((param) => {
key = Object.keys(param)[0];
return key + "=" + param[key];
})
.join("&")
);
}
async function getArticle() {
// Get the tags which the user has subscribed to
const tags = { tags: await getTags() };
// Append the default query parameters with the tags of the user
const parameters = ARTICLE_PARAMS.concat(tags);
// Construct the query url
const url = "https://dev.to/api/articles" + constructQueryString(parameters);
// Initialize new request
const request = new Request(url);
// Add authentication header to the request
request.headers = { "api-key": API_KEY };
// Execute the request and parse the response as json
const response = await request.loadJSON();
// Choose a random article
const article = response[Math.floor(Math.random() * response.length)];
// Return the article
return article;
}
async function getImage(url) {
// Initialize new request
const request = new Request(url);
// Add authentication header to the request
request.headers = { "api-key": API_KEY };
// Execute the request and parse the response as image
const image = await request.loadImage();
// return the image
return image;
}
async function getTags() {
// Initialize new request
const request = new Request("https://dev.to/api/follows/tags");
// Add authentication header to the request
request.headers = { "api-key": API_KEY };
// Execute the request and parse the response as json
const response = await request.loadJSON();
// Get the names of the returned tags and join them to a string
const tags = response.map((tag) => tag.name).join();
// return the created string
return tags;
}
function prefixTags(tags, prefix) {
return tags
.map((tag) => prefix + tag.trim())
.join(" ");
}

Top comments (8)

Collapse
 
cdthomp1 profile image
Cameron Thompson

Have you had much experience with the scriptable app?

Collapse
 
matthri profile image
Matthias

No it was the first time I created a widget with Scriptable but the API is very straightforward.
I plan to publish another article in the next few days on how to create iOS widgets with Scriptable if you are interested in that.

Collapse
 
cdthomp1 profile image
Cameron Thompson

In would be very interested! I found the Scriptable app last week but was intimidated to code on the phone. Are you able to develop on a desktop?

Thread Thread
 
matthri profile image
Matthias

The answer is it depends, when you use macOS you can use the Scriptable Mac Beta to code on desktop. Another solution could be the iCloud sync of Scriptable --> edit the script on your desktop inside the iCloud folder and test the automatic synced script on your iPhone (never tested the second solution myself)

Thread Thread
 
cdthomp1 profile image
Cameron Thompson

Thankfully I develop on a Mac! I will have to try that beta app out, thanks!

Thread Thread
 
matthri profile image
Matthias

It took a while because I had a few stressful weeks but now the article is finally ready.

FYI

Collapse
 
nro337 profile image
Nick Alico

Thank you so much for sharing! It works great!

Collapse
 
matthri profile image
Matthias

Hey Nick,
thanks for the kind reply, I'm glad you like it