Change Feeds to use diffable data sources
This commit is contained in:
parent
def926206d
commit
89a38fa2b5
|
@ -122,6 +122,7 @@
|
|||
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
||||
51C452B42265141B00C03939 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51C452B32265141B00C03939 /* WebKit.framework */; };
|
||||
51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; };
|
||||
51CC9B3E231720B2000E842F /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */; };
|
||||
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; };
|
||||
|
@ -733,6 +734,7 @@
|
|||
51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = "<group>"; };
|
||||
51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = "<group>"; };
|
||||
51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = "<group>"; };
|
||||
51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
||||
51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
||||
|
@ -1134,6 +1136,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
51C45264226508F600C03939 /* MasterFeedViewController.swift */,
|
||||
51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */,
|
||||
51C45260226508F600C03939 /* Cell */,
|
||||
);
|
||||
path = MasterFeed;
|
||||
|
@ -2469,6 +2472,7 @@
|
|||
51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */,
|
||||
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */,
|
||||
51E595AD228E1C2100FCC42B /* AddAccountViewController.swift in Sources */,
|
||||
51CC9B3E231720B2000E842F /* MasterFeedDataSource.swift in Sources */,
|
||||
51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */,
|
||||
51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */,
|
||||
51934CD023108953006127BE /* ActivityID.swift in Sources */,
|
||||
|
|
|
@ -54,24 +54,6 @@ final class DeleteCommand: UndoableCommand {
|
|||
registerUndo()
|
||||
}
|
||||
|
||||
func perform(completion: @escaping () -> Void) {
|
||||
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
itemSpecifiers.forEach {
|
||||
$0.delete() {
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
treeController.rebuild()
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
self.registerUndo()
|
||||
completion()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func undo() {
|
||||
|
||||
BatchUpdate.shared.perform {
|
||||
|
|
|
@ -76,8 +76,12 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return treeController.rootNode
|
||||
}
|
||||
|
||||
var numberOfSections: Int {
|
||||
return shadowTable.count
|
||||
var allSections: [Int] {
|
||||
var sections = [Int]()
|
||||
for (index, _) in shadowTable.enumerated() {
|
||||
sections.append(index)
|
||||
}
|
||||
return sections
|
||||
}
|
||||
|
||||
private(set) var currentMasterIndexPath: IndexPath? {
|
||||
|
@ -313,14 +317,6 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
|
||||
// MARK: API
|
||||
|
||||
func beginUpdates() {
|
||||
animatingChanges = true
|
||||
}
|
||||
|
||||
func endUpdates() {
|
||||
animatingChanges = false
|
||||
}
|
||||
|
||||
func rowsInSection(_ section: Int) -> Int {
|
||||
return shadowTable[section].count
|
||||
}
|
||||
|
@ -361,6 +357,10 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return shadowTable[indexPath.section][indexPath.row]
|
||||
}
|
||||
|
||||
func nodesFor(section: Int) -> [Node] {
|
||||
return shadowTable[section]
|
||||
}
|
||||
|
||||
func indexPathFor(_ node: Node) -> IndexPath? {
|
||||
for i in 0..<shadowTable.count {
|
||||
if let row = shadowTable[i].firstIndex(of: node) {
|
||||
|
@ -388,8 +388,7 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
return 0
|
||||
}
|
||||
|
||||
func expand(section: Int, completion: ([IndexPath]) -> ()) {
|
||||
|
||||
func expand(section: Int) {
|
||||
guard let expandNode = treeController.rootNode.childAtIndex(section) else {
|
||||
return
|
||||
}
|
||||
|
@ -397,11 +396,9 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
|
||||
animatingChanges = true
|
||||
|
||||
var indexPathsToInsert = [IndexPath]()
|
||||
var i = 0
|
||||
|
||||
func addNode(_ node: Node) {
|
||||
indexPathsToInsert.append(IndexPath(row: i, section: section))
|
||||
shadowTable[section].insert(node, at: i)
|
||||
i = i + 1
|
||||
}
|
||||
|
@ -415,36 +412,26 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
completion(indexPathsToInsert)
|
||||
|
||||
animatingChanges = false
|
||||
|
||||
}
|
||||
|
||||
func expand(_ indexPath: IndexPath, completion: ([IndexPath]) -> ()) {
|
||||
|
||||
func expand(_ indexPath: IndexPath) {
|
||||
let expandNode = shadowTable[indexPath.section][indexPath.row]
|
||||
expandedNodes.append(expandNode)
|
||||
|
||||
animatingChanges = true
|
||||
|
||||
var indexPathsToInsert = [IndexPath]()
|
||||
for i in 0..<expandNode.childNodes.count {
|
||||
if let child = expandNode.childAtIndex(i) {
|
||||
let nextIndex = indexPath.row + i + 1
|
||||
indexPathsToInsert.append(IndexPath(row: nextIndex, section: indexPath.section))
|
||||
shadowTable[indexPath.section].insert(child, at: nextIndex)
|
||||
}
|
||||
}
|
||||
|
||||
completion(indexPathsToInsert)
|
||||
|
||||
animatingChanges = false
|
||||
|
||||
}
|
||||
|
||||
func collapse(section: Int, completion: ([IndexPath]) -> ()) {
|
||||
|
||||
func collapse(section: Int) {
|
||||
animatingChanges = true
|
||||
|
||||
guard let collapseNode = treeController.rootNode.childAtIndex(section) else {
|
||||
|
@ -455,20 +442,12 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
expandedNodes.remove(at: removeNode)
|
||||
}
|
||||
|
||||
var indexPathsToRemove = [IndexPath]()
|
||||
for i in 0..<shadowTable[section].count {
|
||||
indexPathsToRemove.append(IndexPath(row: i, section: section))
|
||||
}
|
||||
shadowTable[section] = [Node]()
|
||||
|
||||
completion(indexPathsToRemove)
|
||||
|
||||
animatingChanges = false
|
||||
|
||||
}
|
||||
|
||||
func collapse(_ indexPath: IndexPath, completion: ([IndexPath]) -> ()) {
|
||||
|
||||
func collapse(_ indexPath: IndexPath) {
|
||||
animatingChanges = true
|
||||
|
||||
let collapseNode = shadowTable[indexPath.section][indexPath.row]
|
||||
|
@ -476,24 +455,13 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
|||
expandedNodes.remove(at: removeNode)
|
||||
}
|
||||
|
||||
var indexPathsToRemove = [IndexPath]()
|
||||
|
||||
for child in collapseNode.childNodes {
|
||||
if let index = shadowTable[indexPath.section].firstIndex(of: child) {
|
||||
indexPathsToRemove.append(IndexPath(row: index, section: indexPath.section))
|
||||
}
|
||||
}
|
||||
|
||||
for child in collapseNode.childNodes {
|
||||
if let index = shadowTable[indexPath.section].firstIndex(of: child) {
|
||||
shadowTable[indexPath.section].remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
completion(indexPathsToRemove)
|
||||
|
||||
animatingChanges = false
|
||||
|
||||
}
|
||||
|
||||
func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// MasterFeedDataSource.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 8/28/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
import RSTree
|
||||
import Account
|
||||
|
||||
class MasterFeedDataSource<SectionIdentifierType, ItemIdentifierType>: UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
|
||||
|
||||
private var coordinator: AppCoordinator!
|
||||
private var errorHandler: ((Error) -> ())!
|
||||
|
||||
init(coordinator: AppCoordinator, errorHandler: @escaping (Error) -> (), tableView: UITableView, cellProvider: @escaping UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider) {
|
||||
super.init(tableView: tableView, cellProvider: cellProvider)
|
||||
self.coordinator = coordinator
|
||||
self.errorHandler = errorHandler
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let node = coordinator.nodeFor(indexPath), !(node.representedObject is PseudoFeed) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let node = coordinator.nodeFor(indexPath) else {
|
||||
return false
|
||||
}
|
||||
return node.representedObject is Feed
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
|
||||
guard let sourceNode = coordinator.nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else {
|
||||
return
|
||||
}
|
||||
|
||||
// Based on the drop we have to determine a node to start looking for a parent container.
|
||||
let destNode: Node = {
|
||||
if destinationIndexPath.row == 0 {
|
||||
return coordinator.rootNode.childAtIndex(destinationIndexPath.section)!
|
||||
} else {
|
||||
let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0
|
||||
let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section)
|
||||
return coordinator.nodeFor(adjustedDestIndexPath)!
|
||||
}
|
||||
}()
|
||||
|
||||
// Now we start looking for the parent container
|
||||
let destParentNode: Node? = {
|
||||
if destNode.representedObject is Container {
|
||||
return destNode
|
||||
} else {
|
||||
if destNode.parent?.representedObject is Container {
|
||||
return destNode.parent!
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Move the Feed
|
||||
guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
BatchUpdate.shared.end()
|
||||
case .failure(let error):
|
||||
self.errorHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -18,9 +18,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
@IBOutlet private weak var markAllAsReadButton: UIBarButtonItem!
|
||||
@IBOutlet private weak var addNewItemButton: UIBarButtonItem!
|
||||
|
||||
private lazy var dataSource = makeDataSource()
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
|
||||
weak var coordinator: AppCoordinator!
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
@ -36,6 +37,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
navigationItem.rightBarButtonItem = editButtonItem
|
||||
|
||||
tableView.register(MasterFeedTableViewSectionHeader.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
|
||||
tableView.dataSource = dataSource
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
|
@ -48,6 +50,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
|
||||
|
||||
updateUI()
|
||||
applyChanges(animate: false)
|
||||
|
||||
}
|
||||
|
||||
|
@ -71,38 +74,8 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
// MARK: Notifications
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
|
||||
updateUI()
|
||||
|
||||
guard let representedObject = note.object else {
|
||||
return
|
||||
}
|
||||
|
||||
if let account = representedObject as? Account {
|
||||
if let node = coordinator.rootNode.childNodeRepresentingObject(account) {
|
||||
let sectionIndex = coordinator.rootNode.indexOfChild(node)!
|
||||
if let headerView = tableView.headerView(forSection: sectionIndex) as? MasterFeedTableViewSectionHeader {
|
||||
headerView.unreadCount = account.unreadCount
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var node: Node? = nil
|
||||
if let coordinator = representedObject as? AppCoordinator, let fetcher = coordinator.timelineFetcher {
|
||||
node = coordinator.rootNode.descendantNodeRepresentingObject(fetcher as AnyObject)
|
||||
} else {
|
||||
node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject)
|
||||
}
|
||||
|
||||
guard let unwrappedNode = node, let indexPath = coordinator.indexPathFor(unwrappedNode) else {
|
||||
return
|
||||
}
|
||||
|
||||
performBlockAndRestoreSelection {
|
||||
tableView.reloadRows(at: [indexPath], with: .none)
|
||||
}
|
||||
|
||||
applyChanges(animate: false)
|
||||
}
|
||||
|
||||
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||
|
@ -110,15 +83,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(feed)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
|
@ -133,19 +103,11 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
tableView.reloadData()
|
||||
applyChanges(animate: false)
|
||||
}
|
||||
|
||||
// MARK: Table View
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return coordinator.numberOfSections
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return coordinator.rowsInSection(section)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
|
||||
guard let nameProvider = coordinator.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else {
|
||||
|
@ -197,26 +159,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
return UIView(frame: CGRect.zero)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterFeedTableViewCell
|
||||
|
||||
guard let node = coordinator.nodeFor(indexPath) else {
|
||||
return cell
|
||||
}
|
||||
|
||||
configure(cell, node)
|
||||
return cell
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let node = coordinator.nodeFor(indexPath), !(node.representedObject is PseudoFeed) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
var actions = [UIContextualAction]()
|
||||
|
||||
|
@ -296,13 +238,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
coordinator.selectFeed(indexPath)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let node = coordinator.nodeFor(indexPath) else {
|
||||
return false
|
||||
}
|
||||
return node.representedObject is Feed
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
|
||||
|
||||
// Adjust the index path so that it will never be in the smart feeds area
|
||||
|
@ -360,53 +295,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
|
||||
guard let sourceNode = coordinator.nodeFor(sourceIndexPath), let feed = sourceNode.representedObject as? Feed else {
|
||||
return
|
||||
}
|
||||
|
||||
// Based on the drop we have to determine a node to start looking for a parent container.
|
||||
let destNode: Node = {
|
||||
if destinationIndexPath.row == 0 {
|
||||
return coordinator.rootNode.childAtIndex(destinationIndexPath.section)!
|
||||
} else {
|
||||
let movementAdjustment = sourceIndexPath > destinationIndexPath ? 1 : 0
|
||||
let adjustedDestIndexPath = IndexPath(row: destinationIndexPath.row - movementAdjustment, section: destinationIndexPath.section)
|
||||
return coordinator.nodeFor(adjustedDestIndexPath)!
|
||||
}
|
||||
}()
|
||||
|
||||
// Now we start looking for the parent container
|
||||
let destParentNode: Node? = {
|
||||
if destNode.representedObject is Container {
|
||||
return destNode
|
||||
} else {
|
||||
if destNode.parent?.representedObject is Container {
|
||||
return destNode.parent!
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Move the Feed
|
||||
guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
BatchUpdate.shared.end()
|
||||
case .failure(let error):
|
||||
self.presentError(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction func settings(_ sender: UIBarButtonItem) {
|
||||
|
@ -449,18 +337,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
if coordinator.isExpanded(sectionNode) {
|
||||
headerView.disclosureExpanded = false
|
||||
coordinator.collapse(section: sectionIndex) { [weak self] indexPaths in
|
||||
self?.tableView.beginUpdates()
|
||||
self?.tableView.deleteRows(at: indexPaths, with: .automatic)
|
||||
self?.tableView.endUpdates()
|
||||
}
|
||||
coordinator.collapse(section: sectionIndex)
|
||||
self.applyChanges(animate: true)
|
||||
} else {
|
||||
headerView.disclosureExpanded = true
|
||||
coordinator.expand(section: sectionIndex) { [weak self] indexPaths in
|
||||
self?.tableView.beginUpdates()
|
||||
self?.tableView.insertRows(at: indexPaths, with: .automatic)
|
||||
self?.tableView.endUpdates()
|
||||
}
|
||||
coordinator.expand(section: sectionIndex)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -486,7 +368,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
|
||||
func reloadFeeds() {
|
||||
updateUI()
|
||||
tableView.reloadData()
|
||||
applyChanges(animate: true)
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed) {
|
||||
|
@ -506,13 +388,8 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
return
|
||||
}
|
||||
|
||||
coordinator.expand(indexPath) { [weak self] indexPaths in
|
||||
self?.tableView.beginUpdates()
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
self?.tableView.insertRows(at: indexPaths, with: .automatic)
|
||||
self?.tableView.endUpdates()
|
||||
}
|
||||
|
||||
coordinator.expand(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
if let indexPath = coordinator.indexPathFor(node) {
|
||||
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
|
||||
coordinator.selectFeed(indexPath)
|
||||
|
@ -545,6 +422,41 @@ private extension MasterFeedViewController {
|
|||
addNewItemButton.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
|
||||
}
|
||||
|
||||
func applyChanges(animate: Bool) {
|
||||
|
||||
let selectedNode: Node? = {
|
||||
if let selectedIndexPath = tableView.indexPathForSelectedRow {
|
||||
return coordinator.nodeFor(selectedIndexPath)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Node>()
|
||||
|
||||
let sections = coordinator.allSections
|
||||
snapshot.appendSections(sections)
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendItems(coordinator.nodesFor(section: section), toSection: section)
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: animate)
|
||||
|
||||
if let nodeToSelect = selectedNode, let selectingIndexPath = coordinator.indexPathFor(nodeToSelect) {
|
||||
tableView.selectRow(at: selectingIndexPath, animated: false, scrollPosition: .none)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeDataSource() -> UITableViewDiffableDataSource<Int, Node> {
|
||||
return MasterFeedDataSource(coordinator: coordinator, errorHandler: ErrorHandler.present(self), tableView: tableView, cellProvider: { [weak self] tableView, indexPath, node in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterFeedTableViewCell
|
||||
self?.configure(cell, node)
|
||||
return cell
|
||||
})
|
||||
}
|
||||
|
||||
func configure(_ cell: MasterFeedTableViewCell, _ node: Node) {
|
||||
|
||||
cell.delegate = self
|
||||
|
@ -619,30 +531,16 @@ private extension MasterFeedViewController {
|
|||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
return
|
||||
}
|
||||
coordinator.expand(indexPath) { [weak self] indexPaths in
|
||||
self?.tableView.beginUpdates()
|
||||
self?.tableView.insertRows(at: indexPaths, with: .automatic)
|
||||
self?.tableView.endUpdates()
|
||||
}
|
||||
coordinator.expand(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
|
||||
func collapse(_ cell: MasterFeedTableViewCell) {
|
||||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
return
|
||||
}
|
||||
coordinator.collapse(indexPath) { [weak self] indexPaths in
|
||||
self?.tableView.beginUpdates()
|
||||
self?.tableView.deleteRows(at: indexPaths, with: .automatic)
|
||||
self?.tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func performBlockAndRestoreSelection(_ block: (() -> Void)) {
|
||||
let indexPaths = tableView.indexPathsForSelectedRows
|
||||
block()
|
||||
indexPaths?.forEach { [weak self] indexPath in
|
||||
self?.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||
}
|
||||
coordinator.collapse(indexPath)
|
||||
self.applyChanges(animate: true)
|
||||
}
|
||||
|
||||
func makeFeedContextMenu(indexPath: IndexPath, includeDeleteRename: Bool) -> UIContextMenuConfiguration {
|
||||
|
@ -820,7 +718,7 @@ private extension MasterFeedViewController {
|
|||
feed.rename(to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
self?.configureCellsForRepresentedObject(feed)
|
||||
case .failure(let error):
|
||||
self?.presentError(error)
|
||||
}
|
||||
|
@ -829,7 +727,7 @@ private extension MasterFeedViewController {
|
|||
folder.rename(to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
self?.configureCellsForRepresentedObject(folder)
|
||||
case .failure(let error):
|
||||
self?.presentError(error)
|
||||
}
|
||||
|
@ -851,7 +749,6 @@ private extension MasterFeedViewController {
|
|||
}
|
||||
|
||||
func delete(indexPath: IndexPath) {
|
||||
|
||||
guard let undoManager = undoManager,
|
||||
let deleteNode = coordinator.nodeFor(indexPath),
|
||||
let deleteCommand = DeleteCommand(nodesToDelete: [deleteNode], treeController: coordinator.treeController, undoManager: undoManager, errorHandler: ErrorHandler.present(self))
|
||||
|
@ -865,23 +762,8 @@ private extension MasterFeedViewController {
|
|||
ActivityManager.shared.cleanUp(feed)
|
||||
}
|
||||
|
||||
var deleteIndexPaths = [indexPath]
|
||||
if coordinator.isExpanded(deleteNode) {
|
||||
for i in 0..<deleteNode.numberOfChildNodes {
|
||||
deleteIndexPaths.append(IndexPath(row: indexPath.row + 1 + i, section: indexPath.section))
|
||||
}
|
||||
}
|
||||
|
||||
pushUndoableCommand(deleteCommand)
|
||||
|
||||
coordinator.beginUpdates()
|
||||
deleteCommand.perform {
|
||||
self.coordinator.treeController.rebuild()
|
||||
self.coordinator.rebuildShadowTable()
|
||||
self.tableView.deleteRows(at: deleteIndexPaths, with: .automatic)
|
||||
self.coordinator.endUpdates()
|
||||
}
|
||||
|
||||
deleteCommand.perform()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue