Compare commits
3 Commits
b663335c6d
...
cd8f0e7926
Author | SHA1 | Date |
---|---|---|
Shadowfacts | cd8f0e7926 | |
Shadowfacts | 960ba84683 | |
Shadowfacts | 2eead1f9de |
|
@ -292,12 +292,15 @@ 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 the account switching if necessary
|
// if the scene is already active, then we animate things
|
||||||
delegate.activateAccount(account, animated: scene.activationState == .foregroundActive)
|
let animated = scene.activationState == .foregroundActive
|
||||||
|
|
||||||
rootViewController.select(route: .notifications, animated: false) {
|
delegate.activateAccount(account, animated: animated)
|
||||||
|
|
||||||
|
rootViewController.runNavigation(animated: animated) { navigation in
|
||||||
|
navigation.select(route: .notifications)
|
||||||
let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController)
|
let vc = NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController)
|
||||||
rootViewController.getNavigationController().pushViewController(vc, animated: false)
|
navigation.push(viewController: vc)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID)
|
let activity = UserActivityManager.showNotificationActivity(id: notificationID, accountID: accountID)
|
||||||
|
|
|
@ -83,9 +83,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
} else {
|
} else {
|
||||||
context = ActiveAccountUserActivityHandlingContext(isHandoff: true, root: rootViewController!)
|
context = ActiveAccountUserActivityHandlingContext(isHandoff: true, root: rootViewController!)
|
||||||
}
|
}
|
||||||
Task(priority: .userInitiated) {
|
_ = userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene, context: context))
|
||||||
_ = 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) {
|
||||||
|
@ -193,10 +191,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
|
|
||||||
if let activity = launchActivity {
|
if let activity = launchActivity {
|
||||||
func doRestoreActivity(context: UserActivityHandlingContext) {
|
func doRestoreActivity(context: UserActivityHandlingContext) {
|
||||||
Task(priority: .userInitiated) {
|
_ = activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!, context: context))
|
||||||
_ = 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!))
|
||||||
|
|
|
@ -79,12 +79,10 @@ 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()
|
||||||
|
|
|
@ -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)
|
tabBarViewController.select(tab: .explore, dismissPresented: false, animated: 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)
|
tabBarViewController.select(tab: tab, dismissPresented: false, animated: false)
|
||||||
|
|
||||||
case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_):
|
case .bookmarks, .favorites, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||||
tabBarViewController.select(tab: .explore, dismissPresented: false)
|
tabBarViewController.select(tab: .explore, dismissPresented: false, animated: 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
|
||||||
|
|
|
@ -54,19 +54,22 @@ class MainTabBarViewController: BaseMainTabBarViewController {
|
||||||
view.backgroundColor = .appBackground
|
view.backgroundColor = .appBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
func select(tab: Tab, dismissPresented: Bool) {
|
func select(tab: Tab, dismissPresented: Bool, animated: Bool, completion: (() -> Void)? = nil) {
|
||||||
if tab == .compose {
|
if tab == .compose {
|
||||||
compose(editing: nil)
|
compose(editing: nil, completion: completion)
|
||||||
} 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: true) {
|
dismiss(animated: animated) {
|
||||||
|
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?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,25 +154,24 @@ 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)
|
select(tab: .timelines, dismissPresented: true, animated: animated, completion: completion)
|
||||||
case .notifications:
|
case .notifications:
|
||||||
select(tab: .notifications, dismissPresented: true)
|
select(tab: .notifications, dismissPresented: true, animated: animated, completion: completion)
|
||||||
case .myProfile:
|
case .myProfile:
|
||||||
select(tab: .myProfile, dismissPresented: true)
|
select(tab: .myProfile, dismissPresented: true, animated: animated, completion: completion)
|
||||||
case .explore:
|
case .explore:
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion)
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion)
|
||||||
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)
|
select(tab: .explore, dismissPresented: true, animated: animated, completion: completion)
|
||||||
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? {
|
||||||
|
@ -186,7 +188,7 @@ extension MainTabBarViewController: TuskerRootViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
select(tab: .explore, dismissPresented: true)
|
select(tab: .explore, dismissPresented: true, animated: false)
|
||||||
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
|
||||||
|
|
|
@ -20,6 +20,14 @@ 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
|
||||||
|
@ -29,6 +37,67 @@ 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 }
|
||||||
|
|
|
@ -43,9 +43,9 @@ extension NSUserActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func handleResume(manager: UserActivityManager) async -> Bool {
|
func handleResume(manager: UserActivityManager) -> Bool {
|
||||||
guard let type = UserActivityType(rawValue: activityType) else { return false }
|
guard let type = UserActivityType(rawValue: activityType) else { return false }
|
||||||
await type.handle(manager)(self)
|
type.handle(manager)(self)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,93 +16,94 @@ import ComposeUI
|
||||||
protocol UserActivityHandlingContext {
|
protocol UserActivityHandlingContext {
|
||||||
var isHandoff: Bool { get }
|
var isHandoff: Bool { get }
|
||||||
|
|
||||||
func select(route: TuskerRoute) async
|
func select(route: TuskerRoute)
|
||||||
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
|
||||||
let root: TuskerRootViewController
|
private let root: TuskerRootViewController
|
||||||
var navigationDelegate: TuskerNavigationDelegate {
|
private let navigation: TuskerNavigationSequence
|
||||||
root.getNavigationDelegate()!
|
|
||||||
|
init(isHandoff: Bool, root: TuskerRootViewController) {
|
||||||
|
self.isHandoff = isHandoff
|
||||||
|
self.root = root
|
||||||
|
self.navigation = TuskerNavigationSequence(root: root, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func select(route: TuskerRoute, completion: (() -> Void)?) {
|
func select(route: TuskerRoute) {
|
||||||
root.select(route: route, animated: true, completion: completion)
|
navigation.select(route: route)
|
||||||
}
|
}
|
||||||
|
|
||||||
func present(_ vc: UIViewController) {
|
func present(_ vc: UIViewController) {
|
||||||
navigationDelegate.present(vc, animated: true)
|
navigation.present(viewController: vc)
|
||||||
}
|
}
|
||||||
|
|
||||||
var topViewController: UIViewController? { root.getNavigationController().topViewController }
|
|
||||||
|
|
||||||
func popToRoot() {
|
func popToRoot() {
|
||||||
_ = root.getNavigationController().popToRootViewController(animated: true)
|
navigation.popToRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func push(_ vc: UIViewController) {
|
func push(_ vc: UIViewController) {
|
||||||
navigationDelegate.show(vc, sender: nil)
|
navigation.push(viewController: vc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void) {
|
||||||
|
navigation.withTopViewController(block)
|
||||||
|
}
|
||||||
|
|
||||||
func compose(editing draft: Draft) {
|
func compose(editing draft: Draft) {
|
||||||
navigationDelegate.compose(editing: draft, animated: true, isDucked: true)
|
navigation.addOperation { completion in
|
||||||
|
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
|
||||||
let root: TuskerRootViewController
|
private 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 { false }
|
var isHandoff: Bool {
|
||||||
|
false
|
||||||
func select(route: TuskerRoute, completion: (() -> Void)?) {
|
|
||||||
root.select(route: route, animated: false) {
|
|
||||||
self.state = .selectedRoute
|
|
||||||
completion?()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var topViewController: UIViewController? { root.getNavigationController().topViewController }
|
func select(route: TuskerRoute) {
|
||||||
|
navigation.select(route: route)
|
||||||
|
state = .selectedRoute
|
||||||
|
}
|
||||||
|
|
||||||
func popToRoot() {
|
func popToRoot() {
|
||||||
// unnecessary during state restoration
|
navigation.popToRoot()
|
||||||
}
|
}
|
||||||
|
|
||||||
func push(_ vc: UIViewController) {
|
func push(_ vc: UIViewController) {
|
||||||
precondition(state >= .selectedRoute)
|
precondition(state >= .selectedRoute)
|
||||||
root.getNavigationController().pushViewController(vc, animated: false)
|
navigation.push(viewController: vc)
|
||||||
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) {
|
||||||
root.present(vc, animated: false)
|
navigation.present(viewController: vc)
|
||||||
state = .presented
|
state = .presented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +121,7 @@ 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) {
|
||||||
|
|
|
@ -133,12 +133,15 @@ class UserActivityManager {
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCheckNotifications(activity: NSUserActivity) async {
|
func handleCheckNotifications(activity: NSUserActivity) {
|
||||||
await context.select(route: .notifications)
|
context.select(route: .notifications)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
if let notificationsPageController = context.topViewController as? NotificationsPageViewController {
|
context.withTopViewController { topViewController, completion in
|
||||||
notificationsPageController.loadViewIfNeeded()
|
if let notificationsPageController = topViewController as? NotificationsPageViewController {
|
||||||
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
|
notificationsPageController.loadViewIfNeeded()
|
||||||
|
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
|
||||||
|
}
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,32 +207,41 @@ class UserActivityManager {
|
||||||
return (timeline, positionInfo)
|
return (timeline, positionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleShowTimeline(activity: NSUserActivity) async {
|
func handleShowTimeline(activity: NSUserActivity) {
|
||||||
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) {
|
||||||
await context.select(route: .timelines)
|
context.select(route: .timelines)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
let pageController = context.topViewController as! TimelinesPageViewController
|
context.withTopViewController { topViewController, completion in
|
||||||
pageController.selectTimeline(pinned, animated: false)
|
let pageController = topViewController as! TimelinesPageViewController
|
||||||
timelineVC = pageController.currentViewController as? TimelineViewController
|
pageController.selectTimeline(pinned, animated: false)
|
||||||
|
}
|
||||||
} else if case .list(let id) = timeline {
|
} else if case .list(let id) = timeline {
|
||||||
await context.select(route: .list(id: id))
|
context.select(route: .list(id: id))
|
||||||
timelineVC = context.topViewController as? TimelineViewController
|
|
||||||
} else {
|
} else {
|
||||||
await context.select(route: .explore)
|
context.select(route: .explore)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
let timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
||||||
context.push(timelineVC!)
|
context.push(timelineVC)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let timelineVC,
|
if let positionInfo,
|
||||||
let positionInfo,
|
|
||||||
context.isHandoff {
|
context.isHandoff {
|
||||||
Task {
|
context.withTopViewController { topViewController, completion in
|
||||||
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
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 {
|
||||||
|
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,11 +261,11 @@ class UserActivityManager {
|
||||||
return activity.userInfo?["mainStatusID"] as? String
|
return activity.userInfo?["mainStatusID"] as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleShowConversation(activity: NSUserActivity) async {
|
func handleShowConversation(activity: NSUserActivity) {
|
||||||
guard let mainStatusID = Self.getConversationStatus(from: activity) else {
|
guard let mainStatusID = Self.getConversationStatus(from: activity) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await context.select(route: .timelines)
|
context.select(route: .timelines)
|
||||||
context.push(ConversationViewController(for: mainStatusID, state: .unknown, mastodonController: mastodonController))
|
context.push(ConversationViewController(for: mainStatusID, state: .unknown, mastodonController: mastodonController))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,32 +286,34 @@ class UserActivityManager {
|
||||||
return activity.userInfo?["query"] as? String
|
return activity.userInfo?["query"] as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSearch(activity: NSUserActivity) async {
|
func handleSearch(activity: NSUserActivity) {
|
||||||
await context.select(route: .explore)
|
context.select(route: .explore)
|
||||||
context.popToRoot()
|
context.popToRoot()
|
||||||
|
|
||||||
let searchController: UISearchController
|
context.withTopViewController { topViewController, completion in
|
||||||
let resultsController: SearchResultsViewController
|
let searchController: UISearchController
|
||||||
if let explore = context.topViewController as? ExploreViewController {
|
let resultsController: SearchResultsViewController
|
||||||
explore.loadViewIfNeeded()
|
if let explore = topViewController as? ExploreViewController {
|
||||||
explore.searchControllerStatusOnAppearance = true
|
explore.loadViewIfNeeded()
|
||||||
searchController = explore.searchController
|
explore.searchControllerStatusOnAppearance = true
|
||||||
resultsController = explore.resultsController
|
searchController = explore.searchController
|
||||||
} else if let inlineTrends = context.topViewController as? InlineTrendsViewController {
|
resultsController = explore.resultsController
|
||||||
inlineTrends.loadViewIfNeeded()
|
} else if let inlineTrends = topViewController as? InlineTrendsViewController {
|
||||||
inlineTrends.searchControllerStatusOnAppearance = true
|
inlineTrends.loadViewIfNeeded()
|
||||||
searchController = inlineTrends.searchController
|
inlineTrends.searchControllerStatusOnAppearance = true
|
||||||
resultsController = inlineTrends.resultsController
|
searchController = inlineTrends.searchController
|
||||||
} else {
|
resultsController = inlineTrends.resultsController
|
||||||
return
|
} else {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
if let query = Self.getSearchQuery(from: activity),
|
|
||||||
!query.isEmpty {
|
if let query = Self.getSearchQuery(from: activity),
|
||||||
searchController.searchBar.text = query
|
!query.isEmpty {
|
||||||
resultsController.performSearch(query: query)
|
searchController.searchBar.text = query
|
||||||
} else {
|
resultsController.performSearch(query: query)
|
||||||
searchController.searchBar.becomeFirstResponder()
|
} else {
|
||||||
|
searchController.searchBar.becomeFirstResponder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,8 +325,8 @@ class UserActivityManager {
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBookmarks(activity: NSUserActivity) async {
|
func handleBookmarks(activity: NSUserActivity) {
|
||||||
await context.select(route: .bookmarks)
|
context.select(route: .bookmarks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - My Profile
|
// MARK: - My Profile
|
||||||
|
@ -325,8 +339,8 @@ class UserActivityManager {
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMyProfile(activity: NSUserActivity) async {
|
func handleMyProfile(activity: NSUserActivity) {
|
||||||
await context.select(route: .myProfile)
|
context.select(route: .myProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Show Profile
|
// MARK: - Show Profile
|
||||||
|
@ -344,11 +358,11 @@ class UserActivityManager {
|
||||||
return activity.userInfo?["profileID"] as? String
|
return activity.userInfo?["profileID"] as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleShowProfile(activity: NSUserActivity) async {
|
func handleShowProfile(activity: NSUserActivity) {
|
||||||
guard let accountID = Self.getProfile(from: activity) else {
|
guard let accountID = Self.getProfile(from: activity) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await context.select(route: .timelines)
|
context.select(route: .timelines)
|
||||||
context.push(ProfileViewController(accountID: accountID, mastodonController: mastodonController))
|
context.push(ProfileViewController(accountID: accountID, mastodonController: mastodonController))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,11 +375,11 @@ class UserActivityManager {
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleShowNotification(activity: NSUserActivity) async {
|
func handleShowNotification(activity: NSUserActivity) {
|
||||||
guard let notificationID = activity.userInfo?["notificationID"] as? String else {
|
guard let notificationID = activity.userInfo?["notificationID"] as? String else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await context.select(route: .notifications)
|
context.select(route: .notifications)
|
||||||
context.push(NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController))
|
context.push(NotificationLoadingViewController(notificationID: notificationID, mastodonController: mastodonController))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ enum UserActivityType: String {
|
||||||
|
|
||||||
extension UserActivityType {
|
extension UserActivityType {
|
||||||
@MainActor
|
@MainActor
|
||||||
var handle: (UserActivityManager) -> @MainActor (NSUserActivity) async -> Void {
|
var handle: (UserActivityManager) -> @MainActor (NSUserActivity) -> Void {
|
||||||
switch self {
|
switch self {
|
||||||
case .mainScene:
|
case .mainScene:
|
||||||
fatalError("cannot handle main scene activity")
|
fatalError("cannot handle main scene activity")
|
||||||
|
|
Loading…
Reference in New Issue