Make handling of programmatic feed selection when filtered or collapsed more consistent. Issues #1788 and #1792
This commit is contained in:
parent
6c5f0cc8b6
commit
03c1ed2625
|
@ -14,6 +14,13 @@ final class FetchRequestQueue {
|
|||
|
||||
private var pendingRequests = [FetchRequestOperation]()
|
||||
private var currentRequest: FetchRequestOperation? = nil
|
||||
|
||||
var isAnyCurrentRequest: Bool {
|
||||
if let currentRequest = currentRequest {
|
||||
return !currentRequest.isCanceled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cancelAllRequests() {
|
||||
precondition(Thread.isMainThread)
|
||||
|
|
|
@ -58,7 +58,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
|
||||
|
@ -149,13 +148,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
discloseFeed(webFeed, animations: [.scroll, .navigation])
|
||||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
resetEstimatedRowHeight()
|
||||
applyChanges(animated: false)
|
||||
|
@ -335,7 +327,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
becomeFirstResponder()
|
||||
coordinator.selectFeed(indexPath, animations: [.navigation, .select, .scroll])
|
||||
coordinator.selectFeed(indexPath: indexPath, animations: [.navigation, .select, .scroll])
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
|
||||
|
@ -545,7 +537,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
|
||||
func reloadFeeds(initialLoad: Bool) {
|
||||
func reloadFeeds(initialLoad: Bool, completion: (() -> Void)? = nil) {
|
||||
updateUI()
|
||||
|
||||
// We have to reload all the visible cells because if we got here by doing a table cell move,
|
||||
|
@ -553,75 +545,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
// drops on a "folder" that should cause the dropped cell to disappear.
|
||||
applyChanges(animated: !initialLoad) { [weak self] in
|
||||
if !initialLoad {
|
||||
self?.reloadAllVisibleCells()
|
||||
self?.reloadAllVisibleCells(completion: completion)
|
||||
} else {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSectionIsExpanded(_ sectionIndex: Int, completion: (() -> Void)? = nil) {
|
||||
guard let sectionNode = coordinator.rootNode.childAtIndex(sectionIndex) else {
|
||||
return
|
||||
}
|
||||
|
||||
if !coordinator.isExpanded(sectionNode) {
|
||||
coordinator.expand(sectionNode)
|
||||
self.applyChanges(animated: true) {
|
||||
completion?()
|
||||
}
|
||||
} else {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func discloseFeed(_ webFeed: WebFeed, animations: Animations, completion: (() -> Void)? = nil) {
|
||||
|
||||
func discloseFeedInAccount() {
|
||||
guard let node = coordinator.rootNode.descendantNodeRepresentingObject(webFeed as AnyObject) else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
if let indexPath = dataSource.indexPath(for: node) {
|
||||
coordinator.selectFeed(indexPath, animations: animations) {
|
||||
completion?()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// It wasn't already visable, so expand its folder and try again
|
||||
guard let parent = node.parent, parent.representedObject is Folder else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
coordinator.expand(parent)
|
||||
reloadNode(parent)
|
||||
|
||||
applyChanges(animated: true, adjustScroll: true) { [weak self] in
|
||||
if let indexPath = self?.dataSource.indexPath(for: node) {
|
||||
self?.coordinator.selectFeed(indexPath, animations: animations) {
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the account for the feed is collapsed, expand it
|
||||
if let account = webFeed.account,
|
||||
let accountNode = coordinator.rootNode.childNodeRepresentingObject(account as AnyObject),
|
||||
!coordinator.isExpanded(accountNode) {
|
||||
|
||||
coordinator.expand(accountNode)
|
||||
applyChanges(animated: true) {
|
||||
discloseFeedInAccount()
|
||||
}
|
||||
|
||||
} else {
|
||||
discloseFeedInAccount()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func focus() {
|
||||
becomeFirstResponder()
|
||||
}
|
||||
|
@ -836,16 +766,17 @@ private extension MasterFeedViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private func reloadAllVisibleCells() {
|
||||
private func reloadAllVisibleCells(completion: (() -> Void)? = nil) {
|
||||
let visibleNodes = tableView.indexPathsForVisibleRows!.compactMap { return dataSource.itemIdentifier(for: $0) }
|
||||
reloadCells(visibleNodes)
|
||||
reloadCells(visibleNodes, completion: completion)
|
||||
}
|
||||
|
||||
private func reloadCells(_ nodes: [Node]) {
|
||||
private func reloadCells(_ nodes: [Node], completion: (() -> Void)? = nil) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reloadItems(nodes)
|
||||
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
||||
self?.restoreSelectionIfNecessary(adjustScroll: false)
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1224,7 +1155,7 @@ private extension MasterFeedViewController {
|
|||
deleteCommand.perform()
|
||||
|
||||
if indexPath == coordinator.currentFeedIndexPath {
|
||||
coordinator.selectFeed(nil)
|
||||
coordinator.selectFeed(indexPath: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -774,7 +774,7 @@ private extension MasterTimelineViewController {
|
|||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(webFeed, animations: [.scroll, .navigation])
|
||||
self?.coordinator.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
@ -785,7 +785,7 @@ private extension MasterTimelineViewController {
|
|||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(webFeed, animations: [.scroll, .navigation])
|
||||
self?.coordinator.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
|
||||
completion(true)
|
||||
}
|
||||
return action
|
||||
|
|
|
@ -298,6 +298,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddAccount(_:)), name: .UserDidAddAccount, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidDeleteAccount(_:)), name: .UserDidDeleteAccount, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(downloadArticlesDidUpdateUnreadCounts(_:)), name: .DownloadArticlesDidUpdateUnreadCounts, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
|
||||
|
@ -360,27 +361,27 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
func handle(_ activity: NSUserActivity, initialLoad: Bool) {
|
||||
selectFeed(nil) {
|
||||
func handle(_ activity: NSUserActivity) {
|
||||
selectFeed(indexPath: nil) {
|
||||
guard let activityType = ActivityType(rawValue: activity.activityType) else { return }
|
||||
switch activityType {
|
||||
case .restoration:
|
||||
break
|
||||
case .selectFeed:
|
||||
self.handleSelectFeed(activity.userInfo, initialLoad: initialLoad)
|
||||
self.handleSelectFeed(activity.userInfo)
|
||||
case .nextUnread:
|
||||
self.selectFirstUnreadInAllUnread()
|
||||
case .readArticle:
|
||||
self.handleReadArticle(activity.userInfo, initialLoad: initialLoad)
|
||||
self.handleReadArticle(activity.userInfo)
|
||||
case .addFeedIntent:
|
||||
self.showAdd(.feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle(_ response: UNNotificationResponse, initialLoad: Bool) {
|
||||
func handle(_ response: UNNotificationResponse) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
handleReadArticle(userInfo, initialLoad: initialLoad)
|
||||
handleReadArticle(userInfo)
|
||||
}
|
||||
|
||||
func configurePanelMode(for size: CGSize) {
|
||||
|
@ -404,15 +405,16 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
func selectFirstUnreadInAllUnread() {
|
||||
masterFeedViewController.ensureSectionIsExpanded(0) {
|
||||
self.selectFeed(IndexPath(row: 1, section: 0)) {
|
||||
expand(SmartFeedsController.shared)
|
||||
self.ensureFeedIsAvailableToSelect(SmartFeedsController.shared.unreadFeed) {
|
||||
self.selectFeed(SmartFeedsController.shared.unreadFeed) {
|
||||
self.selectFirstUnreadArticleInTimeline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showSearch() {
|
||||
selectFeed(nil) {
|
||||
selectFeed(indexPath: nil) {
|
||||
self.installTimelineControllerIfNecessary(animated: false)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||
self.masterTimelineViewController!.showSearchAll()
|
||||
|
@ -443,14 +445,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return
|
||||
}
|
||||
|
||||
for section in shadowTable {
|
||||
for node in section {
|
||||
if let feed = node.representedObject as? Feed, let feedID = feed.feedID {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addShadowTableToFilterExceptions()
|
||||
rebuildBackingStores()
|
||||
treeControllerDelegate.resetFilterExceptions()
|
||||
}
|
||||
|
@ -490,14 +485,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndMergeArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles(resetScroll: false)
|
||||
self.rebuildBackingStores() {
|
||||
expandNewlyActivatedAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: expandNewlyActivatedAccount)
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores() {
|
||||
expandNewlyActivatedAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: expandNewlyActivatedAccount)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -513,14 +504,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndMergeArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles(resetScroll: false)
|
||||
self.rebuildBackingStores() {
|
||||
expandNewAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: expandNewAccount)
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores() {
|
||||
expandNewAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: expandNewAccount)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,17 +522,20 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndMergeArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles(resetScroll: false)
|
||||
self.rebuildBackingStores() {
|
||||
cleanupAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: cleanupAccount)
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores() {
|
||||
cleanupAccount()
|
||||
}
|
||||
self.rebuildBackingStores(updateExpandedNodes: cleanupAccount)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
discloseWebFeed(webFeed, animations: [.scroll, .navigation])
|
||||
}
|
||||
|
||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||
self.sortDirection = AppDefaults.timelineSortDirection
|
||||
self.groupByFeed = AppDefaults.timelineGroupByFeed
|
||||
|
@ -567,7 +557,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
|
||||
@objc func willEnterForeground(_ note: Notification) {
|
||||
queueFetchAndMergeArticles()
|
||||
// Don't interfere with any fetch requests that we may have initiated before the app was returned to the foreground.
|
||||
// For example if you select Next Unread from the Home Screen Quick actions, you can start a request before we are
|
||||
// in the foreground.
|
||||
if !fetchRequestQueue.isAnyCurrentRequest {
|
||||
queueFetchAndMergeArticles()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: API
|
||||
|
@ -637,20 +632,35 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
refreshTimeline(resetScroll: false)
|
||||
}
|
||||
|
||||
func isExpanded(_ node: Node) -> Bool {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
func isExpanded(_ containerIdentifiable: ContainerIdentifiable) -> Bool {
|
||||
if let containerID = containerIdentifiable.containerID {
|
||||
return expandedTable.contains(containerID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func expand(_ node: Node) {
|
||||
markExpanded(node)
|
||||
func isExpanded(_ node: Node) -> Bool {
|
||||
if let containerIdentifiable = node.representedObject as? ContainerIdentifiable {
|
||||
return isExpanded(containerIdentifiable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func expand(_ containerIdentifiable: ContainerIdentifiable) {
|
||||
guard !isExpanded(containerIdentifiable) else { return }
|
||||
|
||||
markExpanded(containerIdentifiable)
|
||||
animatingChanges = true
|
||||
rebuildShadowTable()
|
||||
animatingChanges = false
|
||||
}
|
||||
|
||||
func expand(_ node: Node) {
|
||||
if let containerIdentifiable = node.representedObject as? ContainerIdentifiable {
|
||||
expand(containerIdentifiable)
|
||||
}
|
||||
}
|
||||
|
||||
func expandAllSectionsAndFolders() {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
markExpanded(sectionNode)
|
||||
|
@ -695,7 +705,18 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return indexPathFor(node)
|
||||
}
|
||||
|
||||
func selectFeed(_ indexPath: IndexPath?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) {
|
||||
func selectFeed(_ feed: Feed?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) {
|
||||
let indexPath: IndexPath? = {
|
||||
if let feed = feed, let indexPath = indexPathFor(feed as AnyObject) {
|
||||
return indexPath
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
selectFeed(indexPath: indexPath, animations: animations, deselectArticle: deselectArticle, completion: completion)
|
||||
}
|
||||
|
||||
func selectFeed(indexPath: IndexPath?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) {
|
||||
guard indexPath != currentFeedIndexPath else {
|
||||
completion?()
|
||||
return
|
||||
|
@ -732,31 +753,34 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
|
||||
func selectPrevFeed() {
|
||||
if let indexPath = prevFeedIndexPath {
|
||||
selectFeed(indexPath, animations: [.navigation, .scroll])
|
||||
selectFeed(indexPath: indexPath, animations: [.navigation, .scroll])
|
||||
}
|
||||
}
|
||||
|
||||
func selectNextFeed() {
|
||||
if let indexPath = nextFeedIndexPath {
|
||||
selectFeed(indexPath, animations: [.navigation, .scroll])
|
||||
selectFeed(indexPath: indexPath, animations: [.navigation, .scroll])
|
||||
}
|
||||
}
|
||||
|
||||
func selectTodayFeed() {
|
||||
masterFeedViewController?.ensureSectionIsExpanded(0) {
|
||||
self.selectFeed(IndexPath(row: 0, section: 0), animations: [.navigation, .scroll])
|
||||
expand(SmartFeedsController.shared)
|
||||
self.ensureFeedIsAvailableToSelect(SmartFeedsController.shared.todayFeed) {
|
||||
self.selectFeed(SmartFeedsController.shared.todayFeed, animations: [.navigation, .scroll])
|
||||
}
|
||||
}
|
||||
|
||||
func selectAllUnreadFeed() {
|
||||
masterFeedViewController?.ensureSectionIsExpanded(0) {
|
||||
self.selectFeed(IndexPath(row: 1, section: 0), animations: [.navigation, .scroll])
|
||||
expand(SmartFeedsController.shared)
|
||||
self.ensureFeedIsAvailableToSelect(SmartFeedsController.shared.unreadFeed) {
|
||||
self.selectFeed(SmartFeedsController.shared.unreadFeed, animations: [.navigation, .scroll])
|
||||
}
|
||||
}
|
||||
|
||||
func selectStarredFeed() {
|
||||
masterFeedViewController?.ensureSectionIsExpanded(0) {
|
||||
self.selectFeed(IndexPath(row: 2, section: 0), animations: [.navigation, .scroll])
|
||||
expand(SmartFeedsController.shared)
|
||||
self.ensureFeedIsAvailableToSelect(SmartFeedsController.shared.starredFeed) {
|
||||
self.selectFeed(SmartFeedsController.shared.starredFeed, animations: [.navigation, .scroll])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1009,14 +1033,35 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return timelineFeed == feed
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: WebFeed, animations: Animations = [], completion: (() -> Void)? = nil) {
|
||||
func discloseWebFeed(_ webFeed: WebFeed, animations: Animations = [], completion: (() -> Void)? = nil) {
|
||||
if isSearching {
|
||||
masterTimelineViewController?.hideSearch()
|
||||
}
|
||||
|
||||
masterFeedViewController.discloseFeed(feed, animations: animations) {
|
||||
guard let account = webFeed.account else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(webFeed) })
|
||||
|
||||
expand(account)
|
||||
if let parentFolder = parentFolder {
|
||||
expand(parentFolder)
|
||||
}
|
||||
|
||||
if let webFeedFeedID = webFeed.feedID {
|
||||
self.treeControllerDelegate.addFilterException(webFeedFeedID)
|
||||
}
|
||||
if let parentFolderFeedID = parentFolder?.feedID {
|
||||
self.treeControllerDelegate.addFilterException(parentFolderFeedID)
|
||||
}
|
||||
|
||||
rebuildBackingStores() {
|
||||
self.treeControllerDelegate.resetFilterExceptions()
|
||||
self.selectFeed(webFeed, animations: animations, completion: completion)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func showStatusBar() {
|
||||
|
@ -1258,30 +1303,26 @@ private extension SceneCoordinator {
|
|||
_idToArticleDictionary = idDictionary
|
||||
articleDictionaryNeedsUpdate = false
|
||||
}
|
||||
|
||||
func rebuildBackingStores(initialLoad: Bool = false, updateExpandedNodes: (() -> Void)? = nil) {
|
||||
if !animatingChanges && !BatchUpdate.shared.isPerforming {
|
||||
|
||||
addCurrentFeedToFilterExeptionsIfNecessary()
|
||||
treeController.rebuild()
|
||||
treeControllerDelegate.resetFilterExceptions()
|
||||
|
||||
updateExpandedNodes?()
|
||||
rebuildShadowTable()
|
||||
masterFeedViewController.reloadFeeds(initialLoad: initialLoad)
|
||||
|
||||
|
||||
func ensureFeedIsAvailableToSelect(_ feed: Feed, completion: @escaping () -> Void) {
|
||||
addToFilterExeptionsIfNecessary(feed)
|
||||
addShadowTableToFilterExceptions()
|
||||
|
||||
rebuildBackingStores() {
|
||||
self.treeControllerDelegate.resetFilterExceptions()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func addCurrentFeedToFilterExeptionsIfNecessary() {
|
||||
if isReadFeedsFiltered, let feedID = timelineFeed?.feedID {
|
||||
if timelineFeed is SmartFeed {
|
||||
|
||||
func addToFilterExeptionsIfNecessary(_ feed: Feed?) {
|
||||
if isReadFeedsFiltered, let feedID = feed?.feedID {
|
||||
if feed is SmartFeed {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
} else if let folderFeed = timelineFeed as? Folder {
|
||||
} else if let folderFeed = feed as? Folder {
|
||||
if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
} else if let webFeed = timelineFeed as? WebFeed {
|
||||
} else if let webFeed = feed as? WebFeed {
|
||||
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
addParentFolderToFilterExceptions(webFeed)
|
||||
|
@ -1299,7 +1340,31 @@ private extension SceneCoordinator {
|
|||
|
||||
treeControllerDelegate.addFilterException(folderFeedID)
|
||||
}
|
||||
|
||||
func addShadowTableToFilterExceptions() {
|
||||
for section in shadowTable {
|
||||
for node in section {
|
||||
if let feed = node.representedObject as? Feed, let feedID = feed.feedID {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rebuildBackingStores(initialLoad: Bool = false, updateExpandedNodes: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||
if !animatingChanges && !BatchUpdate.shared.isPerforming {
|
||||
|
||||
addToFilterExeptionsIfNecessary(timelineFeed)
|
||||
treeController.rebuild()
|
||||
treeControllerDelegate.resetFilterExceptions()
|
||||
|
||||
updateExpandedNodes?()
|
||||
rebuildShadowTable()
|
||||
masterFeedViewController.reloadFeeds(initialLoad: initialLoad, completion: completion)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func rebuildShadowTable() {
|
||||
shadowTable = [[Node]]()
|
||||
|
||||
|
@ -1405,18 +1470,30 @@ private extension SceneCoordinator {
|
|||
self.showIcons = false
|
||||
}
|
||||
|
||||
func markExpanded(_ node: Node) {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
func markExpanded(_ containerIdentifiable: ContainerIdentifiable) {
|
||||
if let containerID = containerIdentifiable.containerID {
|
||||
expandedTable.insert(containerID)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarkExpanded(_ node: Node) {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
func markExpanded(_ node: Node) {
|
||||
if let containerIdentifiable = node.representedObject as? ContainerIdentifiable {
|
||||
markExpanded(containerIdentifiable)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarkExpanded(_ containerIdentifiable: ContainerIdentifiable) {
|
||||
if let containerID = containerIdentifiable.containerID {
|
||||
expandedTable.remove(containerID)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarkExpanded(_ node: Node) {
|
||||
if let containerIdentifiable = node.representedObject as? ContainerIdentifiable {
|
||||
unmarkExpanded(containerIdentifiable)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Select Prev Unread
|
||||
|
||||
@discardableResult
|
||||
|
@ -1507,7 +1584,7 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
if unreadCountProvider.unreadCount > 0 {
|
||||
selectFeed(prevIndexPath, animations: [.scroll, .navigation])
|
||||
selectFeed(indexPath: prevIndexPath, animations: [.scroll, .navigation])
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1618,7 +1695,7 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
if unreadCountProvider.unreadCount > 0 {
|
||||
selectFeed(nextIndexPath, animations: [.scroll, .navigation], deselectArticle: false) {
|
||||
selectFeed(indexPath: nextIndexPath, animations: [.scroll, .navigation], deselectArticle: false) {
|
||||
self.currentArticle = nil
|
||||
completion(true)
|
||||
}
|
||||
|
@ -1660,7 +1737,7 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
func queueFetchAndMergeArticles() {
|
||||
fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticlesAsync))
|
||||
fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticlesAsync))
|
||||
}
|
||||
|
||||
@objc func fetchAndMergeArticlesAsync() {
|
||||
|
@ -1920,7 +1997,7 @@ private extension SceneCoordinator {
|
|||
]
|
||||
}
|
||||
|
||||
func handleSelectFeed(_ userInfo: [AnyHashable : Any]?, initialLoad: Bool) {
|
||||
func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) {
|
||||
guard let userInfo = userInfo,
|
||||
let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
|
||||
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else {
|
||||
|
@ -1930,23 +2007,18 @@ private extension SceneCoordinator {
|
|||
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||
masterFeedViewController.restoreSelection = true
|
||||
|
||||
func rebuildIfNecessary() {
|
||||
if !initialLoad && isReadFeedsFiltered {
|
||||
rebuildBackingStores()
|
||||
treeControllerDelegate.resetFilterExceptions()
|
||||
}
|
||||
}
|
||||
|
||||
switch feedIdentifier {
|
||||
|
||||
case .smartFeed:
|
||||
guard let smartFeed = SmartFeedsController.shared.find(by: feedIdentifier) else { return }
|
||||
|
||||
rebuildIfNecessary()
|
||||
|
||||
if let indexPath = indexPathFor(smartFeed) {
|
||||
selectFeed(indexPath) {
|
||||
self.masterFeedViewController.focus()
|
||||
|
||||
expand(SmartFeedsController.shared)
|
||||
rebuildBackingStores() {
|
||||
self.treeControllerDelegate.resetFilterExceptions()
|
||||
if let indexPath = self.indexPathFor(smartFeed) {
|
||||
self.selectFeed(indexPath: indexPath) {
|
||||
self.masterFeedViewController.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1954,14 +2026,20 @@ private extension SceneCoordinator {
|
|||
break
|
||||
|
||||
case .folder(let accountID, let folderName):
|
||||
rebuildIfNecessary()
|
||||
|
||||
guard let accountNode = findAccountNode(accountID: accountID), let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode) else {
|
||||
guard let accountNode = self.findAccountNode(accountID: accountID),
|
||||
let account = accountNode.representedObject as? Account else {
|
||||
return
|
||||
}
|
||||
if let indexPath = indexPathFor(folderNode) {
|
||||
selectFeed(indexPath) {
|
||||
self.masterFeedViewController.focus()
|
||||
|
||||
expand(account)
|
||||
|
||||
rebuildBackingStores() {
|
||||
self.treeControllerDelegate.resetFilterExceptions()
|
||||
|
||||
if let folderNode = self.findFolderNode(folderName: folderName, beginningAt: accountNode), let indexPath = self.indexPathFor(folderNode) {
|
||||
self.selectFeed(indexPath: indexPath) {
|
||||
self.masterFeedViewController.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1972,23 +2050,13 @@ private extension SceneCoordinator {
|
|||
return
|
||||
}
|
||||
|
||||
for folder in account.sortedFolders ?? [Folder]() {
|
||||
if folder.objectIsChild(webFeed), let folderFeedID = folder.feedID {
|
||||
treeControllerDelegate.addFilterException(folderFeedID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rebuildIfNecessary()
|
||||
|
||||
discloseFeed(webFeed) {
|
||||
self.discloseWebFeed(webFeed) {
|
||||
self.masterFeedViewController.focus()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func handleReadArticle(_ userInfo: [AnyHashable : Any]?, initialLoad: Bool) {
|
||||
func handleReadArticle(_ userInfo: [AnyHashable : Any]?) {
|
||||
guard let userInfo = userInfo else { return }
|
||||
|
||||
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
||||
|
@ -2007,25 +2075,11 @@ private extension SceneCoordinator {
|
|||
return
|
||||
}
|
||||
|
||||
guard let webFeed = account.existingWebFeed(withWebFeedID: webFeedID),
|
||||
let webFeedFeedID = webFeed.feedID else {
|
||||
return
|
||||
guard let webFeed = account.existingWebFeed(withWebFeedID: webFeedID) else {
|
||||
return
|
||||
}
|
||||
|
||||
treeControllerDelegate.addFilterException(webFeedFeedID)
|
||||
for folder in account.sortedFolders ?? [Folder]() {
|
||||
if folder.objectIsChild(webFeed), let folderFeedID = folder.feedID {
|
||||
treeControllerDelegate.addFilterException(folderFeedID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !initialLoad && isReadFeedsFiltered {
|
||||
rebuildBackingStores()
|
||||
treeControllerDelegate.resetFilterExceptions()
|
||||
}
|
||||
|
||||
discloseFeed(webFeed) {
|
||||
discloseWebFeed(webFeed) {
|
||||
self.selectArticleInCurrentFeed(articleID)
|
||||
}
|
||||
}
|
||||
|
@ -2043,7 +2097,7 @@ private extension SceneCoordinator {
|
|||
case .smartFeed:
|
||||
guard let smartFeed = SmartFeedsController.shared.find(by: feedIdentifier) else { return false }
|
||||
if let indexPath = indexPathFor(smartFeed) {
|
||||
selectFeed(indexPath) {
|
||||
selectFeed(indexPath: indexPath) {
|
||||
self.selectArticleInCurrentFeed(articleID)
|
||||
}
|
||||
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||
|
@ -2110,7 +2164,7 @@ private extension SceneCoordinator {
|
|||
|
||||
func selectFeedAndArticle(feedNode: Node, articleID: String) -> Bool {
|
||||
if let feedIndexPath = indexPathFor(feedNode) {
|
||||
selectFeed(feedIndexPath) {
|
||||
selectFeed(indexPath: feedIndexPath) {
|
||||
self.selectArticleInCurrentFeed(articleID)
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -33,12 +33,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
if let notificationResponse = connectionOptions.notificationResponse {
|
||||
window!.makeKeyAndVisible()
|
||||
coordinator.handle(notificationResponse, initialLoad: true)
|
||||
coordinator.handle(notificationResponse)
|
||||
return
|
||||
}
|
||||
|
||||
if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
|
||||
coordinator.handle(userActivity, initialLoad: true)
|
||||
coordinator.handle(userActivity)
|
||||
}
|
||||
|
||||
window!.makeKeyAndVisible()
|
||||
|
@ -52,7 +52,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
||||
appDelegate.resumeDatabaseProcessingIfNecessary()
|
||||
coordinator.handle(userActivity, initialLoad: false)
|
||||
coordinator.handle(userActivity)
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
|
@ -74,7 +74,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
func handle(_ response: UNNotificationResponse) {
|
||||
appDelegate.resumeDatabaseProcessingIfNecessary()
|
||||
coordinator.handle(response, initialLoad: false)
|
||||
coordinator.handle(response)
|
||||
}
|
||||
|
||||
func suspend() {
|
||||
|
|
Loading…
Reference in New Issue