// // FeedViewController+Drop.swift // NetNewsWire-iOS // // Created by Maurice Parker on 11/20/19. // Copyright © 2019 Ranchero Software. All rights reserved. // import UIKit import Account import Tree import Core extension SidebarViewController: UITableViewDropDelegate { func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool { return session.localDragSession != nil } func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal { guard let destIndexPath = destinationIndexPath, destIndexPath.section > 0, tableView.hasActiveDrag else { return UITableViewDropProposal(operation: .forbidden) } guard let destFeed = coordinator.nodeFor(destIndexPath)?.representedObject as? SidebarItem, let destAccount = destFeed.account, let destCell = tableView.cellForRow(at: destIndexPath) else { return UITableViewDropProposal(operation: .forbidden) } // Validate account specific behaviors... if destAccount.behaviors.contains(.disallowFeedInMultipleFolders), let sourceNode = session.localDragSession?.items.first?.localObject as? Node, let sourceFeed = sourceNode.representedObject as? Feed, sourceFeed.account?.accountID != destAccount.accountID && destAccount.hasFeed(withURL: sourceFeed.url) { return UITableViewDropProposal(operation: .forbidden) } // Determine the correct drop proposal if destFeed is Folder { if session.location(in: destCell).y >= 0 { return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath) } else { return UITableViewDropProposal(operation: .move, intent: .unspecified) } } else { return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) } } func tableView(_ tableView: UITableView, performDropWith dropCoordinator: UITableViewDropCoordinator) { guard let dragItem = dropCoordinator.items.first?.dragItem, let dragNode = dragItem.localObject as? Node, let source = dragNode.parent?.representedObject as? Container, let destIndexPath = dropCoordinator.destinationIndexPath else { return } let isFolderDrop: Bool = { if coordinator.nodeFor(destIndexPath)?.representedObject is Folder, let propCell = tableView.cellForRow(at: destIndexPath) { return dropCoordinator.session.location(in: propCell).y >= 0 } return false }() // Based on the drop we have to determine a node to start looking for a parent container. let destNode: Node? = { if isFolderDrop { return coordinator.nodeFor(destIndexPath) } else { if destIndexPath.row == 0 { return coordinator.nodeFor(IndexPath(row: 0, section: destIndexPath.section)) } else if destIndexPath.row > 0 { return coordinator.nodeFor(IndexPath(row: destIndexPath.row - 1, section: destIndexPath.section)) } else { return nil } } }() // Now we start looking for the parent container let destinationContainer: Container? = { if let container = (destNode?.representedObject as? Container) ?? (destNode?.parent?.representedObject as? Container) { return container } else { // If we got here, we are trying to drop on an empty section header. Go and find the Account for this section return coordinator.rootNode.childAtIndex(destIndexPath.section)?.representedObject as? Account } }() guard let destination = destinationContainer, let feed = dragNode.representedObject as? Feed else { return } if source.account == destination.account { moveFeedInAccount(feed: feed, sourceContainer: source, destinationContainer: destination) } else { moveFeedBetweenAccounts(feed: feed, sourceContainer: source, destinationContainer: destination) } } func moveFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) { guard sourceContainer !== destinationContainer else { return } BatchUpdate.shared.start() Task { @MainActor in do { try await sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) } catch { self.presentError(error) } BatchUpdate.shared.end() } } func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) { let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) BatchUpdate.shared.start() Task { @MainActor in do { if let existingFeed { try await destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) } else { _ = try await destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) } try await sourceContainer.account?.removeFeed(feed, from: sourceContainer) } catch { self.presentError(error) } BatchUpdate.shared.end() } } }