Enabling Focus on iOS through scripting and tags (and seeking suggestions for faster execution)

I got tired of waiting/hoping for a Focus mode on iOS, so I’ve hacked together a workaround using AppleScript and OF on the Mac. I call it “Deep Focus”, which is a dumb name, but it helps me differentiate the two.

The general idea is to select a focus area (e.g., a folder), and the script then iterates through all non-selected areas, places any Active projects in those areas On Hold, and tags them with an “Unfocused” tag. To switch back, a reset script iterates through Unfocused items and makes them Active again, then removes the tag.

I plan on using Jason Snell’s remote scripts idea to activate these scripts via Shortcuts on iPhone/iPad.

It’s not perfect, and I have a variety of improvements to make. (e.g., It should be updated to use whatever projects/folders are currently selected as the target thing to focus on—it currently relies on a prompt.)

Still, it essentially provides Focus and syncs the focus setting between platforms. This way, if my work day is haywire, I can turn on Deep Focus and all the flagged and Forecast and etc. tasks outside of my workplace projects will disappear 'til I get through it. I can then glance at my phone/iPad widget and watch and not get distracted by all the other important things I’m ignoring in lieu of looming deadlines.

The problem:

It takes maybe a minute for the script to iterate through the projects and switch them to On Hold. I’d love some suggestions on speeding it up.

The scripts

Activate Deep Focus

property pFolderList : {"Study", "Sundry", "Student Experience Office", "Home", "Defocus"}
property pDefaultFolder : "Defocus"
property pPrompt : "Deep focus setting?"
property debug : false

set resetScript to load script (POSIX file "/Users/ryanjamurphy/Library/Application Scripts/com.omnigroup.OmniFocus3/Reset Deep Focus.scpt")

tell application "OmniFocus"
	activate
	tell the default document
		my resetScript's resetDeepFocus()
		set allFolders to folders
		set allProjects to flattened projects
		set unfocusedTag to the first flattened tag whose name is "Unfocused"
		if debug then
			set folderNames to ""
			repeat with eachFolder in allFolders
				set folderNames to folderNames & "
 " & name of eachFolder as string
			end repeat
			display dialog folderNames
		end if
		
		choose from list pFolderList ¬
			with title "Set deep focus" with prompt pPrompt ¬
			OK button name ¬
			"Confirm" default items pDefaultFolder
		
		if result is false then
			return
		else
			set focusArea to the first folder whose name is my result
		end if
		
		repeat with eachFolder in allFolders
			set focusCheck to true
			set focusCheck to my checkFolder(eachFolder, focusArea)
			if focusCheck is false then
				if debug then
					display dialog (name of eachFolder & " has been found to not be " & name of focusArea & ". We will now make each child project of the folder On Hold and add an Unfocused tag.")
				end if
				set theProjects to (the flattened projects of eachFolder whose status is active)
				repeat with eachProject in theProjects
					if debug then
						display dialog (name of eachProject as string) & "'s status is " & status of eachProject & "."
					end if
					set eachProject's status to on hold
					add unfocusedTag to the tags of eachProject
					if debug then
						display dialog (("Set " & name of eachProject as string) & " to On Hold. Added tag Unfocused.")
					end if
				end repeat
			else
				if debug then
					display dialog "Projects in " & (name of eachFolder) & " will stay active."
				end if
			end if
		end repeat
		
		-- continue
	end tell
end tell

-- A recursive method to see if a given folder is the focused folder. If not, return false. The main script body will then tag each active project in the given folder with "Unfocused" and set their status to On Hold.
on checkFolder(aFolder, theFocusArea)
	tell application "OmniFocus"
		tell default document
			if debug then
				display dialog (("Comparing " & name of aFolder as string) & " with " & name of theFocusArea as string) & "."
			end if
			if id of aFolder is equal to id of theFocusArea then
				if debug then
					display dialog ((name of aFolder as string) & " is the same as " & name of theFocusArea as string) & "."
				end if
				return true
			else
				return false
				try
					set subFolders to flattened folders of aFolder
					if subFolders is not {} then
						repeat with eachFolder in subFolders
							set focusCheck to my checkFolder(eachFolder, theFocusArea)
							return focusCheck
						end repeat
					end if
				end try
			end if
		end tell
	end tell
end checkFolder

Reset Deep Focus

Note: this one is a bit faster, because you can delete and re-create the Unfocused tag to quickly remove it from all items.

property debug : false

tell application "OmniFocus"
	activate
	tell the default document
		my resetDeepFocus()
	end tell
end tell

on resetDeepFocus()
	tell application "OmniFocus"
		tell default document
			set unfocusedTag to first flattened tag whose name is "Unfocused"
			set unfocusedTasks to tasks of unfocusedTag
			set unfocusedTaskCount to count of unfocusedTasks
			set unfocusedTaskNames to ""
			repeat with eachTask in unfocusedTasks
				set unfocusedTaskNames to unfocusedTaskNames & "
" & name of eachTask
				
				try
					set theProject to the containing project of eachTask
					set status of theProject to active
				end try
			end repeat
			
			
			delete unfocusedTag
			make tag with properties {name:"Unfocused"}
			
			display alert "Deep focus has been reset. " & unfocusedTaskCount & " projects have been restored to active status."
			if debug then
				display dialog unfocusedTaskNames
			end if
		end tell
	end tell
end resetDeepFocus

Have you considered Omni automation for that ?

I haven’t played with it yet, but it’s on my agenda. It would help with doing this on iOS, though I’m not sure it would help with the speed issue.

I really liked this idea. :-) I hope you don’t mind, but I stole it and adapted it for Omni Automation:

Use at your own risk, obviously. (I am an amateur.) On my database it’s taking around 20 seconds to run each script. No idea if it will be any faster than your Applescript, but it does work on iOS.

It uses the selected projects and/or folders.

I’d love to figure out a way to run it at a set time with specific folders/projects (ideally triggered by a calendar—automated time-blocking!) but that’s on the “play with when I get back to a Mac” list. I think it’s possible with a combination of Zapier, Hazel, and maybe Keyboard Maestro…but I need to think about it some more…hmmmm.

[Edited 2019-09-25: The script moved. Updated the link here in case anyone stumbles across this later.]

1 Like

Amazing! Can’t wait to try this out.

I was thinking about automatic context-switching too—then I became a full-time work-from-home PhD candidate and I’m not switching contexts as often anymore. Keyboard Maestro is probably all you would need, though!

Finally getting around to setting this up, and it works fabulously. Thanks for the adaptation to Omni Automation!

FYI, the GitHub Readme says it won’t work in macOS because of missing support for a variable, but it seems to work just fine now. Edit: nevermind. I must have been daydreaming when I thought I ran it successfully on macOS, because I can’t get it to work now. Looking forward to future OmniJS updates, I guess!

And I can’t wait for Omni to finally bring this to iOS.
Yes, it’s coming to web… but… iOS, please @kcase :-)

Haha, it is really quite easy to work around that error so if you would find it useful to have on macOS too, I can give you a fix. For my use I didn’t bother just because on macOS I can use the built-in Focus option and I’m assuming the flattenedTasks property won’t be too far away from macOS anyway. :-)

Gracious of you! I’m okay to wait, too. Looking forward to a few more properties becoming available via the API.

Okay, I don’t know what to chalk this up to, but it works again on macOS. Computer weather, I guess? I was going to modify it to work across platforms myself just to play around with Automation more, but it seems it’s not necessary…