Use navigation sequencing for user activity handling
This commit is contained in:
parent
960ba84683
commit
cd8f0e7926
|
@ -42,8 +42,8 @@ enum TuskerRoute {
|
|||
/// 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 {
|
||||
let root: any TuskerRootViewController
|
||||
let animated: Bool
|
||||
private let root: any TuskerRootViewController
|
||||
private let animated: Bool
|
||||
private var operations = [() -> Void]()
|
||||
|
||||
init(root: any TuskerRootViewController, animated: Bool) {
|
||||
|
@ -73,6 +73,24 @@ final class TuskerNavigationSequence {
|
|||
}
|
||||
}
|
||||
|
||||
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()()
|
||||
|
|
|
@ -17,12 +17,11 @@ protocol UserActivityHandlingContext {
|
|||
var isHandoff: Bool { get }
|
||||
|
||||
func select(route: TuskerRoute)
|
||||
func present(_ vc: UIViewController)
|
||||
|
||||
var topViewController: UIViewController? { get }
|
||||
func popToRoot()
|
||||
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 finalize(activity: NSUserActivity)
|
||||
|
@ -30,66 +29,81 @@ protocol UserActivityHandlingContext {
|
|||
|
||||
struct ActiveAccountUserActivityHandlingContext: UserActivityHandlingContext {
|
||||
let isHandoff: Bool
|
||||
let root: TuskerRootViewController
|
||||
var navigationDelegate: TuskerNavigationDelegate {
|
||||
root.getNavigationDelegate()!
|
||||
private let root: TuskerRootViewController
|
||||
private let navigation: TuskerNavigationSequence
|
||||
|
||||
init(isHandoff: Bool, root: TuskerRootViewController) {
|
||||
self.isHandoff = isHandoff
|
||||
self.root = root
|
||||
self.navigation = TuskerNavigationSequence(root: root, animated: true)
|
||||
}
|
||||
|
||||
func select(route: TuskerRoute) {
|
||||
root.select(route: route, animated: true, completion: nil)
|
||||
navigation.select(route: route)
|
||||
}
|
||||
|
||||
func present(_ vc: UIViewController) {
|
||||
navigationDelegate.present(vc, animated: true)
|
||||
navigation.present(viewController: vc)
|
||||
}
|
||||
|
||||
var topViewController: UIViewController? { root.getNavigationController().topViewController }
|
||||
|
||||
func popToRoot() {
|
||||
_ = root.getNavigationController().popToRootViewController(animated: true)
|
||||
navigation.popToRoot()
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
navigation.run()
|
||||
}
|
||||
}
|
||||
|
||||
class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
|
||||
private var state = State.initial
|
||||
let root: TuskerRootViewController
|
||||
private let root: TuskerRootViewController
|
||||
private let navigation: TuskerNavigationSequence
|
||||
|
||||
init(root: TuskerRootViewController) {
|
||||
self.root = root
|
||||
self.navigation = TuskerNavigationSequence(root: root, animated: false)
|
||||
}
|
||||
|
||||
var isHandoff: Bool { false }
|
||||
var isHandoff: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
func select(route: TuskerRoute) {
|
||||
root.select(route: route, animated: false, completion: nil)
|
||||
navigation.select(route: route)
|
||||
state = .selectedRoute
|
||||
}
|
||||
|
||||
var topViewController: UIViewController? { root.getNavigationController().topViewController }
|
||||
|
||||
func popToRoot() {
|
||||
// unnecessary during state restoration
|
||||
navigation.popToRoot()
|
||||
}
|
||||
|
||||
func push(_ vc: UIViewController) {
|
||||
precondition(state >= .selectedRoute)
|
||||
root.getNavigationController().pushViewController(vc, animated: false)
|
||||
navigation.push(viewController: vc)
|
||||
state = .pushed
|
||||
}
|
||||
|
||||
func withTopViewController(_ block: @escaping (_ topViewController: UIViewController?, _ completion: @escaping @MainActor () -> Void) -> Void) {
|
||||
navigation.withTopViewController(block)
|
||||
}
|
||||
|
||||
func present(_ vc: UIViewController) {
|
||||
root.present(vc, animated: false)
|
||||
navigation.present(viewController: vc)
|
||||
state = .presented
|
||||
}
|
||||
|
||||
|
@ -107,6 +121,7 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
|
|||
|
||||
func finalize(activity: NSUserActivity) {
|
||||
precondition(state > .initial)
|
||||
navigation.run()
|
||||
#if !os(visionOS)
|
||||
if #available(iOS 16.0, *),
|
||||
let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) {
|
||||
|
|
|
@ -136,9 +136,12 @@ class UserActivityManager {
|
|||
func handleCheckNotifications(activity: NSUserActivity) {
|
||||
context.select(route: .notifications)
|
||||
context.popToRoot()
|
||||
if let notificationsPageController = context.topViewController as? NotificationsPageViewController {
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
|
||||
context.withTopViewController { topViewController, completion in
|
||||
if let notificationsPageController = topViewController as? NotificationsPageViewController {
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
|
||||
}
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,29 +210,38 @@ class UserActivityManager {
|
|||
func handleShowTimeline(activity: NSUserActivity) {
|
||||
guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return }
|
||||
|
||||
var timelineVC: TimelineViewController?
|
||||
if let pinned = PinnedTimeline(timeline: timeline),
|
||||
mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
|
||||
context.select(route: .timelines)
|
||||
context.popToRoot()
|
||||
let pageController = context.topViewController as! TimelinesPageViewController
|
||||
pageController.selectTimeline(pinned, animated: false)
|
||||
timelineVC = pageController.currentViewController as? TimelineViewController
|
||||
context.withTopViewController { topViewController, completion in
|
||||
let pageController = topViewController as! TimelinesPageViewController
|
||||
pageController.selectTimeline(pinned, animated: false)
|
||||
}
|
||||
} else if case .list(let id) = timeline {
|
||||
context.select(route: .list(id: id))
|
||||
timelineVC = context.topViewController as? TimelineViewController
|
||||
} else {
|
||||
context.select(route: .explore)
|
||||
context.popToRoot()
|
||||
timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
||||
context.push(timelineVC!)
|
||||
let timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
|
||||
context.push(timelineVC)
|
||||
}
|
||||
|
||||
if let timelineVC,
|
||||
let positionInfo,
|
||||
if let positionInfo,
|
||||
context.isHandoff {
|
||||
Task {
|
||||
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
||||
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 {
|
||||
await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -278,28 +290,30 @@ class UserActivityManager {
|
|||
context.select(route: .explore)
|
||||
context.popToRoot()
|
||||
|
||||
let searchController: UISearchController
|
||||
let resultsController: SearchResultsViewController
|
||||
if let explore = context.topViewController as? ExploreViewController {
|
||||
explore.loadViewIfNeeded()
|
||||
explore.searchControllerStatusOnAppearance = true
|
||||
searchController = explore.searchController
|
||||
resultsController = explore.resultsController
|
||||
} else if let inlineTrends = context.topViewController as? InlineTrendsViewController {
|
||||
inlineTrends.loadViewIfNeeded()
|
||||
inlineTrends.searchControllerStatusOnAppearance = true
|
||||
searchController = inlineTrends.searchController
|
||||
resultsController = inlineTrends.resultsController
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if let query = Self.getSearchQuery(from: activity),
|
||||
!query.isEmpty {
|
||||
searchController.searchBar.text = query
|
||||
resultsController.performSearch(query: query)
|
||||
} else {
|
||||
searchController.searchBar.becomeFirstResponder()
|
||||
context.withTopViewController { topViewController, completion in
|
||||
let searchController: UISearchController
|
||||
let resultsController: SearchResultsViewController
|
||||
if let explore = topViewController as? ExploreViewController {
|
||||
explore.loadViewIfNeeded()
|
||||
explore.searchControllerStatusOnAppearance = true
|
||||
searchController = explore.searchController
|
||||
resultsController = explore.resultsController
|
||||
} else if let inlineTrends = topViewController as? InlineTrendsViewController {
|
||||
inlineTrends.loadViewIfNeeded()
|
||||
inlineTrends.searchControllerStatusOnAppearance = true
|
||||
searchController = inlineTrends.searchController
|
||||
resultsController = inlineTrends.resultsController
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if let query = Self.getSearchQuery(from: activity),
|
||||
!query.isEmpty {
|
||||
searchController.searchBar.text = query
|
||||
resultsController.performSearch(query: query)
|
||||
} else {
|
||||
searchController.searchBar.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue