Cannot actually query dark/light mode in script?

[THIRD UPDATE]:
I do not understand why, but having at least one widget on a homescreen that was built with the transparent background code causes all widgets that use the custom isUsingDarkAppearance() function to update correctly when switching between dark/light mode. I added back a widget built with supermamon’s no-background.js code back to my homescreen today, and my widgets now update correctly. The only difference I can find in our code is that supermamon’s example code imports the function that checks for light versus dark mode from no-background.js, and mine is inline. Why this would make a difference is beyond me, since the function doesn’t call anything else in the imported file, and it does work inline in my code just fine – as long as there is a supermamon-style transparent widget also somewhere on the home screen. I don’t see anything in the transparent widget code that would force an update, and, yeah, the single function I’m using doesn’t use anything else in the no-background.js code, anyway.

So I don’t understand it, but I have a solution at least, which is to include at least one widget that imports no-background.js on my homescreen. big shrug emoji.

[SECOND UPDATE]:
Well, today the scriptable widgets spent the entire day in dark mode, despite the phone being in light mode the whole time and me clicking my “udpate widgets” shortcut numerous times, clicking on the widgets themselves numerous times, etc. If it’s so unreliable that it will sometimes go an entire day without updating, it’s not going to be a workable solution (for my use case, which is having light/dark responsive custom widgets, where I have an automation that switches my phone into light mode every morning, and dark mode every evening).

[FIRST UPDATE]:
Ok, so it does work, with the caveat that you can only suggest to the OS that it should update the widgets. The OS will eventually get around to doing the refresh, and display the correct appearance, but it might be several minutes before it finally does this. If you’re patient, all the example code mentioned below will work. But you will not see immediate changes when you switch appearance modes, even if you refresh the widget.

[Original post]:
I’m trying to figure out how to query the dark mode/light mode state of the device within a script so that the result can be used choose an image to display in a widget, depending on which mode the device is in. I’ve seen this question mentioned before and the advice was to use the isUsingDarkAppearance() in supermamon’s transparent background code here. However, it doesn’t actually work when I try the example code (I used the 3-part example code mentioned at the end of the readme).

Here’s what I get for light mode, all good:

But here’s what I get in dark mode, not so good (I did upload and select separate dark and light images from those backgrounds):

I created a simple example to clarify where things are going wrong. The temptation is to use the return value from Color.dynamic() to determine the appearance, but it seems like Color.dynamic() is only evaluated when either setWidget() or present[Size]() is called, regardless of where you stick the call to Color.dynamic() in your code. This means it is only useful for changing the background and text color, but not for querying the state of the device to make other kinds of changes within your script. Here’s the simple example I created, with plenty of comments:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;
const background_light = new Color("#f0f0f0")
const background_dark = new Color("#323232")

// When it's run here Color.dynamic() only returns light mode on the iPhone
// and only returns dark mode on the iPad
// regardless of the actual state of the device.
async function isUsingDarkAppearance() {
    let c = await Color.dynamic(Color.white(), new Color("#000000"))
    console.log(c)
    return c.red == 0
}

// First query the current appearance of the device and save the result
// This doesn't work because Color.dynamic isn't being evaluated inside the
// isUsingDarkAppearance function
let appearance = (await isUsingDarkAppearance()) ? "dark" : "light"

let w = new ListWidget()
w.backgroundColor = Color.dynamic(background_light, background_dark)
w.addSpacer()

// Report what we got for appearance
// before setWidget() or presentMedium() is run
// Spoiler alert -- it will always be light on a phone, dark on an iPad
w.addText("Current appearance: " + appearance)
w.addSpacer()
// more output from Color.dynamic so we can query the hex of the color directly
now = new Date()
dateElem = w.addDate(now)
dateElem.textColor = Color.dynamic(Color.black(), Color.white())
w.addSpacer()
w.addText("Color of the date text: " + dateElem.textColor.hex)
w.addSpacer

// Make the actual widget.
// Here is where, finally, all the calls to Color.dynamic() will actually
// be evaluated.
// But this is too late to use the return value within our own code above.
if (config.runsInWidget) {
    Script.setWidget(w)
} else {
    await w.presentMedium()
}

Here it is running on an iPhone in dark mode (note that it’s reporting light mode with dark text, which is the opposite of what we’re seeing):

and in light mode (this is right in the “stopped clock” sense because it will always only report light mode on the phone):

And here it is on an iPad, which will only report dark mode, in actual dark mode:

and light mode on the iPad, which is wrong, because the iPad only ever reports dark mode:

So, is there any way to successfully query the state of the appearance before running setWidget() or present so that we can use that information within our scripts?

1 Like

Have you read this topic?

Your specific case mentioned is known and documented in the API documentation already

It’s not. The sample code uses a custom function that happens to have the same name as the one in the API. (I should have anticipated this response and just changed the name it to prevent it.) I put the function definition at the top of the example code to help people find it.

(update: Oh, I see, you’re replying to the reply about the one in the API… yep, that one also doesn’t work. Although, I see at the end of the thread someone says it does work now, but I tried it and it doesn’t so…?)

I know. I was replying to sylumer, as you can see ivy the icon next to my reply.

I was helping you with that same code on Discord.

Yes - I was directing the OP to the discussion and analysis that I believe prompted the addition of that update to the docs, based on this section in the original post on this thread.

1 Like