Widget Examples

I wrote about this one:

Based on Matt Silverback’s original code, I added AQI calculations and set the widget to present the background color based on the current air quality status.

code here: https://gist.github.com/jasonsnell/4b458e2775e11ff7dd8b21dd26aa504e

7 Likes

My new widget shuffles one of your latest Pocket bookmarks and opens the article in Safari once you tap it.

I also built a shortcut which makes it easy to obtain your Pocket access token and stores the whole script at the end. Just save it to your scriptable folder in iCloud Drive.

Gist & HowTo: https://gist.github.com/marco79cgn/e05ca19ea2d15194bc7991f7efab8083

3 Likes

Update 24.09.2020: fixed fonts

My next one (Sorry I’m addicted):
I ported my Simpsons Randomizer shortcut to a widget. It shuffles a random episode of The Simpsons. Upon tapping on the widget it plays the episode on Disney+. ¡Ay, caramba!

Gist and instructions: https://gist.github.com/marco79cgn/ac9a8add1c7dc5a6749b751a1d2a05a4

Cool :+1:t2: Thanks for sharing!

PS: Reached you on Twitter, waiting for your response:-)

Okay. I must be dumb. What am I doing wrong? I can’t get a script to show up. I pulled the xkcd example (and others) and this is what I see when I select it from the widget…

Running beta 6

update: now supports build (155) - removed centerContent for addSpacer

I made a Nintendo Switch eShop widget, based on my Nintendo Switch eShop script.

The latest Scriptables update made it easier to debug widgets so I fixed a script I had been working on: a way to quickly see what is on the eShop. Tapping will take you to that game on the eShop’s website.

I wish I could make a shelf or a grid of these, but for now this is pretty cool.

Get it from the gist here

2 Likes

I made a widget that shows the most recent article on a Feedbin account. It uses etags for caching and won’t break even when there is no internet connection - it will just show the last item it downloaded and indicate that it’s cached.

Here’s the code:

let fm = FileManager.local()
const etagPath = fm.joinPath(fm.documentsDirectory(), `feedbin-widget-etag-${Device.name()}.txt`)
const itemPath = fm.joinPath(fm.documentsDirectory(), `feedbin-widget-item-${Device.name()}.json`)

const authHeader = btoa(Keychain.get("feedbin-auth"))
let headers = {"base": {"Authorization": `Basic ${authHeader}`}}
headers["unread_entries"] = Object.assign({}, headers["base"])
if (fm.fileExists(etagPath)) {
    headers["unread_entries"]["If-None-Match"] = fm.readString(etagPath)
}

let item = {}

try {
    item = await loadItem()
} catch {
    item = JSON.parse(fm.readString(itemPath))
    item.author += item.author != "" ? " — Cached" : "Cached"
}

let widget = createWidget(item)
widget.presentMedium()

if (config.runsInWidget) {
    Script.setWidget(widget)
    Script.complete()
}

function createWidget(item) {
  const textColor = new Color("#ffffff")
  let gradient = new LinearGradient()
  gradient.colors = [new Color("#1c1c1e"), new Color("#0c0c0e")]
  gradient.locations = [0.5, 1]

  let w = new ListWidget()
  w.backgroundGradient = gradient
  w.setPadding(10, 20, 10, 20)
  
  let titleTxt = w.addText(item.title)
  titleTxt.applyHeadlineTextStyling()
  titleTxt.textColor = textColor
  titleTxt.lineLimit = 2

  w.addSpacer(7)
  
  let authorTxt = w.addText(item.author)
  authorTxt.applyBodyTextStyling()
  authorTxt.textColor = textColor
  authorTxt.textOpacity = 0.8
  authorTxt.textSize = 12

  w.addSpacer(7)
  
  let summaryTxt = w.addText(item.summary)
  summaryTxt.applySubheadlineTextStyling()
  summaryTxt.textColor = textColor
  summaryTxt.textOpacity = 0.8
  summaryTxt.lineLimit = 2

  w.url = item.url
  
  return w
}
  
async function loadItem() {
    const baseURL = "https://api.feedbin.com/v2/"
    const unreadURL = "unread_entries.json"
    const entryURL = "entries.json"
    
    // get latest unread entry id
    let unreadRequest = new Request(baseURL + unreadURL)
    unreadRequest.headers = headers["unread_entries"]
    const unreadResponse = await unreadRequest.load()
    
    if (unreadRequest.response.statusCode == "200") {
        const unreadEntryIDs = JSON.parse(unreadResponse.toRawString())
        const latestID = unreadEntryIDs[unreadEntryIDs.length - 1]
        const etag = unreadRequest.response.headers["Etag"]
    
        // get entry
        let entryRequest = new Request(baseURL + entryURL + `?ids=${latestID}`)
        entryRequest.headers = headers["base"]
        const unreadEntry = await entryRequest.loadJSON()
        const latestEntry = unreadEntry[0]
        
        if (latestEntry == undefined) {
            entry = {"title": "No unread articles", "author": "", "summary": "—"}
        } else {
            entry = latestEntry
        }
        
        fm.writeString(itemPath, JSON.stringify(entry))
        fm.writeString(etagPath, etag)
        return entry

    } else if (unreadRequest.response.statusCode == "304") {
        // nothing changed, read from file
        return JSON.parse(fm.readString(itemPath))
    }
}

2 Likes

Please note as of the latest beta build (155), any script that is calling calling topAlignContent(), centerAlignContent() or bottomAlignContent() on a ListWidget will now throw an error. I’m sorry, but I’ve replaced the functions during the beta with the more flexible addSpacer() function.

If you add a spacer at the top of your widget by calling addSpacer() before adding other elements, your content will be bottom aligned. If you add the spacer at the bottom of your widget, the content will be top aligned. You can also call addSpacer(5) to add a spacer with a fixed height of 5 pixels.

This may happen if the widget crashes for some reason. If you’re using scripts that for others, it’s probably not due to memory constraints. Can you try rebooting your device? Sometimes that helps, at least for a while. It would be interesting to know if that helped.

Thanks for your great work so far.

Would it be possible to create a way to run the widget without actually running the installed version? Debugging is a pain because iOS doesn’t have a way to force refresh a widget.

Also am I missing something or is there no way to find & replace in the script editor?

You can call widget.presentSmall() to view the widget in the app. There’s also functions for viewing the medium and large size widgets.

There’s not currently any way to find and replace. It’s a popular feature request, so I’ll probably get to it at some point. There’s just so many things to do right now :sweat_smile:

5 Likes

Thanks. I’m surprised native F&R isn’t baked into the OS.

Let’s Test Flight release fixed it. Thanks!

Hi Simon and fellow automators,

maybe my question is stupid, but I hope you can help me.

I wanted to create a widget in which a script should run.
In my script I’m calling a URL and normally use WebView to show the result in Siri.
But now I want the widget to display it.

Do you have an example for me?

Unfortunately, the website I want to see doesnt support rss or json or any public api I could work with.

Many thanks in advance and keep on your great work!

At the moment, Scriptable widgets can only show lines of text and images, not web content.

1 Like

It’s correct that Scriptable doesn’t support showing web content. I’d have to test it but I’m not sure it’s possible to show a web view in a widget. Essentially iOS/iPadOS periodically takes a “snapshot” of a widget and shows a cached result. I’m not sure that would work for web content but I could be wrong.

I just stumbled upon Glimpse, an iOS 14 beta app that can take web content and show it in widgets. I haven’t tested it yet but here is the TestFlight link for those who’d like to give it a go:

2 Likes

Made one that pulls the content of a randomly chosen Trello card with a specific label on a board.

1 Like

I made a widget that takes a random quote from an Airtable base where I store a selection I curate.
Airtable is convenient because it can be enriched from anywhere and it exposes APIs that are super easy to use. Now, I’d be happy to have the possibility to do the same with DataJar @simonbs :slight_smile:
Here is the code - Not so neat but it does the job:

let baseURL = "https://api.airtable.com/v0/REPLACEWITHYOURS/Quotes?maxRecords=100&view=Grid%20view"    
let r = new Request(baseURL);
r.headers = { 'Authorization': 'Bearer REPLACEWITHYOURS'};
let json = await r.loadJSON(); 
let quotes = json['records'];
let randomQuote = (quotes[Math.floor(Math.random()*quotes.length)]);
let quoteText = randomQuote['fields']['Quote'];
let quoteAuthor = randomQuote['fields']['Author'];


let widget = createWidget();

if (config.runsInWidget) {
    let widget = createWidget();
    Script.setWidget(widget);
    Script.complete();
}

function createWidget() {
    let w = new ListWidget();
    let widgetText = w.addText(quoteText);
    widgetText.textSize = 15;
    let widgetSubText = w.addText(quoteAuthor);
    widgetText.textSize = 13;    
    return w
}


4 Likes

Quick question: can the content of a widget only be updated when the script is run? For example, could the example News in Widget script refresh every so often?

1 Like