From 75f290ae8f5a1b87800ea35cfa30a9426c90383e Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 22 Feb 2023 21:38:12 -0500 Subject: [PATCH] Tab state restoration Closes #32 --- Tusker.xcodeproj/project.pbxproj | 4 + .../ConversationViewController.swift | 15 ++++ .../Explore/ExploreViewController.swift | 30 +++++++- .../BookmarksViewController.swift | 11 +++ .../Main/MainSidebarViewController.swift | 6 +- .../Main/MainSplitViewController.swift | 74 ++++++++++++++----- .../Main/MainTabBarViewController.swift | 64 ++++++++++++---- .../Main/TuskerRootViewController.swift | 4 +- .../NotificationsPageViewController.swift | 13 +++- .../Profile/MyProfileViewController.swift | 6 +- .../Profile/ProfileViewController.swift | 16 +++- .../Search/SearchResultsViewController.swift | 2 +- .../TimelinesPageViewController.swift | 34 +++++---- .../SegmentedPageViewController.swift | 2 +- .../StateRestorableViewController.swift | 15 ++++ Tusker/Shortcuts/UserActivityManager.swift | 29 +++++++- 16 files changed, 260 insertions(+), 65 deletions(-) create mode 100644 Tusker/Screens/Utilities/StateRestorableViewController.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 5b72ab1f..a6130518 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -221,6 +221,7 @@ D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; }; D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; }; D691771129A2B76A0054D7EF /* MainActor+Unsafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */; }; + D691771529A6FCAB0054D7EF /* StateRestorableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */; }; D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; }; D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; }; D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; }; @@ -637,6 +638,7 @@ D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = ""; }; D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = ""; }; D691771029A2B76A0054D7EF /* MainActor+Unsafe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainActor+Unsafe.swift"; sourceTree = ""; }; + D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRestorableViewController.swift; sourceTree = ""; }; D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = ""; }; D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = ""; }; D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = ""; }; @@ -1495,6 +1497,7 @@ D6E0DC8D216EDF1E00369478 /* Previewing.swift */, D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */, D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */, + D691771429A6FCAB0054D7EF /* StateRestorableViewController.swift */, D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */, D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */, D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */, @@ -2120,6 +2123,7 @@ D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */, D6895DE928D962C2006341DA /* TimelineLikeController.swift in Sources */, D6A3A3822956123A0036B6EF /* TimelinePosition.swift in Sources */, + D691771529A6FCAB0054D7EF /* StateRestorableViewController.swift in Sources */, D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */, D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */, D61F75BD293D099600C0B37F /* Lazy.swift in Sources */, diff --git a/Tusker/Screens/Conversation/ConversationViewController.swift b/Tusker/Screens/Conversation/ConversationViewController.swift index e1f3c4c8..23841b86 100644 --- a/Tusker/Screens/Conversation/ConversationViewController.swift +++ b/Tusker/Screens/Conversation/ConversationViewController.swift @@ -412,6 +412,21 @@ extension ConversationViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } +extension ConversationViewController: StateRestorableViewController { + func stateRestorationActivity() -> NSUserActivity? { + if let accountID = mastodonController.accountInfo?.id, + case .localID(let id) = mode { + return UserActivityManager.showConversationActivity(mainStatusID: id, accountID: accountID) + } else { + return nil + } + } + + func restoreActivity(_ activity: NSUserActivity) { + fatalError("ConversationViewController must be reconstructed, not restored") + } +} + extension ConversationViewController: ToastableViewController { var toastScrollView: UIScrollView? { if case .displaying(let vc) = state { diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 2441b181..b89deab4 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -531,6 +531,34 @@ extension ExploreViewController { } } +extension ExploreViewController: StateRestorableViewController { + func stateRestorationActivity() -> NSUserActivity? { + if searchController.isActive { + return UserActivityManager.searchActivity(query: searchController.searchBar.text, accountID: mastodonController.accountInfo!.id) + } else { + return nil + } + } + + func restoreActivity(_ activity: NSUserActivity) { + guard let type = UserActivityType(rawValue: activity.activityType) else { + return + } + if type == .bookmarks { + show(BookmarksViewController(mastodonController: mastodonController), sender: nil) + } else if type == .search { + loadViewIfNeeded() + searchController.isActive = true + if let query = UserActivityManager.getSearchQuery(from: activity), + !query.isEmpty { + searchController.searchBar.text = query + } else { + searchController.searchBar.becomeFirstResponder() + } + } + } +} + extension ExploreViewController: InstanceTimelineViewControllerDelegate { func didSaveInstance(url: URL) { dismiss(animated: true) { @@ -553,7 +581,7 @@ extension ExploreViewController: UICollectionViewDragDelegate { let provider: NSItemProvider switch item { case .bookmarks: - let activity = UserActivityManager.bookmarksActivity() + let activity = UserActivityManager.bookmarksActivity(accountID: accountID) activity.displaysAuxiliaryScene = true provider = NSItemProvider(object: activity) case let .list(list): diff --git a/Tusker/Screens/Local Predicate Statuses List/BookmarksViewController.swift b/Tusker/Screens/Local Predicate Statuses List/BookmarksViewController.swift index b6701f7c..f7af2b59 100644 --- a/Tusker/Screens/Local Predicate Statuses List/BookmarksViewController.swift +++ b/Tusker/Screens/Local Predicate Statuses List/BookmarksViewController.swift @@ -18,6 +18,8 @@ class BookmarksViewController: LocalPredicateStatusesViewController { request: { Client.getBookmarks(range: $0) }, mastodonController: mastodonController ) + + userActivity = UserActivityManager.bookmarksActivity(accountID: mastodonController.accountInfo!.id) } required init?(coder: NSCoder) { @@ -25,3 +27,12 @@ class BookmarksViewController: LocalPredicateStatusesViewController { } } + +extension BookmarksViewController: StateRestorableViewController { + func stateRestorationActivity() -> NSUserActivity? { + return UserActivityManager.bookmarksActivity(accountID: mastodonController.accountInfo!.id) + } + + func restoreActivity(_ activity: NSUserActivity) { + } +} diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index b6d7761f..5e1fc928 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -308,11 +308,11 @@ class MainSidebarViewController: UIViewController { case .tab(.compose): return UserActivityManager.newPostActivity(accountID: id) case .explore: - return UserActivityManager.searchActivity() + return UserActivityManager.searchActivity(query: nil, accountID: id) case .bookmarks: - return UserActivityManager.bookmarksActivity() + return UserActivityManager.bookmarksActivity(accountID: id) case .tab(.myProfile): - return UserActivityManager.myProfileActivity() + return UserActivityManager.myProfileActivity(accountID: id) case let .list(list): return UserActivityManager.showTimelineActivity(timeline: .list(id: list.id), accountID: id) case let .savedHashtag(tag): diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index fde39326..8eca4132 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -398,36 +398,76 @@ extension MainSplitViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } -extension MainSplitViewController: TuskerRootViewController { +extension MainSplitViewController: StateRestorableViewController { func stateRestorationActivity() -> NSUserActivity? { if traitCollection.horizontalSizeClass == .compact { return tabBarViewController.stateRestorationActivity() + } else if let currentItem = sidebar.selectedItem, + let navStack = navigationStackFor(item: currentItem), + let top = navStack.last as? StateRestorableViewController { + return top.stateRestorationActivity() } else { - if let timelinePages = navigationStackFor(item: .tab(.timelines))?.first as? TimelinesPageViewController { - return timelinePages.stateRestorationActivity() - } else { - stateRestorationLogger.fault("MainSplitViewController: Unable to create state restoration activity") - return nil - } + stateRestorationLogger.fault("MainSplitViewController: Unable to create state restoration activity") + return nil } } func restoreActivity(_ activity: NSUserActivity) { - if traitCollection.horizontalSizeClass == .compact { + guard traitCollection.horizontalSizeClass != .compact else { tabBarViewController.restoreActivity(activity) - } else { - if activity.activityType == UserActivityType.showTimeline.rawValue { - guard let timelinePages = navigationStackFor(item: .tab(.timelines))?.first as? TimelinesPageViewController else { - stateRestorationLogger.fault("MainSplitViewController: Unable to restore timeline activity, couldn't find VC") - return - } - timelinePages.restoreActivity(activity) + return + } + guard let type = UserActivityType(rawValue: activity.activityType) else { + return + } + + let item: MainSidebarViewController.Item + var needsRestore = true + switch type { + case .showTimeline: + item = .tab(.timelines) + case .checkNotifications: + item = .tab(.notifications) + case .search: + item = .explore + case .bookmarks: + item = .bookmarks + case .myProfile: + item = .tab(.myProfile) + needsRestore = false + case .newPost: + return + case .showConversation, .showProfile: + item = .tab(.timelines) + default: + stateRestorationLogger.fault("MainSplitViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)") + return + } + + sidebar.select(item: item, animated: false) + select(item: item) + + if type == .showConversation { + if let statusID = UserActivityManager.getConversationStatus(from: activity) { + let conv = ConversationViewController(for: statusID, state: .unknown, mastodonController: mastodonController) + secondaryNavController.show(conv, sender: nil) + } + } else if type == .showProfile { + if let accountID = UserActivityManager.getProfile(from: activity) { + let profile = ProfileViewController(accountID: accountID, mastodonController: mastodonController) + secondaryNavController.show(profile, sender: nil) + } + } else if needsRestore { + if let vc = secondaryNavController.viewControllers.first as? StateRestorableViewController { + vc.restoreActivity(activity) } else { - stateRestorationLogger.fault("MainSplitViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)") + stateRestorationLogger.fault("MainSplitViewController: Unable to restore activity, couldn't find StateRestorableViewController") } } } - +} + +extension MainSplitViewController: TuskerRootViewController { @objc func presentCompose() { self.compose() } diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift index d5ac1a9a..3af3740e 100644 --- a/Tusker/Screens/Main/MainTabBarViewController.swift +++ b/Tusker/Screens/Main/MainTabBarViewController.swift @@ -240,14 +240,14 @@ extension MainTabBarViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } -extension MainTabBarViewController: TuskerRootViewController { +extension MainTabBarViewController: StateRestorableViewController { func stateRestorationActivity() -> NSUserActivity? { - let nav = viewController(for: .timelines) as! UINavigationController + let nav = viewController(for: selectedTab) as! UINavigationController var activity: NSUserActivity? - if let timelinePages = nav.viewControllers.first as? TimelinesPageViewController { - activity = timelinePages.stateRestorationActivity() + if let vc = nav.topViewController as? StateRestorableViewController { + activity = vc.stateRestorationActivity() } else { - stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration activity, couldn't find timeline/page VC") + stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration activity, couldn't find StateRestorableViewController") } if let presentedNav = presentedViewController as? UINavigationController, let compose = presentedNav.viewControllers.first as? ComposeHostingController { @@ -257,6 +257,10 @@ extension MainTabBarViewController: TuskerRootViewController { } func restoreActivity(_ activity: NSUserActivity) { + guard let type = UserActivityType(rawValue: activity.activityType) else { + return + } + func restoreEditedDraft() { // on iOS 16+, this is handled by the duckable container if #unavailable(iOS 16.0), @@ -265,22 +269,50 @@ extension MainTabBarViewController: TuskerRootViewController { } } - if activity.activityType == UserActivityType.showTimeline.rawValue { - let nav = viewController(for: .timelines) as! UINavigationController - guard let timelinePages = nav.viewControllers.first as? TimelinesPageViewController else { - stateRestorationLogger.fault("MainTabBarViewController: Unable to restore timeline activity, couldn't find VC") - return - } - timelinePages.restoreActivity(activity) - restoreEditedDraft() - } else if activity.activityType == UserActivityType.newPost.rawValue { + let tab: Tab + switch type { + case .showTimeline: + tab = .timelines + case .checkNotifications: + tab = .notifications + case .search, .bookmarks: + tab = .explore + case .myProfile: + tab = .myProfile + case .newPost: restoreEditedDraft() return - } else { + case .showConversation, .showProfile: + tab = .timelines + default: stateRestorationLogger.fault("MainTabBarViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)") + return + } + + select(tab: tab) + let nav = viewController(for: tab) as! UINavigationController + + if type == .showConversation { + if let statusID = UserActivityManager.getConversationStatus(from: activity) { + let conv = ConversationViewController(for: statusID, state: .unknown, mastodonController: mastodonController) + nav.pushViewController(conv, animated: false) + } + } else if type == .showProfile { + if let accountID = UserActivityManager.getProfile(from: activity) { + let profile = ProfileViewController(accountID: accountID, mastodonController: mastodonController) + nav.pushViewController(profile, animated: false) + } + } else if type == .bookmarks { + nav.pushViewController(BookmarksViewController(mastodonController: mastodonController), animated: false) + } else if let vc = nav.viewControllers.first as? StateRestorableViewController { + vc.restoreActivity(activity) + } else { + stateRestorationLogger.fault("MainTabBarViewController: Unable to restore activity, couldn't find StateRestorableViewController") } } - +} + +extension MainTabBarViewController: TuskerRootViewController { @objc func presentCompose() { compose() } diff --git a/Tusker/Screens/Main/TuskerRootViewController.swift b/Tusker/Screens/Main/TuskerRootViewController.swift index 29fd95d8..71541a9e 100644 --- a/Tusker/Screens/Main/TuskerRootViewController.swift +++ b/Tusker/Screens/Main/TuskerRootViewController.swift @@ -8,9 +8,7 @@ import UIKit -protocol TuskerRootViewController: UIViewController, StatusBarTappableViewController { - func stateRestorationActivity() -> NSUserActivity? - func restoreActivity(_ activity: NSUserActivity) +protocol TuskerRootViewController: UIViewController, StateRestorableViewController, StatusBarTappableViewController { func presentCompose() func select(tab: MainTabBarViewController.Tab) func getTabController(tab: MainTabBarViewController.Tab) -> UIViewController? diff --git a/Tusker/Screens/Notifications/NotificationsPageViewController.swift b/Tusker/Screens/Notifications/NotificationsPageViewController.swift index 3f2f84c3..77aa49b4 100644 --- a/Tusker/Screens/Notifications/NotificationsPageViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsPageViewController.swift @@ -48,7 +48,6 @@ class NotificationsPageViewController: SegmentedPageViewController NSUserActivity? { + return currentPage.userActivity + } + + func restoreActivity(_ activity: NSUserActivity) { + if let mode = UserActivityManager.getNotificationsMode(from: activity) { + selectMode(mode) + } + } +} diff --git a/Tusker/Screens/Profile/MyProfileViewController.swift b/Tusker/Screens/Profile/MyProfileViewController.swift index 6beb1057..1ed607ec 100644 --- a/Tusker/Screens/Profile/MyProfileViewController.swift +++ b/Tusker/Screens/Profile/MyProfileViewController.swift @@ -39,7 +39,11 @@ class MyProfileViewController: ProfileViewController { NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) - userActivity = UserActivityManager.myProfileActivity() + userActivity = UserActivityManager.myProfileActivity(accountID: mastodonController.accountInfo!.id) + } + + override func stateRestorationActivity() -> NSUserActivity? { + return UserActivityManager.myProfileActivity(accountID: mastodonController.accountInfo!.id) } private func setAvatarTabBarImage(account: Account) { diff --git a/Tusker/Screens/Profile/ProfileViewController.swift b/Tusker/Screens/Profile/ProfileViewController.swift index 82f637b0..4699f2a0 100644 --- a/Tusker/Screens/Profile/ProfileViewController.swift +++ b/Tusker/Screens/Profile/ProfileViewController.swift @@ -10,7 +10,7 @@ import UIKit import Pachyderm import Combine -class ProfileViewController: UIViewController { +class ProfileViewController: UIViewController, StateRestorableViewController { weak var mastodonController: MastodonController! @@ -285,6 +285,20 @@ class ProfileViewController: UIViewController { compose(editing: draft) } } + + // MARK: StateRestorableViewController + func stateRestorationActivity() -> NSUserActivity? { + if let accountID, + let accountInfo = mastodonController.accountInfo { + return UserActivityManager.showProfileActivity(id: accountID, accountID: accountInfo.id) + } else { + return nil + } + } + + func restoreActivity(_ activity: NSUserActivity) { + fatalError("ProfileViewController must be reconstructed, not restored") + } } extension ProfileViewController { diff --git a/Tusker/Screens/Search/SearchResultsViewController.swift b/Tusker/Screens/Search/SearchResultsViewController.swift index 2c288489..84e47fc7 100644 --- a/Tusker/Screens/Search/SearchResultsViewController.swift +++ b/Tusker/Screens/Search/SearchResultsViewController.swift @@ -104,7 +104,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController { .filter { $0 != self.currentQuery } .sink(receiveValue: performSearch(query:)) - userActivity = UserActivityManager.searchActivity() + userActivity = UserActivityManager.searchActivity(query: nil, accountID: mastodonController.accountInfo!.id) NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil) } diff --git a/Tusker/Screens/Timeline/TimelinesPageViewController.swift b/Tusker/Screens/Timeline/TimelinesPageViewController.swift index 0d99df1a..69efc3f7 100644 --- a/Tusker/Screens/Timeline/TimelinesPageViewController.swift +++ b/Tusker/Screens/Timeline/TimelinesPageViewController.swift @@ -105,22 +105,6 @@ class TimelinesPageViewController: SegmentedPageViewController NSUserActivity? { - return (currentViewController as? TimelineViewController)?.stateRestorationActivity() - } - - func restoreActivity(_ activity: NSUserActivity) { - guard let timeline = UserActivityManager.getTimeline(from: activity), - let pinned = PinnedTimeline(timeline: timeline) else { - return - } - let page = Page(mastodonController: mastodonController, timeline: pinned) - // the pinned timelines may have changed after an iCloud sync, in which case don't restore anything - if pages.contains(page) { - selectPage(page, animated: false) - } - } - @objc private func customizePressed() { present(UIHostingController(rootView: CustomizeTimelinesView(mastodonController: mastodonController)), animated: true) } @@ -222,3 +206,21 @@ extension TimelinesPageViewController: TimelineViewControllerDelegate { } } } + +extension TimelinesPageViewController: StateRestorableViewController { + func stateRestorationActivity() -> NSUserActivity? { + return (currentViewController as? TimelineViewController)?.stateRestorationActivity() + } + + func restoreActivity(_ activity: NSUserActivity) { + guard let timeline = UserActivityManager.getTimeline(from: activity), + let pinned = PinnedTimeline(timeline: timeline) else { + return + } + let page = Page(mastodonController: mastodonController, timeline: pinned) + // the pinned timelines may have changed after an iCloud sync, in which case don't restore anything + if pages.contains(page) { + selectPage(page, animated: false) + } + } +} diff --git a/Tusker/Screens/Utilities/SegmentedPageViewController.swift b/Tusker/Screens/Utilities/SegmentedPageViewController.swift index 2c2d915e..02482521 100644 --- a/Tusker/Screens/Utilities/SegmentedPageViewController.swift +++ b/Tusker/Screens/Utilities/SegmentedPageViewController.swift @@ -19,7 +19,7 @@ class SegmentedPageViewController: UIView private var pageControllers = [Page: UIViewController]() private var initialPage: Page - private var currentPage: Page + private(set) var currentPage: Page var currentIndex: Int! { pages.firstIndex(of: currentPage) } diff --git a/Tusker/Screens/Utilities/StateRestorableViewController.swift b/Tusker/Screens/Utilities/StateRestorableViewController.swift new file mode 100644 index 00000000..afecf7c2 --- /dev/null +++ b/Tusker/Screens/Utilities/StateRestorableViewController.swift @@ -0,0 +1,15 @@ +// +// StateRestorableViewController.swift +// Tusker +// +// Created by Shadowfacts on 2/22/23. +// Copyright © 2023 Shadowfacts. All rights reserved. +// + +import UIKit + +protocol StateRestorableViewController: UIViewController { + func stateRestorationActivity() -> NSUserActivity? + + func restoreActivity(_ activity: NSUserActivity) +} diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index 5c4bd552..535aefca 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -244,14 +244,24 @@ class UserActivityManager { // MARK: - Explore - static func searchActivity() -> NSUserActivity { + static func searchActivity(query: String?, accountID: String) -> NSUserActivity { let activity = NSUserActivity(type: .search) + activity.userInfo = [ + "accountID": accountID + ] + if let query { + activity.userInfo!["query"] = query + } activity.isEligibleForPrediction = true activity.title = NSLocalizedString("Search", comment: "search shortcut title") activity.suggestedInvocationPhrase = NSLocalizedString("Search the fediverse", comment: "search shortcut invocation phrase") return activity } + static func getSearchQuery(from activity: NSUserActivity) -> String? { + return activity.userInfo?["query"] as? String + } + func handleSearch(activity: NSUserActivity) { let mainViewController = getMainViewController() mainViewController.select(tab: .explore) @@ -260,12 +270,20 @@ class UserActivityManager { navigationController.popToRootViewController(animated: false) exploreController.loadViewIfNeeded() exploreController.searchController.isActive = true - exploreController.searchController.searchBar.becomeFirstResponder() + if let query = Self.getSearchQuery(from: activity), + !query.isEmpty { + exploreController.searchController.searchBar.text = query + } else { + exploreController.searchController.searchBar.becomeFirstResponder() + } } } - static func bookmarksActivity() -> NSUserActivity { + static func bookmarksActivity(accountID: String) -> NSUserActivity { let activity = NSUserActivity(type: .bookmarks) + activity.userInfo = [ + "accountID": accountID + ] activity.isEligibleForPrediction = true activity.title = NSLocalizedString("View Bookmarks", comment: "bookmarks shortcut title") activity.suggestedInvocationPhrase = NSLocalizedString("Show my bookmarks in Tusker", comment: "bookmarks shortcut invocation phrase") @@ -282,8 +300,11 @@ class UserActivityManager { } // MARK: - My Profile - static func myProfileActivity() -> NSUserActivity { + static func myProfileActivity(accountID: String) -> NSUserActivity { let activity = NSUserActivity(type: .myProfile) + activity.userInfo = [ + "accountID": accountID + ] activity.isEligibleForPrediction = true activity.title = NSLocalizedString("My Profile", comment: "my profile shortcut title") activity.suggestedInvocationPhrase = NSLocalizedString("Show my Mastodon profile", comment: "my profile shortuct invocation phrase")