Move navigation state from MasterViewController to NavigationModelController

This commit is contained in:
Maurice Parker 2019-04-21 14:34:51 -05:00
parent c5a891234d
commit 3e1c772cba
4 changed files with 140 additions and 138 deletions

View File

@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; };
5126EE97226CB48A00C22AFC /* AppModelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* AppModelController.swift */; }; 5126EE97226CB48A00C22AFC /* NavigationModelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* NavigationModelController.swift */; };
5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; };
5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; };
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
@ -603,7 +603,7 @@
51121AA12265430A00BC0EC1 /* NetNewsWire_iOS_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOS_target.xcconfig; sourceTree = "<group>"; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOS_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOS_target.xcconfig; sourceTree = "<group>"; };
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = "<group>"; };
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; }; 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
5126EE96226CB48A00C22AFC /* AppModelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModelController.swift; sourceTree = "<group>"; }; 5126EE96226CB48A00C22AFC /* NavigationModelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModelController.swift; sourceTree = "<group>"; };
5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = "<group>"; }; 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = "<group>"; };
5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = "<group>"; }; 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = "<group>"; };
512E08F722688F7C00BDCFDD /* MasterTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTableViewSectionHeader.swift; sourceTree = "<group>"; }; 512E08F722688F7C00BDCFDD /* MasterTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTableViewSectionHeader.swift; sourceTree = "<group>"; };
@ -1514,7 +1514,7 @@
840D617E2029031C009BC708 /* AppDelegate.swift */, 840D617E2029031C009BC708 /* AppDelegate.swift */,
51C45254226507D200C03939 /* AppAssets.swift */, 51C45254226507D200C03939 /* AppAssets.swift */,
51C45255226507D200C03939 /* AppDefaults.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */,
5126EE96226CB48A00C22AFC /* AppModelController.swift */, 5126EE96226CB48A00C22AFC /* NavigationModelController.swift */,
51C4525D226508F600C03939 /* Master */, 51C4525D226508F600C03939 /* Master */,
51C4526D2265091600C03939 /* Timeline */, 51C4526D2265091600C03939 /* Timeline */,
51C4527D2265092C00C03939 /* Detail */, 51C4527D2265092C00C03939 /* Detail */,
@ -2149,7 +2149,7 @@
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */,
51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */,
51C4526B226508F600C03939 /* MasterViewController.swift in Sources */, 51C4526B226508F600C03939 /* MasterViewController.swift in Sources */,
5126EE97226CB48A00C22AFC /* AppModelController.swift in Sources */, 5126EE97226CB48A00C22AFC /* NavigationModelController.swift in Sources */,
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */,
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */,

View File

@ -17,15 +17,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
var undoableCommands = [UndoableCommand]() var undoableCommands = [UndoableCommand]()
var animatingChanges = false var animatingChanges = false
var expandedNodes = [Node]() let nmc = NavigationModelController()
var shadowTable = [[Node]]()
let appModelController = AppModelController()
let treeControllerDelegate = FeedTreeControllerDelegate()
lazy var treeController: TreeController = {
return TreeController(delegate: treeControllerDelegate)
}()
override var canBecomeFirstResponder: Bool { override var canBecomeFirstResponder: Bool {
return true return true
} }
@ -50,14 +42,6 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
refreshControl = UIRefreshControl() refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
// Default the sections to expanded and set up the shadow table
for section in treeController.rootNode.childNodes {
expandedNodes.append(section)
shadowTable.append([Node]())
}
rebuildShadowTable()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -100,8 +84,8 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
} }
if let account = representedObject as? Account { if let account = representedObject as? Account {
if let node = treeController.rootNode.childNodeRepresentingObject(account) { if let node = nmc.treeController.rootNode.childNodeRepresentingObject(account) {
let sectionIndex = treeController.rootNode.indexOfChild(node)! let sectionIndex = nmc.treeController.rootNode.indexOfChild(node)!
let headerView = tableView.headerView(forSection: sectionIndex) as! MasterTableViewSectionHeader let headerView = tableView.headerView(forSection: sectionIndex) as! MasterTableViewSectionHeader
headerView.unreadCount = account.unreadCount headerView.unreadCount = account.unreadCount
} }
@ -142,23 +126,23 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
@objc func userDidAddFeed(_ notification: Notification) { @objc func userDidAddFeed(_ notification: Notification) {
guard let feed = notification.userInfo?[UserInfoKey.feed], guard let feed = notification.userInfo?[UserInfoKey.feed],
let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else { let node = nmc.treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else {
return return
} }
if let indexPath = indexPathFor(node) { if let indexPath = nmc.indexPathFor(node) {
tableView.scrollToRow(at: indexPath, at: .middle, animated: true) tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
return return
} }
// It wasn't already visable, so expand its folder and try again // It wasn't already visable, so expand its folder and try again
guard let parent = node.parent, let indexPath = indexPathFor(parent) else { guard let parent = node.parent, let indexPath = nmc.indexPathFor(parent) else {
return return
} }
expand(indexPath) expand(indexPath)
if let indexPath = indexPathFor(node) { if let indexPath = nmc.indexPathFor(node) {
tableView.scrollToRow(at: indexPath, at: .middle, animated: true) tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
} }
@ -167,11 +151,11 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
// MARK: Table View // MARK: Table View
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return treeController.rootNode.numberOfChildNodes return nmc.treeController.rootNode.numberOfChildNodes
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shadowTable[section].count return nmc.shadowTable[section].count
} }
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -180,14 +164,14 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let nameProvider = treeController.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else { guard let nameProvider = nmc.treeController.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else {
return nil return nil
} }
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! MasterTableViewSectionHeader let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! MasterTableViewSectionHeader
headerView.name = nameProvider.nameForDisplay headerView.name = nameProvider.nameForDisplay
guard let sectionNode = treeController.rootNode.childAtIndex(section) else { guard let sectionNode = nmc.treeController.rootNode.childAtIndex(section) else {
return headerView return headerView
} }
@ -198,7 +182,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
} }
headerView.tag = section headerView.tag = section
headerView.disclosureExpanded = expandedNodes.contains(sectionNode) headerView.disclosureExpanded = nmc.expandedNodes.contains(sectionNode)
let tap = UITapGestureRecognizer(target: self, action:#selector(self.toggleSectionHeader(_:))) let tap = UITapGestureRecognizer(target: self, action:#selector(self.toggleSectionHeader(_:)))
headerView.addGestureRecognizer(tap) headerView.addGestureRecognizer(tap)
@ -219,7 +203,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterTableViewCell
guard let node = nodeFor(indexPath) else { guard let node = nmc.nodeFor(indexPath) else {
return cell return cell
} }
@ -229,7 +213,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
} }
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
guard let node = nodeFor(indexPath), !(node.representedObject is PseudoFeed) else { guard let node = nmc.nodeFor(indexPath), !(node.representedObject is PseudoFeed) else {
return false return false
} }
return true return true
@ -261,7 +245,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let node = nodeFor(indexPath) else { guard let node = nmc.nodeFor(indexPath) else {
assertionFailure() assertionFailure()
return return
} }
@ -269,21 +253,21 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
let timeline = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) let timeline = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self)
if let fetcher = node.representedObject as? ArticleFetcher { if let fetcher = node.representedObject as? ArticleFetcher {
appModelController.timelineFetcher = fetcher nmc.timelineFetcher = fetcher
} }
if let nameProvider = node.representedObject as? DisplayNameProvider { if let nameProvider = node.representedObject as? DisplayNameProvider {
timeline.title = nameProvider.nameForDisplay timeline.title = nameProvider.nameForDisplay
} }
timeline.appModelController = appModelController timeline.nmc = nmc
self.navigationController?.pushViewController(timeline, animated: true) self.navigationController?.pushViewController(timeline, animated: true)
} }
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
guard let node = nodeFor(indexPath) else { guard let node = nmc.nodeFor(indexPath) else {
return false return false
} }
return node.representedObject is Feed return node.representedObject is Feed
@ -299,13 +283,13 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
return proposedDestinationIndexPath return proposedDestinationIndexPath
}() }()
guard let draggedNode = nodeFor(sourceIndexPath), let destNode = nodeFor(destIndexPath), let parentNode = destNode.parent else { guard let draggedNode = nmc.nodeFor(sourceIndexPath), let destNode = nmc.nodeFor(destIndexPath), let parentNode = destNode.parent else {
assertionFailure("This should never happen") assertionFailure("This should never happen")
return sourceIndexPath return sourceIndexPath
} }
// If this is a folder and isn't expanded or doesn't have any entries, let the users drop on it // 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 || !expandedNodes.contains(destNode)) { if destNode.representedObject is Folder && (destNode.numberOfChildNodes == 0 || !nmc.expandedNodes.contains(destNode)) {
let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0 let movementAdjustment = sourceIndexPath > destIndexPath ? 1 : 0
return IndexPath(row: destIndexPath.row + movementAdjustment, section: destIndexPath.section) return IndexPath(row: destIndexPath.row + movementAdjustment, section: destIndexPath.section)
} }
@ -326,7 +310,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
if parentNode.representedObject is Account { if parentNode.representedObject is Account {
return IndexPath(row: 0, section: destIndexPath.section) return IndexPath(row: 0, section: destIndexPath.section)
} else { } else {
return indexPathFor(parentNode)! return nmc.indexPathFor(parentNode)!
} }
} else { } else {
@ -336,10 +320,10 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0 let movementAdjustment = sourceIndexPath < destIndexPath ? 1 : 0
let adjustedIndex = index - movementAdjustment let adjustedIndex = index - movementAdjustment
if adjustedIndex >= sortedNodes.count { if adjustedIndex >= sortedNodes.count {
let lastSortedIndexPath = indexPathFor(sortedNodes[sortedNodes.count - 1])! let lastSortedIndexPath = nmc.indexPathFor(sortedNodes[sortedNodes.count - 1])!
return IndexPath(row: lastSortedIndexPath.row + 1, section: lastSortedIndexPath.section) return IndexPath(row: lastSortedIndexPath.row + 1, section: lastSortedIndexPath.section)
} else { } else {
return indexPathFor(sortedNodes[adjustedIndex])! return nmc.indexPathFor(sortedNodes[adjustedIndex])!
} }
} }
@ -348,18 +332,18 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
guard let sourceNode = nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else { guard let sourceNode = nmc.nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else {
return return
} }
// Based on the drop we have to determine a node to start looking for a parent container. // Based on the drop we have to determine a node to start looking for a parent container.
let destNode: Node = { let destNode: Node = {
if destinationIndexPath.row == 0 { if destinationIndexPath.row == 0 {
return treeController.rootNode.childAtIndex(destinationIndexPath.section)! return nmc.treeController.rootNode.childAtIndex(destinationIndexPath.section)!
} else { } else {
let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0 let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0
let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section) let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section)
return nodeFor(adjustedDestIndexPath)! return nmc.nodeFor(adjustedDestIndexPath)!
} }
}() }()
@ -482,13 +466,13 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
@objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) { @objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) {
guard let sectionIndex = sender.view?.tag, guard let sectionIndex = sender.view?.tag,
let sectionNode = treeController.rootNode.childAtIndex(sectionIndex), let sectionNode = nmc.treeController.rootNode.childAtIndex(sectionIndex),
let headerView = sender.view as? MasterTableViewSectionHeader let headerView = sender.view as? MasterTableViewSectionHeader
else { else {
return return
} }
if expandedNodes.contains(sectionNode) { if nmc.expandedNodes.contains(sectionNode) {
headerView.disclosureExpanded = false headerView.disclosureExpanded = false
collapse(section: sectionIndex) collapse(section: sectionIndex)
} else { } else {
@ -508,7 +492,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
} else { } else {
cell.indentationLevel = 0 cell.indentationLevel = 0
} }
cell.disclosureExpanded = expandedNodes.contains(node) cell.disclosureExpanded = nmc.expandedNodes.contains(node)
cell.allowDisclosureSelection = node.canHaveChildNodes cell.allowDisclosureSelection = node.canHaveChildNodes
cell.name = nameFor(node) cell.name = nameFor(node)
@ -550,8 +534,8 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
func delete(indexPath: IndexPath) { func delete(indexPath: IndexPath) {
guard let undoManager = undoManager, guard let undoManager = undoManager,
let deleteNode = nodeFor(indexPath), let deleteNode = nmc.nodeFor(indexPath),
let deleteCommand = DeleteCommand(nodesToDelete: [deleteNode], treeController: treeController, undoManager: undoManager) let deleteCommand = DeleteCommand(nodesToDelete: [deleteNode], treeController: nmc.treeController, undoManager: undoManager)
else { else {
return return
} }
@ -559,7 +543,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
animatingChanges = true animatingChanges = true
runCommand(deleteCommand) runCommand(deleteCommand)
rebuildShadowTable() nmc.rebuildShadowTable()
tableView.deleteRows(at: [indexPath], with: .automatic) tableView.deleteRows(at: [indexPath], with: .automatic)
animatingChanges = false animatingChanges = false
@ -568,7 +552,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
func rename(indexPath: IndexPath) { func rename(indexPath: IndexPath) {
let name = (nodeFor(indexPath)?.representedObject as? DisplayNameProvider)?.nameForDisplay ?? "" let name = (nmc.nodeFor(indexPath)?.representedObject as? DisplayNameProvider)?.nameForDisplay ?? ""
let formatString = NSLocalizedString("Rename “%@”", comment: "Feed finder") let formatString = NSLocalizedString("Rename “%@”", comment: "Feed finder")
let title = NSString.localizedStringWithFormat(formatString as NSString, name) as String let title = NSString.localizedStringWithFormat(formatString as NSString, name) as String
@ -580,7 +564,7 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
let renameTitle = NSLocalizedString("Rename", comment: "Rename") let renameTitle = NSLocalizedString("Rename", comment: "Rename")
let renameAction = UIAlertAction(title: renameTitle, style: .default) { [weak self] action in let renameAction = UIAlertAction(title: renameTitle, style: .default) { [weak self] action in
guard let node = self?.nodeFor(indexPath), guard let node = self?.nmc.nodeFor(indexPath),
let name = alertController.textFields?[0].text, let name = alertController.textFields?[0].text,
!name.isEmpty else { !name.isEmpty else {
return return
@ -605,19 +589,6 @@ class MasterViewController: UITableViewController, UndoableCommandRunner {
} }
} }
func nodeFor(_ indexPath: IndexPath) -> Node? {
return shadowTable[indexPath.section][indexPath.row]
}
func indexPathFor(_ node: Node) -> IndexPath? {
for i in 0..<shadowTable.count {
if let row = shadowTable[i].firstIndex(of: node) {
return IndexPath(row: row, section: i)
}
}
return nil
}
} }
@ -663,8 +634,8 @@ private extension MasterViewController {
func rebuildBackingStoresAndReloadDataIfNeeded() { func rebuildBackingStoresAndReloadDataIfNeeded() {
if !animatingChanges && !BatchUpdate.shared.isPerforming { if !animatingChanges && !BatchUpdate.shared.isPerforming {
treeController.rebuild() nmc.treeController.rebuild()
rebuildShadowTable() nmc.rebuildShadowTable()
tableView.reloadData() tableView.reloadData()
} }
} }
@ -688,7 +659,7 @@ private extension MasterViewController {
func applyToAvailableCells(_ callback: (MasterTableViewCell, Node) -> Void) { func applyToAvailableCells(_ callback: (MasterTableViewCell, Node) -> Void) {
tableView.visibleCells.forEach { cell in tableView.visibleCells.forEach { cell in
guard let indexPath = tableView.indexPath(for: cell), let node = nodeFor(indexPath) else { guard let indexPath = tableView.indexPath(for: cell), let node = nmc.nodeFor(indexPath) else {
return return
} }
callback(cell as! MasterTableViewCell, node) callback(cell as! MasterTableViewCell, node)
@ -708,35 +679,12 @@ private extension MasterViewController {
return nil return nil
} }
func rebuildShadowTable() {
for i in 0..<treeController.rootNode.numberOfChildNodes {
var result = [Node]()
if let nodes = treeController.rootNode.childAtIndex(i)?.childNodes {
for node in nodes {
result.append(node)
if expandedNodes.contains(node) {
for child in node.childNodes {
result.append(child)
}
}
}
}
shadowTable[i] = result
}
}
func expand(section: Int) { func expand(section: Int) {
guard let expandNode = treeController.rootNode.childAtIndex(section) else { guard let expandNode = nmc.treeController.rootNode.childAtIndex(section) else {
return return
} }
expandedNodes.append(expandNode) nmc.expandedNodes.append(expandNode)
animatingChanges = true animatingChanges = true
@ -745,13 +693,13 @@ private extension MasterViewController {
func addNode(_ node: Node) { func addNode(_ node: Node) {
indexPathsToInsert.append(IndexPath(row: i, section: section)) indexPathsToInsert.append(IndexPath(row: i, section: section))
shadowTable[section].insert(node, at: i) nmc.shadowTable[section].insert(node, at: i)
i = i + 1 i = i + 1
} }
for child in expandNode.childNodes { for child in expandNode.childNodes {
addNode(child) addNode(child)
if expandedNodes.contains(child) { if nmc.expandedNodes.contains(child) {
for gChild in child.childNodes { for gChild in child.childNodes {
addNode(gChild) addNode(gChild)
} }
@ -775,8 +723,8 @@ private extension MasterViewController {
func expand(_ indexPath: IndexPath) { func expand(_ indexPath: IndexPath) {
let expandNode = shadowTable[indexPath.section][indexPath.row] let expandNode = nmc.shadowTable[indexPath.section][indexPath.row]
expandedNodes.append(expandNode) nmc.expandedNodes.append(expandNode)
animatingChanges = true animatingChanges = true
@ -785,7 +733,7 @@ private extension MasterViewController {
if let child = expandNode.childAtIndex(i) { if let child = expandNode.childAtIndex(i) {
let nextIndex = indexPath.row + i + 1 let nextIndex = indexPath.row + i + 1
indexPathsToInsert.append(IndexPath(row: nextIndex, section: indexPath.section)) indexPathsToInsert.append(IndexPath(row: nextIndex, section: indexPath.section))
shadowTable[indexPath.section].insert(child, at: nextIndex) nmc.shadowTable[indexPath.section].insert(child, at: nextIndex)
} }
} }
@ -801,19 +749,19 @@ private extension MasterViewController {
animatingChanges = true animatingChanges = true
guard let collapseNode = treeController.rootNode.childAtIndex(section) else { guard let collapseNode = nmc.treeController.rootNode.childAtIndex(section) else {
return return
} }
if let removeNode = expandedNodes.firstIndex(of: collapseNode) { if let removeNode = nmc.expandedNodes.firstIndex(of: collapseNode) {
expandedNodes.remove(at: removeNode) nmc.expandedNodes.remove(at: removeNode)
} }
var indexPathsToRemove = [IndexPath]() var indexPathsToRemove = [IndexPath]()
for i in 0..<shadowTable[section].count { for i in 0..<nmc.shadowTable[section].count {
indexPathsToRemove.append(IndexPath(row: i, section: section)) indexPathsToRemove.append(IndexPath(row: i, section: section))
} }
shadowTable[section] = [Node]() nmc.shadowTable[section] = [Node]()
tableView.beginUpdates() tableView.beginUpdates()
tableView.deleteRows(at: indexPathsToRemove, with: .automatic) tableView.deleteRows(at: indexPathsToRemove, with: .automatic)
@ -834,22 +782,22 @@ private extension MasterViewController {
animatingChanges = true animatingChanges = true
let collapseNode = shadowTable[indexPath.section][indexPath.row] let collapseNode = nmc.shadowTable[indexPath.section][indexPath.row]
if let removeNode = expandedNodes.firstIndex(of: collapseNode) { if let removeNode = nmc.expandedNodes.firstIndex(of: collapseNode) {
expandedNodes.remove(at: removeNode) nmc.expandedNodes.remove(at: removeNode)
} }
var indexPathsToRemove = [IndexPath]() var indexPathsToRemove = [IndexPath]()
for child in collapseNode.childNodes { for child in collapseNode.childNodes {
if let index = shadowTable[indexPath.section].firstIndex(of: child) { if let index = nmc.shadowTable[indexPath.section].firstIndex(of: child) {
indexPathsToRemove.append(IndexPath(row: index, section: indexPath.section)) indexPathsToRemove.append(IndexPath(row: index, section: indexPath.section))
} }
} }
for child in collapseNode.childNodes { for child in collapseNode.childNodes {
if let index = shadowTable[indexPath.section].firstIndex(of: child) { if let index = nmc.shadowTable[indexPath.section].firstIndex(of: child) {
shadowTable[indexPath.section].remove(at: index) nmc.shadowTable[indexPath.section].remove(at: index)
} }
} }

View File

@ -10,6 +10,7 @@ import Foundation
import Account import Account
import Articles import Articles
import RSCore import RSCore
import RSTree
public extension Notification.Name { public extension Notification.Name {
static let ShowFeedNamesDidChange = Notification.Name(rawValue: "ShowFeedNamesDidChange") static let ShowFeedNamesDidChange = Notification.Name(rawValue: "ShowFeedNamesDidChange")
@ -18,14 +19,17 @@ public extension Notification.Name {
static let ArticlesDidChange = Notification.Name(rawValue: "ArticlesDidChange") static let ArticlesDidChange = Notification.Name(rawValue: "ArticlesDidChange")
} }
class AppModelController { class NavigationModelController {
static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5) static let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5)
init() { var expandedNodes = [Node]()
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) var shadowTable = [[Node]]()
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
} let treeControllerDelegate = FeedTreeControllerDelegate()
lazy var treeController: TreeController = {
return TreeController(delegate: treeControllerDelegate)
}()
private var sortDirection = AppDefaults.timelineSortDirection { private var sortDirection = AppDefaults.timelineSortDirection {
didSet { didSet {
@ -72,6 +76,20 @@ class AppModelController {
private var articleRowMap = [String: Int]() // articleID: rowIndex private var articleRowMap = [String: Int]() // articleID: rowIndex
init() {
for section in treeController.rootNode.childNodes {
expandedNodes.append(section)
shadowTable.append([Node]())
}
rebuildShadowTable()
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
}
// MARK: Notifications // MARK: Notifications
@objc func userDefaultsDidChange(_ note: Notification) { @objc func userDefaultsDidChange(_ note: Notification) {
@ -93,6 +111,42 @@ class AppModelController {
// MARK: API // MARK: API
func rebuildShadowTable() {
for i in 0..<treeController.rootNode.numberOfChildNodes {
var result = [Node]()
if let nodes = treeController.rootNode.childAtIndex(i)?.childNodes {
for node in nodes {
result.append(node)
if expandedNodes.contains(node) {
for child in node.childNodes {
result.append(child)
}
}
}
}
shadowTable[i] = result
}
}
func nodeFor(_ indexPath: IndexPath) -> Node? {
return shadowTable[indexPath.section][indexPath.row]
}
func indexPathFor(_ node: Node) -> IndexPath? {
for i in 0..<shadowTable.count {
if let row = shadowTable[i].firstIndex(of: node) {
return IndexPath(row: row, section: i)
}
}
return nil
}
func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet { func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet {
var indexes = IndexSet() var indexes = IndexSet()
@ -111,7 +165,7 @@ class AppModelController {
} }
private extension AppModelController { private extension NavigationModelController {
// MARK: Fetching Articles // MARK: Fetching Articles
@ -166,7 +220,7 @@ private extension AppModelController {
} }
func queueFetchAndMergeArticles() { func queueFetchAndMergeArticles() {
AppModelController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles)) NavigationModelController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles))
} }
@objc func fetchAndMergeArticles() { @objc func fetchAndMergeArticles() {

View File

@ -17,10 +17,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
private var rowHeightWithoutFeedName: CGFloat = 0.0 private var rowHeightWithoutFeedName: CGFloat = 0.0
private var currentRowHeight: CGFloat { private var currentRowHeight: CGFloat {
return appModelController.showFeedNames ? rowHeightWithFeedName : rowHeightWithoutFeedName return nmc.showFeedNames ? rowHeightWithFeedName : rowHeightWithoutFeedName
} }
var appModelController: AppModelController! var nmc: NavigationModelController!
var undoableCommands = [UndoableCommand]() var undoableCommands = [UndoableCommand]()
var detailViewController: DetailViewController? { var detailViewController: DetailViewController? {
@ -47,10 +47,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(articlesReinitialized(_:)), name: .ArticlesReinitialized, object: appModelController) NotificationCenter.default.addObserver(self, selector: #selector(articlesReinitialized(_:)), name: .ArticlesReinitialized, object: nmc)
NotificationCenter.default.addObserver(self, selector: #selector(showFeedNamesDidChange(_:)), name: .ShowFeedNamesDidChange, object: appModelController) NotificationCenter.default.addObserver(self, selector: #selector(showFeedNamesDidChange(_:)), name: .ShowFeedNamesDidChange, object: nmc)
NotificationCenter.default.addObserver(self, selector: #selector(articleDataDidChange(_:)), name: .ArticleDataDidChange, object: appModelController) NotificationCenter.default.addObserver(self, selector: #selector(articleDataDidChange(_:)), name: .ArticleDataDidChange, object: nmc)
NotificationCenter.default.addObserver(self, selector: #selector(articlesDidChange(_:)), name: .ArticlesDidChange, object: appModelController) NotificationCenter.default.addObserver(self, selector: #selector(articlesDidChange(_:)), name: .ArticlesDidChange, object: nmc)
refreshControl = UIRefreshControl() refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged) refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
@ -71,7 +71,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
if segue.identifier == "showDetail" { if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow { if let indexPath = tableView.indexPathForSelectedRow {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
let article = appModelController.articles[indexPath.row] let article = nmc.articles[indexPath.row]
controller.article = article controller.article = article
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true controller.navigationItem.leftItemsSupplementBackButton = true
@ -95,7 +95,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
let markTitle = NSLocalizedString("Mark All Read", comment: "Mark All Read") let markTitle = NSLocalizedString("Mark All Read", comment: "Mark All Read")
let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in let markAction = UIAlertAction(title: markTitle, style: .default) { [weak self] (action) in
guard let articles = self?.appModelController.articles, guard let articles = self?.nmc.articles,
let undoManager = self?.undoManager, let undoManager = self?.undoManager,
let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else {
return return
@ -116,12 +116,12 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return appModelController.articles.count return nmc.articles.count
} }
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let article = appModelController.articles[indexPath.row] let article = nmc.articles[indexPath.row]
// Set up the star action // Set up the star action
let starTitle = article.status.starred ? let starTitle = article.status.starred ?
@ -165,7 +165,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterTimelineTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterTimelineTableViewCell
let article = appModelController.articles[indexPath.row] let article = nmc.articles[indexPath.row]
configureTimelineCell(cell, article: article) configureTimelineCell(cell, article: article)
@ -173,7 +173,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let article = appModelController.articles[indexPath.row] let article = nmc.articles[indexPath.row]
if !article.status.read { if !article.status.read {
markArticles(Set([article]), statusKey: .read, flag: true) markArticles(Set([article]), statusKey: .read, flag: true)
} }
@ -206,7 +206,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
performBlockAndRestoreSelection { performBlockAndRestoreSelection {
tableView.indexPathsForVisibleRows?.forEach { indexPath in tableView.indexPathsForVisibleRows?.forEach { indexPath in
guard let article = appModelController.articles.articleAtRow(indexPath.row) else { guard let article = nmc.articles.articleAtRow(indexPath.row) else {
return return
} }
@ -222,14 +222,14 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
@objc func avatarDidBecomeAvailable(_ note: Notification) { @objc func avatarDidBecomeAvailable(_ note: Notification) {
guard appModelController.showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { guard nmc.showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
return return
} }
performBlockAndRestoreSelection { performBlockAndRestoreSelection {
tableView.indexPathsForVisibleRows?.forEach { indexPath in tableView.indexPathsForVisibleRows?.forEach { indexPath in
guard let article = appModelController.articles.articleAtRow(indexPath.row), let authors = article.authors, !authors.isEmpty else { guard let article = nmc.articles.articleAtRow(indexPath.row), let authors = article.authors, !authors.isEmpty else {
return return
} }
@ -245,13 +245,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
} }
@objc func imageDidBecomeAvailable(_ note: Notification) { @objc func imageDidBecomeAvailable(_ note: Notification) {
if appModelController.showAvatars { if nmc.showAvatars {
queueReloadVisableCells() queueReloadVisableCells()
} }
} }
@objc func articlesReinitialized(_ note: Notification) { @objc func articlesReinitialized(_ note: Notification) {
if appModelController.articles.count > 0 { if nmc.articles.count > 0 {
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false) tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
} }
} }
@ -290,7 +290,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
if articleIDs.isEmpty { if articleIDs.isEmpty {
return return
} }
let indexes = appModelController.indexesForArticleIDs(articleIDs) let indexes = nmc.indexesForArticleIDs(articleIDs)
reloadVisibleCells(for: indexes) reloadVisibleCells(for: indexes)
} }
@ -344,13 +344,13 @@ private extension MasterTimelineViewController {
} }
let featuredImage = featuredImageFor(article) let featuredImage = featuredImageFor(article)
cell.cellData = MasterTimelineCellData(article: article, showFeedName: appModelController.showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: appModelController.showAvatars, featuredImage: featuredImage) cell.cellData = MasterTimelineCellData(article: article, showFeedName: nmc.showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: nmc.showAvatars, featuredImage: featuredImage)
} }
func avatarFor(_ article: Article) -> UIImage? { func avatarFor(_ article: Article) -> UIImage? {
if !appModelController.showAvatars { if !nmc.showAvatars {
return nil return nil
} }