Persist and restore container expanded state across application launches. Issue #1361

This commit is contained in:
Maurice Parker 2019-11-26 16:33:11 -06:00
parent 3d1f68a877
commit 4a9e79cd1e
8 changed files with 92 additions and 10 deletions

View File

@ -16,4 +16,42 @@ public enum ContainerIdentifier: Hashable {
case smartFeedController case smartFeedController
case account(String) // accountID case account(String) // accountID
case folder(String, String) // accountID, folderName 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
}
}
} }

View File

@ -21,11 +21,19 @@ class ActivityManager {
private var readingActivity: NSUserActivity? private var readingActivity: NSUserActivity?
private var readingArticle: Article? private var readingArticle: Article?
var stateRestorationActivity: NSUserActivity? { var stateRestorationActivity: NSUserActivity {
if readingActivity != nil { if let activity = readingActivity {
return 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() { init() {

View File

@ -9,6 +9,7 @@
import Foundation import Foundation
enum ActivityType: String { enum ActivityType: String {
case restoration = "Restoration"
case selectFeed = "SelectFeed" case selectFeed = "SelectFeed"
case nextUnread = "NextUnread" case nextUnread = "NextUnread"
case readArticle = "ReadArticle" case readArticle = "ReadArticle"

View File

@ -23,4 +23,7 @@ struct UserInfoKey {
static let articlePath = "articlePath" static let articlePath = "articlePath"
static let feedIdentifier = "feedIdentifier" static let feedIdentifier = "feedIdentifier"
static let windowState = "windowState"
static let containerExpandedWindowState = "containerExpandedWindowState"
} }

View File

@ -540,8 +540,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
} }
if let indexPath = dataSource.indexPath(for: node) { if let indexPath = dataSource.indexPath(for: node) {
coordinator.selectFeed(indexPath, animated: animated) coordinator.selectFeed(indexPath, animated: animated) {
completion?() completion?()
}
return return
} }

View File

@ -60,6 +60,7 @@
<string>Grant permission to save images from the article.</string> <string>Grant permission to save images from the article.</string>
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>Restoration</string>
<string>AddWebFeedIntent</string> <string>AddWebFeedIntent</string>
<string>NextUnread</string> <string>NextUnread</string>
<string>ReadArticle</string> <string>ReadArticle</string>

View File

@ -101,8 +101,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private let treeControllerDelegate = WebFeedTreeControllerDelegate() private let treeControllerDelegate = WebFeedTreeControllerDelegate()
private let treeController: TreeController private let treeController: TreeController
var stateRestorationActivity: NSUserActivity? { var stateRestorationActivity: NSUserActivity {
return activityManager.stateRestorationActivity 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 { var isRootSplitCollapsed: Bool {
@ -315,11 +319,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
return rootSplitViewController 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) { func handle(_ activity: NSUserActivity) {
selectFeed(nil, animated: false) { selectFeed(nil, animated: false) {
guard let activityType = ActivityType(rawValue: activity.activityType) else { return } guard let activityType = ActivityType(rawValue: activity.activityType) else { return }
switch activityType { switch activityType {
case .restoration:
break
case .selectFeed: case .selectFeed:
self.handleSelectFeed(activity.userInfo) self.handleSelectFeed(activity.userInfo)
case .nextUnread: case .nextUnread:
@ -329,7 +342,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
case .addFeedIntent: case .addFeedIntent:
self.showAdd(.feed) self.showAdd(.feed)
} }
} }
} }
@ -1744,6 +1756,20 @@ private extension SceneCoordinator {
// MARK: NSUserActivity // 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]?) { func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) {
guard let userInfo = userInfo, guard let userInfo = userInfo,
let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any], let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any],

View File

@ -23,6 +23,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window!.tintColor = AppAssets.primaryAccentColor window!.tintColor = AppAssets.primaryAccentColor
window!.rootViewController = coordinator.start(for: window!.frame.size) window!.rootViewController = coordinator.start(for: window!.frame.size)
if let stateRestorationActivity = session.stateRestorationActivity {
coordinator.restoreWindowState(stateRestorationActivity)
}
if let shortcutItem = connectionOptions.shortcutItem { if let shortcutItem = connectionOptions.shortcutItem {
window!.makeKeyAndVisible() window!.makeKeyAndVisible()
handleShortcutItem(shortcutItem) handleShortcutItem(shortcutItem)