From cc0da2ec5400f99bf6845e27692d7adcb421f023 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 13 May 2022 17:10:18 -0400 Subject: [PATCH] Fix user activities not continuing when passed at launch Fix crash when continuing user activities on iPad --- Tusker/AppDelegate.swift | 12 +++++- Tusker/MainSceneDelegate.swift | 9 +++- .../Drafts/DraftsTableViewController.swift | 1 + .../Explore/ExploreViewController.swift | 6 ++- .../ProfileDirectoryViewController.swift | 1 + .../TrendingHashtagsViewController.swift | 1 + .../Main/MainSidebarViewController.swift | 9 ++++ .../Main/MainSplitViewController.swift | 13 ++++-- .../Main/MainTabBarViewController.swift | 13 ++++++ .../Shortcuts/NSUserActivity+Extensions.swift | 16 +++++++- Tusker/Shortcuts/UserActivityManager.swift | 41 +++++++++++-------- Tusker/Shortcuts/UserActivityType.swift | 2 +- .../Account Cell/AccountTableViewCell.swift | 1 + ...FollowNotificationGroupTableViewCell.swift | 1 + ...llowRequestNotificationTableViewCell.swift | 1 + .../Status/BaseStatusTableViewCell.swift | 1 + .../Status/TimelineStatusTableViewCell.swift | 1 + 17 files changed, 101 insertions(+), 28 deletions(-) diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift index eaf3e0688b..d6d61ad136 100644 --- a/Tusker/AppDelegate.swift +++ b/Tusker/AppDelegate.swift @@ -9,6 +9,9 @@ import UIKit import CrashReporter import CoreData +import OSLog + +let stateRestorationLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "StateRestoration") @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -85,7 +88,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { switch type { case .mainScene: return "main-scene" - + case .showConversation, .showTimeline, .checkNotifications, @@ -93,7 +96,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .bookmarks, .myProfile, .showProfile: - return "auxiliary" + if activity.displaysAuxiliaryScene { + stateRestorationLogger.info("Using auxiliary scene for \(type.rawValue, privacy: .public)") + return "auxiliary" + } else { + return "main-scene" + } case .newPost: return "compose" diff --git a/Tusker/MainSceneDelegate.swift b/Tusker/MainSceneDelegate.swift index f9b950c08c..f60ebdc5fd 100644 --- a/Tusker/MainSceneDelegate.swift +++ b/Tusker/MainSceneDelegate.swift @@ -28,6 +28,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { launchActivity = activity } + stateRestorationLogger.info("MainSceneDelegate.launchActivity = \(self.launchActivity?.activityType ?? "nil", privacy: .public)") window = UIWindow(windowScene: windowScene) @@ -69,7 +70,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - _ = userActivity.handleResume() + stateRestorationLogger.info("MainSceneDelegate.scene(_:continue:) called with \(userActivity.activityType, privacy: .public)") + _ = userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene)) } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { @@ -169,6 +171,11 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { } activateAccount(account, animated: false) + + if let activity = launchActivity, + activity.activityType != UserActivityType.mainScene.rawValue { + _ = activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!)) + } } else { window!.rootViewController = createOnboardingUI() } diff --git a/Tusker/Screens/Drafts/DraftsTableViewController.swift b/Tusker/Screens/Drafts/DraftsTableViewController.swift index 2da8b53cf7..3a981864fe 100644 --- a/Tusker/Screens/Drafts/DraftsTableViewController.swift +++ b/Tusker/Screens/Drafts/DraftsTableViewController.swift @@ -123,6 +123,7 @@ extension DraftsTableViewController: UITableViewDragDelegate { func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { let draft = self.draft(for: indexPath) let activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: account.id) + activity.displaysAuxiliaryScene = true let provider = NSItemProvider(object: activity) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 393fc81cbe..df782c4485 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -574,13 +574,17 @@ extension ExploreViewController: UICollectionViewDragDelegate { let provider: NSItemProvider switch item { case .bookmarks: - provider = NSItemProvider(object: UserActivityManager.bookmarksActivity()) + let activity = UserActivityManager.bookmarksActivity() + activity.displaysAuxiliaryScene = true + provider = NSItemProvider(object: activity) case let .list(list): guard let activity = UserActivityManager.showTimelineActivity(timeline: .list(id: list.id), accountID: accountID) else { return [] } + activity.displaysAuxiliaryScene = true provider = NSItemProvider(object: activity) case let .savedHashtag(hashtag): provider = NSItemProvider(object: hashtag.url as NSURL) if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: accountID) { + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) } case let .savedInstance(url): diff --git a/Tusker/Screens/Explore/ProfileDirectoryViewController.swift b/Tusker/Screens/Explore/ProfileDirectoryViewController.swift index 50e22ca9e1..19ce4dd051 100644 --- a/Tusker/Screens/Explore/ProfileDirectoryViewController.swift +++ b/Tusker/Screens/Explore/ProfileDirectoryViewController.swift @@ -183,6 +183,7 @@ extension ProfileDirectoryViewController: UICollectionViewDragDelegate { } let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift index 4b14d36415..ebbd80308d 100644 --- a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift +++ b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift @@ -90,6 +90,7 @@ class TrendingHashtagsViewController: EnhancedTableViewController { } let provider = NSItemProvider(object: hashtag.url as NSURL) if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) { + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) } return [UIDragItem(itemProvider: provider)] diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index 8523d2c169..187e273256 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -19,6 +19,11 @@ class MainSidebarViewController: UIViewController { private weak var mastodonController: MastodonController! weak var sidebarDelegate: MainSidebarViewControllerDelegate? + var onViewDidLoad: (() -> Void)? = nil { + willSet { + precondition(onViewDidLoad == nil) + } + } private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! @@ -95,6 +100,8 @@ class MainSidebarViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedHashtags), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedInstances), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) + + onViewDidLoad?() } func select(item: Item, animated: Bool) { @@ -553,6 +560,7 @@ extension MainSidebarViewController: UICollectionViewDelegate { collectionView.contextMenuInteraction?.menuAppearance == .rich { return nil } + activity.displaysAuxiliaryScene = true return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) in return UIMenu(children: [ UIWindowScene.ActivationAction({ action in @@ -573,6 +581,7 @@ extension MainSidebarViewController: UICollectionViewDragDelegate { return [] } + activity.displaysAuxiliaryScene = true let provider = NSItemProvider(object: activity) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index 86ddf42148..d38b8f4ad6 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -59,10 +59,15 @@ class MainSplitViewController: UISplitViewController { switcher.itemOrientation = .iconsLeading switcher.view.translatesAutoresizingMaskIntoConstraints = false switcher.delegate = self - sidebar.view.addGestureRecognizer(switcher.createSwitcherGesture()) - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(sidebarTapped)) - tapRecognizer.cancelsTouchesInView = false - sidebar.view.addGestureRecognizer(tapRecognizer) + // accessing .view unconditionally loads the view, which we don't want to happen + // because the sidebar view not being loaded is how we know not to transfer nav state + // in splitViewControllerDidCollapse on devices where the sidebar is never shown + sidebar.onViewDidLoad = { [unowned self] in + self.sidebar.view.addGestureRecognizer(switcher.createSwitcherGesture()) + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(sidebarTapped)) + tapRecognizer.cancelsTouchesInView = false + self.sidebar.view.addGestureRecognizer(tapRecognizer) + } } tabBarViewController = MainTabBarViewController(mastodonController: mastodonController) diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift index 79b658c967..b8ac76452f 100644 --- a/Tusker/Screens/Main/MainTabBarViewController.swift +++ b/Tusker/Screens/Main/MainTabBarViewController.swift @@ -42,6 +42,8 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate { override func viewDidLoad() { super.viewDidLoad() + + stateRestorationLogger.info("MainTabBarViewController: viewDidLoad, selectedIndex=\(self.selectedIndex, privacy: .public)") self.delegate = self @@ -75,6 +77,16 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate { tabBar.isSpringLoaded = true } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + stateRestorationLogger.info("MainTabBarViewController: viewWillAppear, selectedIndex=\(self.selectedIndex, privacy: .public)") + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + stateRestorationLogger.info("MainTabBarViewController: viewDidAppear, selectedIndex=\(self.selectedIndex, privacy: .public)") + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -240,6 +252,7 @@ extension MainTabBarViewController: TuskerRootViewController { self.selectedIndex = tab.rawValue } } else { + stateRestorationLogger.info("MainTabBarViewController: selecting \(String(describing: tab), privacy: .public)") selectedIndex = tab.rawValue } } diff --git a/Tusker/Shortcuts/NSUserActivity+Extensions.swift b/Tusker/Shortcuts/NSUserActivity+Extensions.swift index edeb6e52a7..13b71f7361 100644 --- a/Tusker/Shortcuts/NSUserActivity+Extensions.swift +++ b/Tusker/Shortcuts/NSUserActivity+Extensions.swift @@ -10,13 +10,25 @@ import Foundation extension NSUserActivity { + var displaysAuxiliaryScene: Bool { + get { + (userInfo?["displaysAuxiliaryScene"] as? Bool) ?? false + } + set { + if userInfo == nil { + userInfo = [:] + } + userInfo!["displaysAuxiliaryScene"] = newValue + } + } + convenience init(type: UserActivityType) { self.init(activityType: type.rawValue) } - func handleResume() -> Bool { + func handleResume(manager: UserActivityManager) -> Bool { guard let type = UserActivityType(rawValue: activityType) else { return false } - type.handle(self) + type.handle(manager)(self) return true } diff --git a/Tusker/Shortcuts/UserActivityManager.swift b/Tusker/Shortcuts/UserActivityManager.swift index cf90c3a02c..101bb646bf 100644 --- a/Tusker/Shortcuts/UserActivityManager.swift +++ b/Tusker/Shortcuts/UserActivityManager.swift @@ -9,25 +9,32 @@ import UIKit import Intents import Pachyderm +import OSLog + +private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "UserActivityManager") class UserActivityManager { + private let scene: UIWindowScene + + init(scene: UIWindowScene) { + self.scene = scene + } + // MARK: - Utils private static let encoder = PropertyListEncoder() private static let decoder = PropertyListDecoder() - private static var mastodonController: MastodonController { - let scene = UIApplication.shared.activeOrBackgroundScene! - return scene.session.mastodonController! + private var mastodonController: MastodonController { + scene.session.mastodonController! } - private static func getMainViewController() -> TuskerRootViewController { - let scene = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first! - let window = scene.windows.first { $0.isKeyWindow }! + private func getMainViewController() -> TuskerRootViewController { + let window = scene.windows.first { $0.isKeyWindow } ?? scene.windows.first! return window.rootViewController as! TuskerRootViewController } - private static func present(_ vc: UIViewController, animated: Bool = true) { + private func present(_ vc: UIViewController, animated: Bool = true) { getMainViewController().present(vc, animated: animated) } @@ -66,7 +73,7 @@ class UserActivityManager { return activity } - static func handleNewPost(activity: NSUserActivity) { + func handleNewPost(activity: NSUserActivity) { // TODO: check not currently showing compose screen let mentioning = activity.userInfo?["mentioning"] as? String let draft = mastodonController.createDraft(mentioningAcct: mentioning) @@ -111,14 +118,14 @@ class UserActivityManager { return activity } - static func handleCheckNotifications(activity: NSUserActivity) { + func handleCheckNotifications(activity: NSUserActivity) { let mainViewController = getMainViewController() mainViewController.select(tab: .notifications) if let navigationController = mainViewController.getTabController(tab: .notifications) as? UINavigationController, - let notificationsPageController = navigationController.viewControllers.first as? NotificationsPageViewController { + let notificationsPageController = navigationController.viewControllers.first as? NotificationsPageViewController { navigationController.popToRootViewController(animated: false) notificationsPageController.loadViewIfNeeded() - notificationsPageController.selectMode(getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode) + notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode) } } @@ -168,11 +175,11 @@ class UserActivityManager { let data = activity.userInfo?["timelineData"] as? Data else { return nil } - return try? decoder.decode(Timeline.self, from: data) + return try? UserActivityManager.decoder.decode(Timeline.self, from: data) } - static func handleShowTimeline(activity: NSUserActivity) { - guard let timeline = getTimeline(from: activity) else { return } + func handleShowTimeline(activity: NSUserActivity) { + guard let timeline = Self.getTimeline(from: activity) else { return } let mainViewController = getMainViewController() mainViewController.select(tab: .timelines) @@ -228,7 +235,7 @@ class UserActivityManager { return activity } - static func handleSearch(activity: NSUserActivity) { + func handleSearch(activity: NSUserActivity) { let mainViewController = getMainViewController() mainViewController.select(tab: .explore) if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController, @@ -247,7 +254,7 @@ class UserActivityManager { return activity } - static func handleBookmarks(activity: NSUserActivity) { + func handleBookmarks(activity: NSUserActivity) { let mainViewController = getMainViewController() mainViewController.select(tab: .explore) if let navigationController = mainViewController.getTabController(tab: .explore) as? UINavigationController { @@ -265,7 +272,7 @@ class UserActivityManager { return activity } - static func handleMyProfile(activity: NSUserActivity) { + func handleMyProfile(activity: NSUserActivity) { let mainViewController = getMainViewController() mainViewController.select(tab: .myProfile) } diff --git a/Tusker/Shortcuts/UserActivityType.swift b/Tusker/Shortcuts/UserActivityType.swift index d15e8e541d..aad6ff1d68 100644 --- a/Tusker/Shortcuts/UserActivityType.swift +++ b/Tusker/Shortcuts/UserActivityType.swift @@ -21,7 +21,7 @@ enum UserActivityType: String { } extension UserActivityType { - var handle: (NSUserActivity) -> Void { + var handle: (UserActivityManager) -> (NSUserActivity) -> Void { switch self { case .mainScene: fatalError("cannot handle main scene activity") diff --git a/Tusker/Views/Account Cell/AccountTableViewCell.swift b/Tusker/Views/Account Cell/AccountTableViewCell.swift index 0271d7109d..95b78ff326 100644 --- a/Tusker/Views/Account Cell/AccountTableViewCell.swift +++ b/Tusker/Views/Account Cell/AccountTableViewCell.swift @@ -115,6 +115,7 @@ extension AccountTableViewCell: DraggableTableViewCell { } let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift index 446e39952c..303825c5b1 100644 --- a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift +++ b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift @@ -223,6 +223,7 @@ extension FollowNotificationGroupTableViewCell: DraggableTableViewCell { let notification = group.notifications[0] let provider = NSItemProvider(object: notification.account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: notification.account.id, accountID: mastodonController.accountInfo!.id) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift index e1613f6164..a687d44112 100644 --- a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift +++ b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift @@ -206,6 +206,7 @@ extension FollowRequestNotificationTableViewCell: DraggableTableViewCell { func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: mastodonController.accountInfo!.id) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Views/Status/BaseStatusTableViewCell.swift b/Tusker/Views/Status/BaseStatusTableViewCell.swift index f25841e5af..5bacedb3ee 100644 --- a/Tusker/Views/Status/BaseStatusTableViewCell.swift +++ b/Tusker/Views/Status/BaseStatusTableViewCell.swift @@ -531,6 +531,7 @@ extension BaseStatusTableViewCell: UIDragInteractionDelegate { } let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: accountID, accountID: currentAccountID) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } diff --git a/Tusker/Views/Status/TimelineStatusTableViewCell.swift b/Tusker/Views/Status/TimelineStatusTableViewCell.swift index ae8a1af6db..a427925206 100644 --- a/Tusker/Views/Status/TimelineStatusTableViewCell.swift +++ b/Tusker/Views/Status/TimelineStatusTableViewCell.swift @@ -356,6 +356,7 @@ extension TimelineStatusTableViewCell: DraggableTableViewCell { } let provider = NSItemProvider(object: status.url! as NSURL) let activity = UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID) + activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] }