Compare commits

..

No commits in common. "cd8f0e79264b8080a036a14dae3af6c2e4661bb7" and "b663335c6d17d38b89bdbf12fa35c79dd4196714" have entirely different histories.

10 changed files with 127 additions and 211 deletions

View File

@ -292,15 +292,12 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
let rootViewController = delegate.rootViewController { let rootViewController = delegate.rootViewController {
let mastodonController = MastodonController.getForAccount(account) let mastodonController = MastodonController.getForAccount(account)
// if the scene is already active, then we animate things // if the scene is already active, then we animate the account switching if necessary
let animated = scene.activationState == .foregroundActive delegate.activateAccount(account, animated: scene.activationState == .foregroundActive)
delegate.activateAccount(account, animated: animated) rootViewController.select(route: .notifications, animated: false) {
rootViewController.runNavigation(animated: animated) { navigation in
navigation.select(route: .notifications)
let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController) let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController)
navigation.push(viewController: vc) rootViewController.getNavigationController().pushViewController(vc, animated: false)
} }
} else { } else {
let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID) let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID)

View File

@ -83,7 +83,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
} else { } else {
context = ActiveAccountUserActivityHandlingContext(isHandoff: true, root: rootViewController!) context = ActiveAccountUserActivityHandlingContext(isHandoff: true, root: rootViewController!)
} }
_ = userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene, context: context)) Task(priority: .userInitiated) {
_ = await userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene, context: context))
}
} }
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
@ -191,9 +193,11 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
if let activity = launchActivity { if let activity = launchActivity {
func doRestoreActivity(context: UserActivityHandlingContext) { func doRestoreActivity(context: UserActivityHandlingContext) {
_ = activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!, context: context)) Task(priority: .userInitiated) {
_ = await activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!, context: context))
context.finalize(activity: activity) context.finalize(activity: activity)
} }
}
if activity.isStateRestorationActivity { if activity.isStateRestorationActivity {
doRestoreActivity(context: StateRestorationUserActivityHandlingContext(root: rootViewController!)) doRestoreActivity(context: StateRestorationUserActivityHandlingContext(root: rootViewController!))
} else if activity.activityType != UserActivityType.mainScene.rawValue { } else if activity.activityType != UserActivityType.mainScene.rawValue {

View File

@ -79,11 +79,13 @@ class AccountSwitchingContainerViewController: UIViewController {
stateRestorationLogger.debug("AccountSwitchingContainer: reusing existing VC for \(account.id, privacy: .public)") stateRestorationLogger.debug("AccountSwitchingContainer: reusing existing VC for \(account.id, privacy: .public)")
} else { } else {
newRoot = newRootProvider() newRoot = newRootProvider()
Task(priority: .userInitiated) {
stateRestorationLogger.debug("AccountSwitchingContainer: restoring \(activity.activityType, privacy: .public) for \(account.id, privacy: .public)") stateRestorationLogger.debug("AccountSwitchingContainer: restoring \(activity.activityType, privacy: .public) for \(account.id, privacy: .public)")
let context = StateRestorationUserActivityHandlingContext(root: newRoot) let context = StateRestorationUserActivityHandlingContext(root: newRoot)
_ = activity.handleResume(manager: UserActivityManager(scene: view.window!.windowScene!, context: context)) _ = await activity.handleResume(manager: UserActivityManager(scene: view.window!.windowScene!, context: context))
context.finalize(activity: activity) context.finalize(activity: activity)
} }
}
} else { } else {
newRoot = newRootProvider() newRoot = newRootProvider()
} }

View File

@ -333,14 +333,14 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
// Transfer the navigation stack, dropping the search VC, to keep anything the user has opened // Transfer the navigation stack, dropping the search VC, to keep anything the user has opened
transferNavigationStack(from: .tab(.explore), to: exploreNav, dropFirst: true, append: true) transferNavigationStack(from: .tab(.explore), to: exploreNav, dropFirst: true, append: true)
tabBarViewController.select(tab: .explore, dismissPresented: false, animated: false) tabBarViewController.select(tab: .explore, dismissPresented: false)
case let .tab(tab): case let .tab(tab):
// sidebar items that map 1 <-> 1 can be transferred directly // sidebar items that map 1 <-> 1 can be transferred directly
tabBarViewController.select(tab: tab, dismissPresented: false, animated: false) tabBarViewController.select(tab: tab, dismissPresented: false)
case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_): case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_):
tabBarViewController.select(tab: .explore, dismissPresented: false, animated: false) tabBarViewController.select(tab: .explore, dismissPresented: false)
// Make sure the Explore VC doesn't show its search bar when it appears, in case the user was previously // Make sure the Explore VC doesn't show its search bar when it appears, in case the user was previously
// in compact mode and performing a search. // in compact mode and performing a search.
let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController let exploreNav = tabBarViewController.viewController(for: .explore) as! UINavigationController

View File

@ -54,22 +54,19 @@ class MainTabBarViewController: BaseMainTabBarViewController {
view.backgroundColor = .appBackground view.backgroundColor = .appBackground
} }
func select(tab: Tab, dismissPresented: Bool, animated: Bool, completion: (() -> Void)? = nil) { func select(tab: Tab, dismissPresented: Bool) {
if tab == .compose { if tab == .compose {
compose(editing: nil, completion: completion) compose(editing: nil)
} else { } else {
// when switching tabs, dismiss the currently presented VC // when switching tabs, dismiss the currently presented VC
// otherwise the selected tab changes behind the presented VC // otherwise the selected tab changes behind the presented VC
if presentedViewController != nil && dismissPresented { if presentedViewController != nil && dismissPresented {
dismiss(animated: animated) { dismiss(animated: true) {
stateRestorationLogger.info("MainTabBarViewController: selecting \(String(describing: tab), privacy: .public)")
self.selectedIndex = tab.rawValue self.selectedIndex = tab.rawValue
completion?()
} }
} else { } else {
stateRestorationLogger.info("MainTabBarViewController: selecting \(String(describing: tab), privacy: .public)") stateRestorationLogger.info("MainTabBarViewController: selecting \(String(describing: tab), privacy: .public)")
selectedIndex = tab.rawValue selectedIndex = tab.rawValue
completion?()
} }
} }
} }
@ -154,24 +151,25 @@ extension MainTabBarViewController: TuskerRootViewController {
func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) { func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) {
switch route { switch route {
case .timelines: case .timelines:
select(tab: .timelines, dismissPresented: true, animated: animated, completion: completion) select(tab: .timelines, dismissPresented: true)
case .notifications: case .notifications:
select(tab: .notifications, dismissPresented: true, animated: animated, completion: completion) select(tab: .notifications, dismissPresented: true)
case .myProfile: case .myProfile:
select(tab: .myProfile, dismissPresented: true, animated: animated, completion: completion) select(tab: .myProfile, dismissPresented: true)
case .explore: case .explore:
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion) select(tab: .explore, dismissPresented: true)
case .bookmarks: case .bookmarks:
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion) select(tab: .explore, dismissPresented: true)
getNavigationController().pushViewController(BookmarksViewController(mastodonController: mastodonController), animated: animated) getNavigationController().pushViewController(BookmarksViewController(mastodonController: mastodonController), animated: animated)
case .list(id: let id): case .list(id: let id):
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion) select(tab: .explore, dismissPresented: true)
if let list = mastodonController.getCachedList(id: id) { if let list = mastodonController.getCachedList(id: id) {
let nav = getNavigationController() let nav = getNavigationController()
_ = nav.popToRootViewController(animated: animated) _ = nav.popToRootViewController(animated: animated)
nav.pushViewController(ListTimelineViewController(for: list, mastodonController: mastodonController), animated: animated) nav.pushViewController(ListTimelineViewController(for: list, mastodonController: mastodonController), animated: animated)
} }
} }
completion?()
} }
func getNavigationDelegate() -> TuskerNavigationDelegate? { func getNavigationDelegate() -> TuskerNavigationDelegate? {
@ -188,7 +186,7 @@ extension MainTabBarViewController: TuskerRootViewController {
return return
} }
select(tab: .explore, dismissPresented: true, animated: false) select(tab: .explore, dismissPresented: true)
exploreNavController.popToRootViewController(animated: false) exploreNavController.popToRootViewController(animated: false)
// setting searchController.isActive directly doesn't work until the view has loaded/appeared for the first time // setting searchController.isActive directly doesn't work until the view has loaded/appeared for the first time

View File

@ -20,14 +20,6 @@ protocol TuskerRootViewController: UIViewController, StateRestorableViewControll
func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController?
} }
extension TuskerRootViewController {
func runNavigation(animated: Bool, _ builder: (_ navigation: TuskerNavigationSequence) -> Void) {
let sequence = TuskerNavigationSequence(root: self, animated: animated)
builder(sequence)
sequence.run()
}
}
enum TuskerRoute { enum TuskerRoute {
case timelines case timelines
case notifications case notifications
@ -37,67 +29,6 @@ enum TuskerRoute {
case list(id: String) case list(id: String)
} }
/// A class that manages running a sequence of navigation operations on a ``TuskerRootViewController``.
///
/// Use this type, rather than calling multiple methods on the root VC in a row, because it manages waiting until each previous step finishes.
@MainActor
final class TuskerNavigationSequence {
private let root: any TuskerRootViewController
private let animated: Bool
private var operations = [() -> Void]()
init(root: any TuskerRootViewController, animated: Bool) {
self.root = root
self.animated = animated
}
func select(route: TuskerRoute) {
operations.append {
self.root.select(route: route, animated: self.animated, completion: self.run)
}
}
func push(viewController: UIViewController) {
operations.append {
let nav = self.root.getNavigationController()
nav.pushViewController(viewController, animated: self.animated)
self.run()
}
}
func popToRoot() {
operations.append {
let nav = self.root.getNavigationController()
nav.popToRootViewController(animated: self.animated)
self.run()
}
}
func present(viewController: UIViewController) {
operations.append {
self.root.present(viewController, animated: self.animated, completion: self.run)
}
}
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void) {
operations.append {
block(self.root.getNavigationController().topViewController, self.run)
}
}
func addOperation(_ operation: @escaping (_ completion: @escaping () -> Void) -> Void) {
operations.append {
operation(self.run)
}
}
func run() {
if !operations.isEmpty {
operations.removeFirst()()
}
}
}
@MainActor @MainActor
protocol NavigationControllerProtocol: UIViewController { protocol NavigationControllerProtocol: UIViewController {
var viewControllers: [UIViewController] { get set } var viewControllers: [UIViewController] { get set }

View File

@ -43,9 +43,9 @@ extension NSUserActivity {
} }
@MainActor @MainActor
func handleResume(manager: UserActivityManager) -> Bool { func handleResume(manager: UserActivityManager) async -> Bool {
guard let type = UserActivityType(rawValue: activityType) else { return false } guard let type = UserActivityType(rawValue: activityType) else { return false }
type.handle(manager)(self) await type.handle(manager)(self)
return true return true
} }

View File

@ -16,94 +16,93 @@ import ComposeUI
protocol UserActivityHandlingContext { protocol UserActivityHandlingContext {
var isHandoff: Bool { get } var isHandoff: Bool { get }
func select(route: TuskerRoute) func select(route: TuskerRoute) async
func select(route: TuskerRoute, completion: (() -> Void)?)
func present(_ vc: UIViewController)
var topViewController: UIViewController? { get }
func popToRoot() func popToRoot()
func push(_ vc: UIViewController) func push(_ vc: UIViewController)
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void)
func present(_ vc: UIViewController)
func compose(editing draft: Draft) func compose(editing draft: Draft)
func finalize(activity: NSUserActivity) func finalize(activity: NSUserActivity)
} }
extension UserActivityHandlingContext {
func select(route: TuskerRoute) async {
await withCheckedContinuation { continuation in
select(route: route) {
continuation.resume()
}
}
}
}
struct ActiveAccountUserActivityHandlingContext: UserActivityHandlingContext { struct ActiveAccountUserActivityHandlingContext: UserActivityHandlingContext {
let isHandoff: Bool let isHandoff: Bool
private let root: TuskerRootViewController let root: TuskerRootViewController
private let navigation: TuskerNavigationSequence var navigationDelegate: TuskerNavigationDelegate {
root.getNavigationDelegate()!
init(isHandoff: Bool, root: TuskerRootViewController) {
self.isHandoff = isHandoff
self.root = root
self.navigation = TuskerNavigationSequence(root: root, animated: true)
} }
func select(route: TuskerRoute) { func select(route: TuskerRoute, completion: (() -> Void)?) {
navigation.select(route: route) root.select(route: route, animated: true, completion: completion)
} }
func present(_ vc: UIViewController) { func present(_ vc: UIViewController) {
navigation.present(viewController: vc) navigationDelegate.present(vc, animated: true)
} }
var topViewController: UIViewController? { root.getNavigationController().topViewController }
func popToRoot() { func popToRoot() {
navigation.popToRoot() _ = root.getNavigationController().popToRootViewController(animated: true)
} }
func push(_ vc: UIViewController) { func push(_ vc: UIViewController) {
navigation.push(viewController: vc) navigationDelegate.show(vc, sender: nil)
}
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void) {
navigation.withTopViewController(block)
} }
func compose(editing draft: Draft) { func compose(editing draft: Draft) {
navigation.addOperation { completion in navigationDelegate.compose(editing: draft, animated: true, isDucked: true)
root.compose(editing: draft, animated: true, isDucked: true, completion: completion)
}
} }
func finalize(activity: NSUserActivity) { func finalize(activity: NSUserActivity) {
navigation.run()
} }
} }
class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext { class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
private var state = State.initial private var state = State.initial
private let root: TuskerRootViewController let root: TuskerRootViewController
private let navigation: TuskerNavigationSequence
init(root: TuskerRootViewController) { init(root: TuskerRootViewController) {
self.root = root self.root = root
self.navigation = TuskerNavigationSequence(root: root, animated: false)
} }
var isHandoff: Bool { var isHandoff: Bool { false }
false
func select(route: TuskerRoute, completion: (() -> Void)?) {
root.select(route: route, animated: false) {
self.state = .selectedRoute
completion?()
}
} }
func select(route: TuskerRoute) { var topViewController: UIViewController? { root.getNavigationController().topViewController }
navigation.select(route: route)
state = .selectedRoute
}
func popToRoot() { func popToRoot() {
navigation.popToRoot() // unnecessary during state restoration
} }
func push(_ vc: UIViewController) { func push(_ vc: UIViewController) {
precondition(state >= .selectedRoute) precondition(state >= .selectedRoute)
navigation.push(viewController: vc) root.getNavigationController().pushViewController(vc, animated: false)
state = .pushed state = .pushed
} }
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void) {
navigation.withTopViewController(block)
}
func present(_ vc: UIViewController) { func present(_ vc: UIViewController) {
navigation.present(viewController: vc) root.present(vc, animated: false)
state = .presented state = .presented
} }
@ -121,7 +120,6 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
func finalize(activity: NSUserActivity) { func finalize(activity: NSUserActivity) {
precondition(state > .initial) precondition(state > .initial)
navigation.run()
#if !os(visionOS) #if !os(visionOS)
if #available(iOS 16.0, *), if #available(iOS 16.0, *),
let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) { let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) {

View File

@ -133,16 +133,13 @@ class UserActivityManager {
return activity return activity
} }
func handleCheckNotifications(activity: NSUserActivity) { func handleCheckNotifications(activity: NSUserActivity) async {
context.select(route: .notifications) await context.select(route: .notifications)
context.popToRoot() context.popToRoot()
context.withTopViewController { topViewController, completion in if let notificationsPageController = context.topViewController as? NotificationsPageViewController {
if let notificationsPageController = topViewController as? NotificationsPageViewController {
notificationsPageController.loadViewIfNeeded() notificationsPageController.loadViewIfNeeded()
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode) notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
} }
completion()
}
} }
static func getNotificationsMode(from activity: NSUserActivity) -> NotificationsMode? { static func getNotificationsMode(from activity: NSUserActivity) -> NotificationsMode? {
@ -207,41 +204,32 @@ class UserActivityManager {
return (timeline, positionInfo) return (timeline, positionInfo)
} }
func handleShowTimeline(activity: NSUserActivity) { func handleShowTimeline(activity: NSUserActivity) async {
guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return } guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return }
var timelineVC: TimelineViewController?
if let pinned = PinnedTimeline(timeline: timeline), if let pinned = PinnedTimeline(timeline: timeline),
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) { mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
context.select(route: .timelines) await context.select(route: .timelines)
context.popToRoot() context.popToRoot()
context.withTopViewController { topViewController, completion in let pageController = context.topViewController as! TimelinesPageViewController
let pageController = topViewController as! TimelinesPageViewController
pageController.selectTimeline(pinned, animated: false) pageController.selectTimeline(pinned, animated: false)
} timelineVC = pageController.currentViewController as? TimelineViewController
} else if case .list(let id) = timeline { } else if case .list(let id) = timeline {
context.select(route: .list(id: id)) await context.select(route: .list(id: id))
timelineVC = context.topViewController as? TimelineViewController
} else { } else {
context.select(route: .explore) await context.select(route: .explore)
context.popToRoot() context.popToRoot()
let timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController) timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
context.push(timelineVC) context.push(timelineVC!)
} }
if let positionInfo, if let timelineVC,
let positionInfo,
context.isHandoff { context.isHandoff {
context.withTopViewController { topViewController, completion in
let timelineVC: TimelineViewController
if let topViewController = topViewController as? TimelineViewController {
timelineVC = topViewController
} else if let topViewController = topViewController as? TimelinesPageViewController {
timelineVC = topViewController.currentViewController as! TimelineViewController
} else {
return
}
Task { Task {
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID) await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
completion()
}
} }
} }
} }
@ -261,11 +249,11 @@ class UserActivityManager {
return activity.userInfo?["mainStatusID"] as? String return activity.userInfo?["mainStatusID"] as? String
} }
func handleShowConversation(activity: NSUserActivity) { func handleShowConversation(activity: NSUserActivity) async {
guard let mainStatusID = Self.getConversationStatus(from: activity) else { guard let mainStatusID = Self.getConversationStatus(from: activity) else {
return return
} }
context.select(route: .timelines) await context.select(route: .timelines)
context.push(ConversationViewController(for: mainStatusID, state: .unknown, mastodonController: mastodonController)) context.push(ConversationViewController(for: mainStatusID, state: .unknown, mastodonController: mastodonController))
} }
@ -286,19 +274,18 @@ class UserActivityManager {
return activity.userInfo?["query"] as? String return activity.userInfo?["query"] as? String
} }
func handleSearch(activity: NSUserActivity) { func handleSearch(activity: NSUserActivity) async {
context.select(route: .explore) await context.select(route: .explore)
context.popToRoot() context.popToRoot()
context.withTopViewController { topViewController, completion in
let searchController: UISearchController let searchController: UISearchController
let resultsController: SearchResultsViewController let resultsController: SearchResultsViewController
if let explore = topViewController as? ExploreViewController { if let explore = context.topViewController as? ExploreViewController {
explore.loadViewIfNeeded() explore.loadViewIfNeeded()
explore.searchControllerStatusOnAppearance = true explore.searchControllerStatusOnAppearance = true
searchController = explore.searchController searchController = explore.searchController
resultsController = explore.resultsController resultsController = explore.resultsController
} else if let inlineTrends = topViewController as? InlineTrendsViewController { } else if let inlineTrends = context.topViewController as? InlineTrendsViewController {
inlineTrends.loadViewIfNeeded() inlineTrends.loadViewIfNeeded()
inlineTrends.searchControllerStatusOnAppearance = true inlineTrends.searchControllerStatusOnAppearance = true
searchController = inlineTrends.searchController searchController = inlineTrends.searchController
@ -315,7 +302,6 @@ class UserActivityManager {
searchController.searchBar.becomeFirstResponder() searchController.searchBar.becomeFirstResponder()
} }
} }
}
static func bookmarksActivity(accountID: String) -> NSUserActivity { static func bookmarksActivity(accountID: String) -> NSUserActivity {
let activity = NSUserActivity(type: .bookmarks, accountID: accountID) let activity = NSUserActivity(type: .bookmarks, accountID: accountID)
@ -325,8 +311,8 @@ class UserActivityManager {
return activity return activity
} }
func handleBookmarks(activity: NSUserActivity) { func handleBookmarks(activity: NSUserActivity) async {
context.select(route: .bookmarks) await context.select(route: .bookmarks)
} }
// MARK: - My Profile // MARK: - My Profile
@ -339,8 +325,8 @@ class UserActivityManager {
return activity return activity
} }
func handleMyProfile(activity: NSUserActivity) { func handleMyProfile(activity: NSUserActivity) async {
context.select(route: .myProfile) await context.select(route: .myProfile)
} }
// MARK: - Show Profile // MARK: - Show Profile
@ -358,11 +344,11 @@ class UserActivityManager {
return activity.userInfo?["profileID"] as? String return activity.userInfo?["profileID"] as? String
} }
func handleShowProfile(activity: NSUserActivity) { func handleShowProfile(activity: NSUserActivity) async {
guard let accountID = Self.getProfile(from: activity) else { guard let accountID = Self.getProfile(from: activity) else {
return return
} }
context.select(route: .timelines) await context.select(route: .timelines)
context.push(ProfileViewController(accountID: accountID, mastodonController: mastodonController)) context.push(ProfileViewController(accountID: accountID, mastodonController: mastodonController))
} }
@ -375,11 +361,11 @@ class UserActivityManager {
return activity return activity
} }
func handleShowNotification(activity: NSUserActivity) { func handleShowNotification(activity: NSUserActivity) async {
guard let notificationID = activity.userInfo?["notificationID"] as? String else { guard let notificationID = activity.userInfo?["notificationID"] as? String else {
return return
} }
context.select(route: .notifications) await context.select(route: .notifications)
context.push(NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController)) context.push(NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController))
} }

View File

@ -23,7 +23,7 @@ enum UserActivityType: String {
extension UserActivityType { extension UserActivityType {
@MainActor @MainActor
var handle: (UserActivityManager) -> @MainActor (NSUserActivity) -> Void { var handle: (UserActivityManager) -> @MainActor (NSUserActivity) async -> Void {
switch self { switch self {
case .mainScene: case .mainScene:
fatalError("cannot handle main scene activity") fatalError("cannot handle main scene activity")