Compare commits

...

3 Commits

Author SHA1 Message Date
Shadowfacts 9e75f41e7b Fix crash when opening push notification while VC modally presented
The dismissal of the modally presented VC turns the route change into an
asynchronous operation, even when not animated.

Closes #484
2024-06-02 10:44:31 -07:00
Shadowfacts 5e55ce75c2 Fix previous sidebar selection losing navigation stack in some circumstances 2024-06-02 10:33:25 -07:00
Shadowfacts eec2adbfd9 Set target content identifiers on scenes/activities 2024-06-02 10:10:16 -07:00
17 changed files with 128 additions and 150 deletions

View File

@ -63,6 +63,7 @@ class NotificationService: UNNotificationServiceExtension {
mutableContent.body = notification.body mutableContent.body = notification.body
mutableContent.userInfo["notificationID"] = notification.notificationID mutableContent.userInfo["notificationID"] = notification.notificationID
mutableContent.userInfo["accountID"] = accountID mutableContent.userInfo["accountID"] = accountID
mutableContent.targetContentIdentifier = accountID
let task = Task { let task = Task {
await updateNotificationContent(mutableContent, account: account, push: notification) await updateNotificationContent(mutableContent, account: account, push: notification)

View File

@ -290,13 +290,19 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
// if the scene is already active, then we animate the account switching if necessary // if the scene is already active, then we animate the account switching if necessary
delegate.activateAccount(account, animated: scene.activationState == .foregroundActive) delegate.activateAccount(account, animated: scene.activationState == .foregroundActive)
rootViewController.select(route: .notifications, animated: false) rootViewController.select(route: .notifications, animated: false) {
let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController) let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController)
rootViewController.getNavigationController().pushViewController(vc, animated: false) rootViewController.getNavigationController().pushViewController(vc, animated: false)
}
} else { } else {
let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID) let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID)
if #available(iOS 17.0, *) {
let request = UISceneSessionActivationRequest(userActivity: activity)
UIApplication.shared.activateSceneSession(for: request)
} else {
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil) UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil)
} }
}
completionHandler() completionHandler()
} }
} }

View File

@ -32,8 +32,9 @@ class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDel
} }
launchActivity = activity launchActivity = activity
let account: UserAccountInfo scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: false)
let account: UserAccountInfo
if let activityAccount = UserActivityManager.getAccount(from: activity) { if let activityAccount = UserActivityManager.getAccount(from: activity) {
account = activityAccount account = activityAccount
} else if let mostRecent = UserAccountsManager.shared.getMostRecentAccount() { } else if let mostRecent = UserAccountsManager.shared.getMostRecentAccount() {

View File

@ -29,6 +29,8 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDeleg
return return
} }
scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: false)
let account: UserAccountInfo let account: UserAccountInfo
let controller: MastodonController let controller: MastodonController
let draft: Draft? let draft: Draft?

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 {
@ -225,6 +229,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
window!.windowScene!.title = account.instanceURL.host! window!.windowScene!.title = account.instanceURL.host!
} }
window!.windowScene!.activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == %@", account.id)
if let container = window?.rootViewController as? AccountSwitchingContainerViewController { if let container = window?.rootViewController as? AccountSwitchingContainerViewController {
let direction: AccountSwitchingContainerViewController.AnimationDirection let direction: AccountSwitchingContainerViewController.AnimationDirection
if animated, if animated,

View File

@ -70,11 +70,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()
} }
@ -150,9 +152,9 @@ extension AccountSwitchingContainerViewController: TuskerRootViewController {
root.compose(editing: draft, animated: animated, isDucked: isDucked) root.compose(editing: draft, animated: animated, isDucked: isDucked)
} }
func select(route: TuskerRoute, animated: Bool) { func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) {
loadViewIfNeeded() loadViewIfNeeded()
root.select(route: route, animated: animated) root.select(route: route, animated: animated, completion: completion)
} }
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? { func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? {

View File

@ -35,8 +35,8 @@ extension DuckableContainerViewController: AccountSwitchableViewController {
(child as! TuskerRootViewController).getNavigationController() (child as! TuskerRootViewController).getNavigationController()
} }
func select(route: TuskerRoute, animated: Bool) { func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) {
(child as? TuskerRootViewController)?.select(route: route, animated: animated) (child as? TuskerRootViewController)?.select(route: route, animated: animated, completion: completion)
} }
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? { func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? {

View File

@ -13,8 +13,8 @@ import Combine
@MainActor @MainActor
protocol MainSidebarViewControllerDelegate: AnyObject { protocol MainSidebarViewControllerDelegate: AnyObject {
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController) func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item) func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item, previousItem: MainSidebarViewController.Item?)
func sidebar(_ sidebarViewController: MainSidebarViewController, showViewController viewController: UIViewController) func sidebar(_ sidebarViewController: MainSidebarViewController, showViewController viewController: UIViewController, previousItem: MainSidebarViewController.Item?)
func sidebar(_ sidebarViewController: MainSidebarViewController, scrollToTopFor item: MainSidebarViewController.Item) func sidebar(_ sidebarViewController: MainSidebarViewController, scrollToTopFor item: MainSidebarViewController.Item)
} }
@ -57,7 +57,7 @@ class MainSidebarViewController: UIViewController {
return items return items
} }
private(set) var previouslySelectedItem: Item? private var previouslySelectedItem: Item?
var selectedItem: Item? { var selectedItem: Item? {
guard let indexPath = collectionView?.indexPathsForSelectedItems?.first else { guard let indexPath = collectionView?.indexPathsForSelectedItems?.first else {
return nil return nil
@ -261,19 +261,21 @@ class MainSidebarViewController: UIViewController {
} }
private func returnToPreviousItem() { private func returnToPreviousItem() {
let item = previouslySelectedItem ?? .tab(.timelines) let oldItem = selectedItem
let newItem = previouslySelectedItem ?? .tab(.timelines)
previouslySelectedItem = nil previouslySelectedItem = nil
select(item: item, animated: true) select(item: newItem, animated: true)
sidebarDelegate?.sidebar(self, didSelectItem: item) sidebarDelegate?.sidebar(self, didSelectItem: newItem, previousItem: oldItem)
} }
private func showAddList() { private func showAddList() {
let service = CreateListService(mastodonController: mastodonController, present: { self.present($0, animated: true let service = CreateListService(mastodonController: mastodonController, present: { self.present($0, animated: true
) }) { list in ) }) { list in
let oldItem = self.selectedItem
self.select(item: .list(list), animated: false) self.select(item: .list(list), animated: false)
let list = ListTimelineViewController(for: list, mastodonController: self.mastodonController) let list = ListTimelineViewController(for: list, mastodonController: self.mastodonController)
list.presentEditOnAppear = true list.presentEditOnAppear = true
self.sidebarDelegate?.sidebar(self, showViewController: list) self.sidebarDelegate?.sidebar(self, showViewController: list, previousItem: oldItem)
} }
service.run() service.run()
} }
@ -471,7 +473,7 @@ extension MainSidebarViewController: UICollectionViewDelegate {
fatalError("unreachable") fatalError("unreachable")
} }
} else { } else {
sidebarDelegate?.sidebar(self, didSelectItem: item) sidebarDelegate?.sidebar(self, didSelectItem: item, previousItem: previouslySelectedItem)
} }
} }
@ -540,8 +542,9 @@ extension MainSidebarViewController: UICollectionViewDragDelegate {
extension MainSidebarViewController: InstanceTimelineViewControllerDelegate { extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
func didSaveInstance(url: URL) { func didSaveInstance(url: URL) {
dismiss(animated: true) { dismiss(animated: true) {
let oldItem = self.selectedItem
self.select(item: .savedInstance(url), animated: true) self.select(item: .savedInstance(url), animated: true)
self.sidebarDelegate?.sidebar(self, didSelectItem: .savedInstance(url)) self.sidebarDelegate?.sidebar(self, didSelectItem: .savedInstance(url), previousItem: oldItem)
} }
} }

View File

@ -86,7 +86,7 @@ class MainSplitViewController: UISplitViewController {
// don't unnecesarily construct a content VC unless the we're in actually split mode // don't unnecesarily construct a content VC unless the we're in actually split mode
// when we change from compact -> split for the first time, the VC will be transferred anyways // when we change from compact -> split for the first time, the VC will be transferred anyways
if traitCollection.horizontalSizeClass != .compact { if traitCollection.horizontalSizeClass != .compact {
select(item: .tab(.timelines)) doSelect(item: .tab(.timelines))
} }
if UIDevice.current.userInterfaceIdiom != .mac { if UIDevice.current.userInterfaceIdiom != .mac {
@ -149,7 +149,15 @@ class MainSplitViewController: UISplitViewController {
self.setViewController(newNav, for: .secondary) self.setViewController(newNav, for: .secondary)
} }
func select(item: MainSidebarViewController.Item) { private func select(newItem: MainSidebarViewController.Item, oldItem: MainSidebarViewController.Item?) {
if let oldItem,
newItem != oldItem {
navigationStacks[oldItem] = secondaryNavController.viewControllers
}
doSelect(item: newItem)
}
private func doSelect(item: MainSidebarViewController.Item) {
secondaryNavController.viewControllers = getOrCreateNavigationStack(item: item) secondaryNavController.viewControllers = getOrCreateNavigationStack(item: item)
} }
@ -180,43 +188,28 @@ class MainSplitViewController: UISplitViewController {
} }
@objc func handleSidebarCommandTimelines() { @objc func handleSidebarCommandTimelines() {
if let previous = sidebar.selectedItem {
navigationStacks[previous] = secondaryNavController.viewControllers
}
sidebar.select(item: .tab(.timelines), animated: false) sidebar.select(item: .tab(.timelines), animated: false)
select(item: .tab(.timelines)) select(newItem: .tab(.timelines), oldItem: sidebar.selectedItem)
} }
@objc func handleSidebarCommandNotifications() { @objc func handleSidebarCommandNotifications() {
if let previous = sidebar.selectedItem {
navigationStacks[previous] = secondaryNavController.viewControllers
}
sidebar.select(item: .tab(.notifications), animated: false) sidebar.select(item: .tab(.notifications), animated: false)
select(item: .tab(.notifications)) select(newItem: .tab(.notifications), oldItem: sidebar.selectedItem)
} }
@objc func handleSidebarCommandExplore() { @objc func handleSidebarCommandExplore() {
if let previous = sidebar.selectedItem {
navigationStacks[previous] = secondaryNavController.viewControllers
}
sidebar.select(item: .tab(.explore), animated: false) sidebar.select(item: .tab(.explore), animated: false)
select(item: .tab(.explore)) select(newItem: .tab(.explore), oldItem: sidebar.selectedItem)
} }
@objc func handleSidebarCommandBookmarks() { @objc func handleSidebarCommandBookmarks() {
if let previous = sidebar.selectedItem {
navigationStacks[previous] = secondaryNavController.viewControllers
}
sidebar.select(item: .bookmarks, animated: false) sidebar.select(item: .bookmarks, animated: false)
select(item: .bookmarks) select(newItem: .bookmarks, oldItem: sidebar.selectedItem)
} }
@objc func handleSidebarCommandMyProfile() { @objc func handleSidebarCommandMyProfile() {
if let previous = sidebar.selectedItem {
navigationStacks[previous] = secondaryNavController.viewControllers
}
sidebar.select(item: .tab(.myProfile), animated: false) sidebar.select(item: .tab(.myProfile), animated: false)
select(item: .tab(.myProfile)) select(newItem: .tab(.myProfile), oldItem: sidebar.selectedItem)
} }
@objc private func sidebarTapped() { @objc private func sidebarTapped() {
@ -459,12 +452,12 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
// These tabs map 1 <-> 1 with sidebar items // These tabs map 1 <-> 1 with sidebar items
let item = MainSidebarViewController.Item.tab(tabBarViewController.selectedTab) let item = MainSidebarViewController.Item.tab(tabBarViewController.selectedTab)
sidebar.select(item: item, animated: false) sidebar.select(item: item, animated: false)
select(item: item) doSelect(item: item)
case .explore: case .explore:
// If the explore tab is active, the sidebar item is determined above when transferring the explore VC's nav stack // If the explore tab is active, the sidebar item is determined above when transferring the explore VC's nav stack
sidebar.select(item: exploreItem!, animated: false) sidebar.select(item: exploreItem!, animated: false)
select(item: exploreItem!) doSelect(item: exploreItem!)
default: default:
return return
@ -489,16 +482,13 @@ extension MainSplitViewController: MainSidebarViewControllerDelegate {
compose(editing: nil) compose(editing: nil)
} }
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item) { func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item, previousItem: MainSidebarViewController.Item?) {
if let previous = sidebar.previouslySelectedItem { select(newItem: item, oldItem: previousItem)
navigationStacks[previous] = secondaryNavController.viewControllers
}
select(item: item)
} }
func sidebar(_ sidebarViewController: MainSidebarViewController, showViewController viewController: UIViewController) { func sidebar(_ sidebarViewController: MainSidebarViewController, showViewController viewController: UIViewController, previousItem: MainSidebarViewController.Item?) {
if let previous = sidebar.previouslySelectedItem { if let previousItem {
navigationStacks[previous] = secondaryNavController.viewControllers navigationStacks[previousItem] = secondaryNavController.viewControllers
} }
secondaryNavController.viewControllers = [viewController] secondaryNavController.viewControllers = [viewController]
} }
@ -552,14 +542,14 @@ extension MainSplitViewController: StateRestorableViewController {
} }
extension MainSplitViewController: TuskerRootViewController { extension MainSplitViewController: TuskerRootViewController {
func select(route: TuskerRoute, animated: Bool) { func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) {
guard traitCollection.horizontalSizeClass != .compact else { guard traitCollection.horizontalSizeClass != .compact else {
tabBarViewController?.select(route: route, animated: animated) tabBarViewController?.select(route: route, animated: animated, completion: completion)
return return
} }
guard presentedViewController == nil else { guard presentedViewController == nil else {
dismiss(animated: animated) { dismiss(animated: animated) {
self.select(route: route, animated: animated) self.select(route: route, animated: animated, completion: completion)
} }
return return
} }
@ -582,8 +572,10 @@ extension MainSplitViewController: TuskerRootViewController {
return return
} }
} }
let oldItem = sidebar.selectedItem
sidebar.select(item: item, animated: false) sidebar.select(item: item, animated: false)
select(item: item) select(newItem: item, oldItem: oldItem)
completion?()
} }
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? { func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? {
@ -625,7 +617,7 @@ extension MainSplitViewController: TuskerRootViewController {
} }
if sidebar.selectedItem != .explore { if sidebar.selectedItem != .explore {
select(item: .explore) select(newItem: .explore, oldItem: sidebar.selectedItem)
} }
guard let searchViewController = secondaryNavController.viewControllers.first as? InlineTrendsViewController else { guard let searchViewController = secondaryNavController.viewControllers.first as? InlineTrendsViewController else {

View File

@ -289,7 +289,7 @@ extension MainTabBarViewController: StateRestorableViewController {
} }
extension MainTabBarViewController: TuskerRootViewController { extension MainTabBarViewController: TuskerRootViewController {
func select(route: TuskerRoute, animated: Bool) { func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?) {
switch route { switch route {
case .timelines: case .timelines:
select(tab: .timelines, dismissPresented: true) select(tab: .timelines, dismissPresented: true)
@ -310,6 +310,7 @@ extension MainTabBarViewController: TuskerRootViewController {
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? {

View File

@ -12,7 +12,7 @@ import ComposeUI
@MainActor @MainActor
protocol TuskerRootViewController: UIViewController, StateRestorableViewController, StatusBarTappableViewController { protocol TuskerRootViewController: UIViewController, StateRestorableViewController, StatusBarTappableViewController {
func compose(editing draft: Draft?, animated: Bool, isDucked: Bool) func compose(editing draft: Draft?, animated: Bool, isDucked: Bool)
func select(route: TuskerRoute, animated: Bool) func select(route: TuskerRoute, animated: Bool, completion: (() -> Void)?)
func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController?
func getNavigationDelegate() -> TuskerNavigationDelegate? func getNavigationDelegate() -> TuskerNavigationDelegate?
func getNavigationController() -> NavigationControllerProtocol func getNavigationController() -> NavigationControllerProtocol
@ -21,33 +21,6 @@ protocol TuskerRootViewController: UIViewController, StateRestorableViewControll
func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController?
} }
//extension TuskerRootViewController {
// func select(route: NewRoute, animated: Bool) {
// doApply(components: route.components, animated: animated)
// }
//
// private func doApply(components: ArraySlice<RouteComponent>, animated: Bool) {
// guard let first = components.first else {
// return
// }
// doApply(component: first, animated: animated) {
// self.doApply(components: components.dropFirst(), animated: animated)
// }
// }
//
// private func doApply(component: RouteComponent, animated: Bool, completion: @escaping () -> Void) {
// switch component {
// case .topLevelItem(let rootRoute):
// select(route: rootRoute)
// completion()
// case .popToRoot:
// _ = getNavigationController().popToRootViewController(animated: animated)
// completion()
// case .push(<#T##(MastodonController) -> UIViewController#>)
// }
// }
//}
enum TuskerRoute { enum TuskerRoute {
case timelines case timelines
case notifications case notifications
@ -57,33 +30,6 @@ enum TuskerRoute {
case list(id: String) case list(id: String)
} }
//struct NewRoute: ExpressibleByArrayLiteral {
// let components: [RouteComponent]
//
// init(arrayLiteral elements: RouteComponent...) {
// self.components = elements
// }
//
// static var timelines: Self { [.topLevelItem(.timelines)] }
// static var explore: Self { [.topLevelItem(.explore)] }
// static var myProfile: Self { [.topLevelItem(.myProfile)] }
// static var bookmarks: Self { [.topLevelItem(.explore), .push({ BookmarksViewController(mastodonController: $0) })] }
// static func profile(accountID: String) -> Self { [.topLevelItem(.timelines), .push({ ProfileViewController(accountID: accountID, mastodonController: $0) })] }
//}
//
//enum RouteComponent {
// case topLevelItem(RootRoute)
// case popToRoot
// case push((MastodonController) -> UIViewController)
// case present(UIViewController)
//}
//
//enum RootRoute {
// case timelines
// case explore
// case myProfile
//}
//
@MainActor @MainActor
protocol NavigationControllerProtocol: UIViewController { protocol NavigationControllerProtocol: UIViewController {
var viewControllers: [UIViewController] { get set } var viewControllers: [UIViewController] { get set }

View File

@ -94,10 +94,14 @@ class NotificationLoadingViewController: UIViewController {
showLoadingError(Error.unknownType) showLoadingError(Error.unknownType)
return return
} }
guard let navigationController else { // guard let navigationController else {
fatalError("Don't know how to show notification VC outside of navigation controller") // fatalError("Don't know how to show notification VC outside of navigation controller")
} // }
if let navigationController {
navigationController.viewControllers[navigationController.viewControllers.count - 1] = vc navigationController.viewControllers[navigationController.viewControllers.count - 1] = vc
} else {
print("AAAAAAAAAA")
}
} }
private func showLoadingError(_ error: any Swift.Error) { private func showLoadingError(_ error: any Swift.Error) {

View File

@ -44,9 +44,9 @@ enum AppShortcutItem: String, CaseIterable {
} }
switch self { switch self {
case .showHomeTimeline: case .showHomeTimeline:
root.select(route: .timelines, animated: false) root.select(route: .timelines, animated: false, completion: nil)
case .showNotifications: case .showNotifications:
root.select(route: .notifications, animated: false) root.select(route: .notifications, animated: false, completion: nil)
case .composePost: case .composePost:
root.compose(editing: nil, animated: false, isDucked: false) root.compose(editing: nil, animated: false, isDucked: false)
} }

View File

@ -39,12 +39,13 @@ extension NSUserActivity {
self.userInfo = [ self.userInfo = [
"accountID": accountID "accountID": accountID
] ]
self.targetContentIdentifier = accountID
} }
@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,7 +16,8 @@ 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) func present(_ vc: UIViewController)
var topViewController: UIViewController? { get } var topViewController: UIViewController? { get }
@ -28,6 +29,16 @@ protocol UserActivityHandlingContext {
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
let root: TuskerRootViewController let root: TuskerRootViewController
@ -35,8 +46,8 @@ struct ActiveAccountUserActivityHandlingContext: UserActivityHandlingContext {
root.getNavigationDelegate()! root.getNavigationDelegate()!
} }
func select(route: TuskerRoute) { func select(route: TuskerRoute, completion: (() -> Void)?) {
root.select(route: route, animated: true) root.select(route: route, animated: true, completion: completion)
} }
func present(_ vc: UIViewController) { func present(_ vc: UIViewController) {
@ -71,9 +82,11 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
var isHandoff: Bool { false } var isHandoff: Bool { false }
func select(route: TuskerRoute) { func select(route: TuskerRoute, completion: (() -> Void)?) {
root.select(route: route, animated: false) root.select(route: route, animated: false) {
state = .selectedRoute self.state = .selectedRoute
completion?()
}
} }
var topViewController: UIViewController? { root.getNavigationController().topViewController } var topViewController: UIViewController? { root.getNavigationController().topViewController }

View File

@ -133,8 +133,8 @@ 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()
if let notificationsPageController = context.topViewController as? NotificationsPageViewController { if let notificationsPageController = context.topViewController as? NotificationsPageViewController {
notificationsPageController.loadViewIfNeeded() notificationsPageController.loadViewIfNeeded()
@ -204,22 +204,22 @@ 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? 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()
let pageController = context.topViewController as! TimelinesPageViewController let pageController = context.topViewController as! TimelinesPageViewController
pageController.selectTimeline(pinned, animated: false) pageController.selectTimeline(pinned, animated: false)
timelineVC = pageController.currentViewController as? TimelineViewController 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 timelineVC = context.topViewController as? TimelineViewController
} else { } else {
context.select(route: .explore) await context.select(route: .explore)
context.popToRoot() context.popToRoot()
timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController) timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
context.push(timelineVC!) context.push(timelineVC!)
@ -249,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))
} }
@ -274,8 +274,8 @@ 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()
let searchController: UISearchController let searchController: UISearchController
@ -311,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
@ -325,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
@ -344,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))
} }
@ -361,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")