From 2f6d0ae07c0e61714de56eaca0221558529b47be Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 7 Mar 2022 21:54:41 -0500 Subject: [PATCH] Add user activities for read all/unread --- Reader/Info.plist | 2 + Reader/SceneDelegate.swift | 62 ++++++++++++++------ Reader/Screens/AppSplitViewController.swift | 12 ++++ Reader/Screens/Home/HomeViewController.swift | 46 ++++++++------- Reader/UserActivities.swift | 29 ++++++++- 5 files changed, 111 insertions(+), 40 deletions(-) diff --git a/Reader/Info.plist b/Reader/Info.plist index e8dc7c1..88aece3 100644 --- a/Reader/Info.plist +++ b/Reader/Info.plist @@ -4,6 +4,8 @@ NSUserActivityTypes + $(PRODUCT_BUNDLE_IDENTIFIER).activity.read-unread + $(PRODUCT_BUNDLE_IDENTIFIER).activity.read-all $(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences $(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account $(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift index 2621a0f..6e93dba 100644 --- a/Reader/SceneDelegate.swift +++ b/Reader/SceneDelegate.swift @@ -5,7 +5,7 @@ // Created by Shadowfacts on 10/29/21. // -import Foundation +@preconcurrency import Foundation import UIKit import OSLog @@ -27,30 +27,31 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) window!.tintColor = .appTintColor - let activity = connectionOptions.userActivities.first + var activity = connectionOptions.userActivities.first + + var account = LocalData.mostRecentAccount() + if activity?.activityType == NSUserActivity.addAccountType { - let loginVC = LoginViewController() - loginVC.delegate = self - window!.rootViewController = loginVC - } else if activity?.activityType == NSUserActivity.activateAccountType, - let idStr = activity!.userInfo?["accountID"] as? String, - let id = Data(base64Encoded: idStr), - let account = LocalData.account(with: id) { - Task { @MainActor in - fervorController = await FervorController(account: account) - syncFromServer() - createAppUI() + account = nil + } else if let id = activity?.accountID() { + account = LocalData.account(with: id) + if account == nil { + activity = nil + logger.log("Missing account for activity, not restoring") } - } else if let account = LocalData.mostRecentAccount() { - Task { @MainActor in + } + + if let account = account { + Task { @MainActor [activity] in fervorController = await FervorController(account: account) syncFromServer() createAppUI() + if let activity = activity { + setupUI(from: activity) + } } } else { - let loginVC = LoginViewController() - loginVC.delegate = self - window!.rootViewController = loginVC + createLoginUI() } #if targetEnvironment(macCatalyst) @@ -106,6 +107,31 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // to restore the scene back to its current state. } + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + setupUI(from: userActivity) + } + + private func setupUI(from activity: NSUserActivity) { + guard let split = window?.rootViewController as? AppSplitViewController else { + logger.error("Failed to setup UI for user activity: missing split VC") + return + } + switch activity.activityType { + case NSUserActivity.readUnreadType: + split.selectHomeItem(.unread) + case NSUserActivity.readAllType: + split.selectHomeItem(.all) + default: + break + } + } + + private func createLoginUI() { + let vc = LoginViewController() + vc.delegate = self + window!.rootViewController = vc + } + private func createAppUI() { window!.rootViewController = AppSplitViewController(fervorController: fervorController) } diff --git a/Reader/Screens/AppSplitViewController.swift b/Reader/Screens/AppSplitViewController.swift index 2638f6f..228f4e5 100644 --- a/Reader/Screens/AppSplitViewController.swift +++ b/Reader/Screens/AppSplitViewController.swift @@ -53,6 +53,18 @@ class AppSplitViewController: UISplitViewController { let nav = AppNavigationController(rootViewController: home) setViewController(nav, for: .compact) } + + func selectHomeItem(_ item: HomeViewController.Item) { + let column: Column + if traitCollection.horizontalSizeClass == .compact { + column = .compact + } else { + column = .primary + } + let nav = viewController(for: column) as! UINavigationController + let home = nav.viewControllers.first! as! HomeViewController + home.selectItem(item) + } } diff --git a/Reader/Screens/Home/HomeViewController.swift b/Reader/Screens/Home/HomeViewController.swift index d8e351d..27a2929 100644 --- a/Reader/Screens/Home/HomeViewController.swift +++ b/Reader/Screens/Home/HomeViewController.swift @@ -198,6 +198,28 @@ class HomeViewController: UIViewController { } } + private func itemsViewController(for item: Item) -> ItemsViewController { + let vc = ItemsViewController(fetchRequest: item.idFetchRequest, fervorController: fervorController) + vc.title = item.title + vc.delegate = itemsDelegate + switch item { + case .all: + vc.userActivity = .readAll() + case .unread: + vc.userActivity = .readUnread() + case .group(let group): + break + case .feed(let feed): + break + } + return vc + } + + func selectItem(_ item: Item) { + navigationController!.popToRootViewController(animated: false) + navigationController!.pushViewController(itemsViewController(for: item), animated: false) + } + } extension HomeViewController { @@ -236,21 +258,6 @@ extension HomeViewController { } } - var fetchRequest: NSFetchRequest { - let req = Reader.Item.fetchRequest() - switch self { - case .unread: - req.predicate = NSPredicate(format: "read = NO") - case .all: - break - case .group(let group): - req.predicate = NSPredicate(format: "feed in %@", group.feeds!) - case .feed(let feed): - req.predicate = NSPredicate(format: "feed = %@", feed) - } - return req - } - var idFetchRequest: NSFetchRequest { let req = NSFetchRequest(entityName: "Item") req.resultType = .managedObjectIDResultType @@ -307,10 +314,7 @@ extension HomeViewController: UICollectionViewDelegate { guard let item = dataSource.itemIdentifier(for: indexPath) else { return } - let vc = ItemsViewController(fetchRequest: item.idFetchRequest, fervorController: fervorController) - vc.title = item.title - vc.delegate = itemsDelegate - show(vc, sender: nil) + show(itemsViewController(for: item), sender: nil) UISelectionFeedbackGenerator().selectionChanged() } @@ -318,8 +322,8 @@ extension HomeViewController: UICollectionViewDelegate { guard let item = dataSource.itemIdentifier(for: indexPath) else { return nil } - return UIContextMenuConfiguration(identifier: nil, previewProvider: { - return ItemsViewController(fetchRequest: item.idFetchRequest, fervorController: self.fervorController) + return UIContextMenuConfiguration(identifier: nil, previewProvider: { [unowned self] in + return self.itemsViewController(for: item) }, actionProvider: nil) } diff --git a/Reader/UserActivities.swift b/Reader/UserActivities.swift index 305fd32..15ab040 100644 --- a/Reader/UserActivities.swift +++ b/Reader/UserActivities.swift @@ -12,6 +12,17 @@ extension NSUserActivity { static let preferencesType = "net.shadowfacts.Reader.activity.preferences" static let addAccountType = "net.shadowfacts.Reader.activity.add-account" static let activateAccountType = "net.shadowfacts.Reader.activity.activate-account" + static let readUnreadType = "net.shadowfacts.Reader.activity.read-unread" + static let readAllType = "net.shadowfacts.Reader.activity.read-all" + + func accountID() -> Data? { + if [NSUserActivity.addAccountType].contains(self.activityType), + let id = self.userInfo?["accountID"] as? Data { + return id + } else { + return nil + } + } static func preferences() -> NSUserActivity { return NSUserActivity(activityType: preferencesType) @@ -24,9 +35,25 @@ extension NSUserActivity { static func activateAccount(_ account: LocalData.Account) -> NSUserActivity { let activity = NSUserActivity(activityType: activateAccountType) activity.userInfo = [ - "accountID": account.id.uuidString + "accountID": account.id, ] return activity } + static func readUnread() -> NSUserActivity { + let activity = NSUserActivity(activityType: readUnreadType) + activity.isEligibleForHandoff = true + activity.isEligibleForPrediction = true + activity.title = "Show unread articles" + return activity + } + + static func readAll() -> NSUserActivity { + let activity = NSUserActivity(activityType: readAllType) + activity.isEligibleForHandoff = true + activity.isEligibleForPrediction = true + activity.title = "Show all articles" + return activity + } + }