mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-31 03:05:00 +01:00
Moved expanded state away from Node so that it won't get lost on rebuilds. Issue #1346
This commit is contained in:
parent
cd493730b1
commit
6a56936850
@ -84,6 +84,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public var isDeleted = false
|
||||
|
||||
public var containerID: ContainerIdentifier? {
|
||||
return ContainerIdentifier.account(accountID)
|
||||
}
|
||||
|
||||
public var account: Account? {
|
||||
return self
|
||||
}
|
||||
|
@ -33,6 +33,7 @@
|
||||
5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; };
|
||||
51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; };
|
||||
51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; };
|
||||
51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; };
|
||||
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
|
||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; };
|
||||
51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; };
|
||||
@ -235,6 +236,7 @@
|
||||
518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = "<group>"; };
|
||||
51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = "<group>"; };
|
||||
51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
||||
51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = "<group>"; };
|
||||
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
||||
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
||||
51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = "<group>"; };
|
||||
@ -535,6 +537,7 @@
|
||||
84F73CF0202788D80000BCEF /* ArticleFetcher.swift */,
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */,
|
||||
8419740D1F6DD25F006346C4 /* Container.swift */,
|
||||
51BFDECD238B508D00216323 /* ContainerIdentifier.swift */,
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
||||
51BC8FCB237EC055004F8B56 /* Feed.swift */,
|
||||
@ -1039,6 +1042,7 @@
|
||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||
9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */,
|
||||
51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */,
|
||||
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
||||
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */,
|
||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||
|
@ -16,7 +16,7 @@ extension Notification.Name {
|
||||
public static let ChildrenDidChange = Notification.Name("ChildrenDidChange")
|
||||
}
|
||||
|
||||
public protocol Container: class {
|
||||
public protocol Container: class, ContainerIdentifiable {
|
||||
|
||||
var account: Account? { get }
|
||||
var topLevelWebFeeds: Set<WebFeed> { get set }
|
||||
|
19
Frameworks/Account/ContainerIdentifier.swift
Normal file
19
Frameworks/Account/ContainerIdentifier.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// ContainerIdentifier.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 11/24/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ContainerIdentifiable {
|
||||
var containerID: ContainerIdentifier? { get }
|
||||
}
|
||||
|
||||
public enum ContainerIdentifier: Hashable {
|
||||
case smartFeedController
|
||||
case account(String) // accountID
|
||||
case folder(String, String) // accountID, folderName
|
||||
}
|
@ -16,6 +16,14 @@ public final class Folder: Feed, Renamable, Container, Hashable {
|
||||
return .read
|
||||
}
|
||||
|
||||
public var containerID: ContainerIdentifier? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
return nil
|
||||
}
|
||||
return ContainerIdentifier.folder(accountID, nameForDisplay)
|
||||
}
|
||||
|
||||
public var feedID: FeedIdentifier? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
|
@ -10,7 +10,11 @@ import Foundation
|
||||
import RSCore
|
||||
import Account
|
||||
|
||||
final class SmartFeedsController: DisplayNameProvider {
|
||||
final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
|
||||
|
||||
var containerID: ContainerIdentifier? {
|
||||
return ContainerIdentifier.smartFeedController
|
||||
}
|
||||
|
||||
public static let shared = SmartFeedsController()
|
||||
let nameForDisplay = NSLocalizedString("Smart Feeds", comment: "Smart Feeds group title")
|
||||
|
@ -40,7 +40,6 @@ private extension WebFeedTreeControllerDelegate {
|
||||
let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared)
|
||||
smartFeedsNode.canHaveChildNodes = true
|
||||
smartFeedsNode.isGroupItem = true
|
||||
smartFeedsNode.isExpanded = true
|
||||
topLevelNodes.append(smartFeedsNode)
|
||||
}
|
||||
|
||||
@ -137,7 +136,6 @@ private extension WebFeedTreeControllerDelegate {
|
||||
let accountNode = parent.existingOrNewChildNode(with: account)
|
||||
accountNode.canHaveChildNodes = true
|
||||
accountNode.isGroupItem = true
|
||||
accountNode.isExpanded = true
|
||||
return accountNode
|
||||
}
|
||||
return nodes
|
||||
|
@ -191,7 +191,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
headerView.tag = section
|
||||
headerView.disclosureExpanded = sectionNode.isExpanded
|
||||
headerView.disclosureExpanded = coordinator.isExpanded(sectionNode)
|
||||
|
||||
if section == tableView.numberOfSections - 1 {
|
||||
headerView.isLastSection = true
|
||||
@ -334,7 +334,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
// If this is a folder and isn't expanded or doesn't have any entries, let the users drop on it
|
||||
if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !destNode.isExpanded) {
|
||||
if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !coordinator.isExpanded(destNode)) {
|
||||
return proposedDestinationIndexPath
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
return
|
||||
}
|
||||
|
||||
if sectionNode.isExpanded {
|
||||
if coordinator.isExpanded(sectionNode) {
|
||||
headerView.disclosureExpanded = false
|
||||
coordinator.collapse(sectionNode)
|
||||
self.applyChanges(animated: true)
|
||||
@ -520,7 +520,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
return
|
||||
}
|
||||
|
||||
if !sectionNode.isExpanded {
|
||||
if !coordinator.isExpanded(sectionNode) {
|
||||
coordinator.expand(sectionNode)
|
||||
self.applyChanges(animated: true) {
|
||||
completion?()
|
||||
@ -687,7 +687,7 @@ private extension MasterFeedViewController {
|
||||
} else {
|
||||
cell.indentationLevel = 1
|
||||
}
|
||||
cell.setDisclosure(isExpanded: node.isExpanded, animated: false)
|
||||
cell.setDisclosure(isExpanded: coordinator.isExpanded(node), animated: false)
|
||||
cell.isDisclosureAvailable = node.canHaveChildNodes
|
||||
|
||||
cell.name = nameFor(node)
|
||||
|
@ -66,6 +66,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
private let fetchRequestQueue = FetchRequestQueue()
|
||||
|
||||
private var animatingChanges = false
|
||||
private var expandedTable = Set<ContainerIdentifier>()
|
||||
private var shadowTable = [[Node]]()
|
||||
private var lastSearchString = ""
|
||||
private var lastSearchScope: SearchScope? = nil
|
||||
@ -269,7 +270,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
super.init()
|
||||
|
||||
for _ in treeController.rootNode.childNodes {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
markExpanded(sectionNode)
|
||||
shadowTable.append([Node]())
|
||||
}
|
||||
|
||||
@ -393,15 +395,59 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
@objc func accountStateDidChange(_ note: Notification) {
|
||||
updateForAccountChanges()
|
||||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndReplaceArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles()
|
||||
self.rebuildBackingStores()
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func userDidAddAccount(_ note: Notification) {
|
||||
updateForAccountChanges()
|
||||
let expandNewAccount = {
|
||||
if let account = note.userInfo?[Account.UserInfoKey.account] as? Account,
|
||||
let node = self.treeController.rootNode.childNodeRepresentingObject(account) {
|
||||
self.markExpanded(node)
|
||||
}
|
||||
}
|
||||
|
||||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndReplaceArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles()
|
||||
self.rebuildBackingStores() {
|
||||
expandNewAccount()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores() {
|
||||
expandNewAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func userDidDeleteAccount(_ note: Notification) {
|
||||
updateForAccountChanges()
|
||||
let cleanupAccount = {
|
||||
if let account = note.userInfo?[Account.UserInfoKey.account] as? Account,
|
||||
let node = self.treeController.rootNode.childNodeRepresentingObject(account) {
|
||||
self.unmarkExpanded(node)
|
||||
}
|
||||
}
|
||||
|
||||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndReplaceArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles()
|
||||
self.rebuildBackingStores() {
|
||||
cleanupAccount()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores() {
|
||||
cleanupAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func userDefaultsDidChange(_ note: Notification) {
|
||||
@ -469,9 +515,28 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
articleReadFilterType = .read
|
||||
refreshTimeline()
|
||||
}
|
||||
|
||||
func markExpanded(_ node: Node) {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
expandedTable.insert(containerID)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarkExpanded(_ node: Node) {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
expandedTable.remove(containerID)
|
||||
}
|
||||
}
|
||||
|
||||
func isExpanded(_ node: Node) -> Bool {
|
||||
if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID {
|
||||
return expandedTable.contains(containerID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func expand(_ node: Node) {
|
||||
node.isExpanded = true
|
||||
markExpanded(node)
|
||||
animatingChanges = true
|
||||
rebuildShadowTable()
|
||||
animatingChanges = false
|
||||
@ -479,10 +544,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
func expandAllSectionsAndFolders() {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
sectionNode.isExpanded = true
|
||||
markExpanded(sectionNode)
|
||||
for topLevelNode in sectionNode.childNodes {
|
||||
if topLevelNode.representedObject is Folder {
|
||||
topLevelNode.isExpanded = true
|
||||
markExpanded(topLevelNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,7 +557,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
func collapse(_ node: Node) {
|
||||
node.isExpanded = false
|
||||
unmarkExpanded(node)
|
||||
animatingChanges = true
|
||||
rebuildShadowTable()
|
||||
animatingChanges = false
|
||||
@ -500,10 +565,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
func collapseAllFolders() {
|
||||
for sectionNode in treeController.rootNode.childNodes {
|
||||
sectionNode.isExpanded = true
|
||||
unmarkExpanded(sectionNode)
|
||||
for topLevelNode in sectionNode.childNodes {
|
||||
if topLevelNode.representedObject is Folder {
|
||||
topLevelNode.isExpanded = true
|
||||
unmarkExpanded(topLevelNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1073,17 +1138,6 @@ private extension SceneCoordinator {
|
||||
unreadCount = count
|
||||
}
|
||||
|
||||
func updateForAccountChanges() {
|
||||
if timelineFetcherContainsAnyPseudoFeed() {
|
||||
fetchAndReplaceArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles()
|
||||
self.rebuildBackingStores()
|
||||
}
|
||||
} else {
|
||||
rebuildBackingStores()
|
||||
}
|
||||
}
|
||||
|
||||
func rebuildBackingStores(_ updateExpandedNodes: (() -> Void)? = nil) {
|
||||
if !animatingChanges && !BatchUpdate.shared.isPerforming {
|
||||
treeController.rebuild()
|
||||
@ -1101,10 +1155,10 @@ private extension SceneCoordinator {
|
||||
var result = [Node]()
|
||||
let sectionNode = treeController.rootNode.childAtIndex(i)!
|
||||
|
||||
if sectionNode.isExpanded {
|
||||
if isExpanded(sectionNode) {
|
||||
for node in sectionNode.childNodes {
|
||||
result.append(node)
|
||||
if node.isExpanded {
|
||||
if isExpanded(node) {
|
||||
for child in node.childNodes {
|
||||
result.append(child)
|
||||
}
|
||||
@ -1263,7 +1317,7 @@ private extension SceneCoordinator {
|
||||
return true
|
||||
}
|
||||
|
||||
if node.isExpanded {
|
||||
if isExpanded(node) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -1374,7 +1428,7 @@ private extension SceneCoordinator {
|
||||
return
|
||||
}
|
||||
|
||||
if node.isExpanded {
|
||||
if isExpanded(node) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a041d4fc0e45077d28386e7efe4fca3a175584ad
|
||||
Subproject commit 2fc9b9cff60032a272303ff6d6df5b39ec297179
|
Loading…
x
Reference in New Issue
Block a user