diff --git a/Frameworks/Account/ContainerIdentifier.swift b/Frameworks/Account/ContainerIdentifier.swift
index 9745a80db..47d87e4b6 100644
--- a/Frameworks/Account/ContainerIdentifier.swift
+++ b/Frameworks/Account/ContainerIdentifier.swift
@@ -16,4 +16,42 @@ public enum ContainerIdentifier: Hashable {
case smartFeedController
case account(String) // accountID
case folder(String, String) // accountID, folderName
+
+ public var userInfo: [AnyHashable: Any] {
+ switch self {
+ case .smartFeedController:
+ return [
+ "type": "smartFeedController"
+ ]
+ case .account(let accountID):
+ return [
+ "type": "account",
+ "accountID": accountID
+ ]
+ case .folder(let accountID, let folderName):
+ return [
+ "type": "folder",
+ "accountID": accountID,
+ "folderName": folderName
+ ]
+ }
+ }
+
+ public init?(userInfo: [AnyHashable: Any]) {
+ guard let type = userInfo["type"] as? String else { return nil }
+
+ switch type {
+ case "smartFeedController":
+ self = ContainerIdentifier.smartFeedController
+ case "account":
+ guard let accountID = userInfo["accountID"] as? String else { return nil }
+ self = ContainerIdentifier.account(accountID)
+ case "folder":
+ guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil }
+ self = ContainerIdentifier.folder(accountID, folderName)
+ default:
+ return nil
+ }
+ }
+
}
diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift
index 25403da8f..ef26b958a 100644
--- a/Shared/Activity/ActivityManager.swift
+++ b/Shared/Activity/ActivityManager.swift
@@ -21,11 +21,19 @@ class ActivityManager {
private var readingActivity: NSUserActivity?
private var readingArticle: Article?
- var stateRestorationActivity: NSUserActivity? {
- if readingActivity != nil {
- return readingActivity
+ var stateRestorationActivity: NSUserActivity {
+ if let activity = readingActivity {
+ return activity
}
- return selectingActivity
+
+ if let activity = selectingActivity {
+ return activity
+ }
+
+ let activity = NSUserActivity(activityType: ActivityType.restoration.rawValue)
+ activity.persistentIdentifier = UUID().uuidString
+ activity.becomeCurrent()
+ return activity
}
init() {
diff --git a/Shared/Activity/ActivityType.swift b/Shared/Activity/ActivityType.swift
index 921bccda5..53d8fe9d3 100644
--- a/Shared/Activity/ActivityType.swift
+++ b/Shared/Activity/ActivityType.swift
@@ -9,6 +9,7 @@
import Foundation
enum ActivityType: String {
+ case restoration = "Restoration"
case selectFeed = "SelectFeed"
case nextUnread = "NextUnread"
case readArticle = "ReadArticle"
diff --git a/Shared/UserInfoKey.swift b/Shared/UserInfoKey.swift
index 1318b0ea0..81f939766 100644
--- a/Shared/UserInfoKey.swift
+++ b/Shared/UserInfoKey.swift
@@ -23,4 +23,7 @@ struct UserInfoKey {
static let articlePath = "articlePath"
static let feedIdentifier = "feedIdentifier"
+ static let windowState = "windowState"
+ static let containerExpandedWindowState = "containerExpandedWindowState"
+
}
diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift
index 67dbbb1e0..df391180f 100644
--- a/iOS/MasterFeed/MasterFeedViewController.swift
+++ b/iOS/MasterFeed/MasterFeedViewController.swift
@@ -540,8 +540,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
if let indexPath = dataSource.indexPath(for: node) {
- coordinator.selectFeed(indexPath, animated: animated)
- completion?()
+ coordinator.selectFeed(indexPath, animated: animated) {
+ completion?()
+ }
return
}
diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist
index fa66f10ec..a03e1e166 100644
--- a/iOS/Resources/Info.plist
+++ b/iOS/Resources/Info.plist
@@ -60,6 +60,7 @@
Grant permission to save images from the article.
NSUserActivityTypes
+ Restoration
AddWebFeedIntent
NextUnread
ReadArticle
diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift
index 3463ebe9a..1ca436875 100644
--- a/iOS/SceneCoordinator.swift
+++ b/iOS/SceneCoordinator.swift
@@ -101,8 +101,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private let treeControllerDelegate = WebFeedTreeControllerDelegate()
private let treeController: TreeController
- var stateRestorationActivity: NSUserActivity? {
- return activityManager.stateRestorationActivity
+ var stateRestorationActivity: NSUserActivity {
+ let activity = activityManager.stateRestorationActivity
+ var userInfo = activity.userInfo == nil ? [AnyHashable: Any]() : activity.userInfo
+ userInfo![UserInfoKey.windowState] = windowState()
+ activity.userInfo = userInfo
+ return activity
}
var isRootSplitCollapsed: Bool {
@@ -315,11 +319,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return rootSplitViewController
}
+ func restoreWindowState(_ activity: NSUserActivity) {
+ if let windowState = activity.userInfo?[UserInfoKey.windowState] as? [AnyHashable: Any] {
+ restoreWindowState(windowState)
+ rebuildShadowTable()
+ masterFeedViewController.reloadFeeds()
+ }
+ }
+
func handle(_ activity: NSUserActivity) {
selectFeed(nil, animated: false) {
-
guard let activityType = ActivityType(rawValue: activity.activityType) else { return }
switch activityType {
+ case .restoration:
+ break
case .selectFeed:
self.handleSelectFeed(activity.userInfo)
case .nextUnread:
@@ -329,7 +342,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
case .addFeedIntent:
self.showAdd(.feed)
}
-
}
}
@@ -1744,6 +1756,20 @@ private extension SceneCoordinator {
// MARK: NSUserActivity
+ func windowState() -> [AnyHashable: Any] {
+ let containerIdentifierUserInfos = expandedTable.map( { $0.userInfo })
+ return [
+ UserInfoKey.containerExpandedWindowState: containerIdentifierUserInfos
+ ]
+ }
+
+ func restoreWindowState(_ windowState: [AnyHashable: Any]) {
+ if let containerIdentifierUserInfos = windowState[UserInfoKey.containerExpandedWindowState] as? [[AnyHashable: Any]] {
+ let containerIdentifers = containerIdentifierUserInfos.compactMap( { ContainerIdentifier(userInfo: $0) })
+ expandedTable = Set(containerIdentifers)
+ }
+ }
+
func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) {
guard let userInfo = userInfo,
let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any],
diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift
index 0a8868d1a..839517f07 100644
--- a/iOS/SceneDelegate.swift
+++ b/iOS/SceneDelegate.swift
@@ -23,6 +23,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window!.tintColor = AppAssets.primaryAccentColor
window!.rootViewController = coordinator.start(for: window!.frame.size)
+ if let stateRestorationActivity = session.stateRestorationActivity {
+ coordinator.restoreWindowState(stateRestorationActivity)
+ }
+
if let shortcutItem = connectionOptions.shortcutItem {
window!.makeKeyAndVisible()
handleShortcutItem(shortcutItem)