mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Make dragging feeds between Accounts copy as they do on the Mac. Fixes #3691
This commit is contained in:
@@ -22,32 +22,59 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
guard let destIndexPath = correctDestinationIndexPath(session: session) else {
|
||||
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
||||
guard let sourceNode = session.localDragSession?.items.first?.localObject as? Node,
|
||||
let sourceWebFeed = sourceNode.representedObject as? WebFeed else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
var successOperation = UIDropOperation.move
|
||||
|
||||
if let destinationIndexPath = destinationIndexPath,
|
||||
let sourceIndexPath = coordinator.indexPathFor(sourceNode),
|
||||
destinationIndexPath.section != sourceIndexPath.section {
|
||||
successOperation = .copy
|
||||
}
|
||||
|
||||
guard destIndexPath.section > 0 else {
|
||||
guard let correctedIndexPath = correctDestinationIndexPath(session: session) else {
|
||||
// We didn't hit the corrected indexPath, but this at least it gets the section right
|
||||
guard let section = destinationIndexPath?.section,
|
||||
let account = coordinator.nodeFor(section)?.representedObject as? Account,
|
||||
!account.hasChildWebFeed(withURL: sourceWebFeed.url) else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
return UITableViewDropProposal(operation: successOperation, intent: .insertAtDestinationIndexPath)
|
||||
}
|
||||
|
||||
guard correctedIndexPath.section > 0 else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
guard let destFeed = coordinator.nodeFor(destIndexPath)?.representedObject as? Feed,
|
||||
let destAccount = destFeed.account else {
|
||||
guard let correctDestNode = coordinator.nodeFor(correctedIndexPath),
|
||||
let correctDestFeed = correctDestNode.representedObject as? Feed,
|
||||
let correctDestAccount = correctDestFeed.account else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
// Validate account specific behaviors...
|
||||
if destAccount.behaviors.contains(.disallowFeedInMultipleFolders),
|
||||
let sourceNode = session.localDragSession?.items.first?.localObject as? Node,
|
||||
let sourceWebFeed = sourceNode.representedObject as? WebFeed,
|
||||
sourceWebFeed.account?.accountID != destAccount.accountID && destAccount.hasWebFeed(withURL: sourceWebFeed.url) {
|
||||
if correctDestAccount.behaviors.contains(.disallowFeedInMultipleFolders),
|
||||
sourceWebFeed.account?.accountID != correctDestAccount.accountID && correctDestAccount.hasWebFeed(withURL: sourceWebFeed.url) {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
// Determine the correct drop proposal
|
||||
if destFeed is Folder {
|
||||
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
|
||||
if let correctFolder = correctDestFeed as? Folder {
|
||||
if correctFolder.hasChildWebFeed(withURL: sourceWebFeed.url) {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
} else {
|
||||
return UITableViewDropProposal(operation: successOperation, intent: .insertIntoDestinationIndexPath)
|
||||
}
|
||||
} else {
|
||||
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
||||
if let parentContainer = correctDestNode.parent?.representedObject as? Container, !parentContainer.hasChildWebFeed(withURL: sourceWebFeed.url) {
|
||||
return UITableViewDropProposal(operation: successOperation, intent: .insertAtDestinationIndexPath)
|
||||
} else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,13 +82,13 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
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 = correctDestinationIndexPath(session: dropCoordinator.session) else {
|
||||
let source = dragNode.parent?.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
// Based on the drop we have to determine a node to start looking for a parent container.
|
||||
let destNode: Node? = {
|
||||
guard let destIndexPath = correctDestinationIndexPath(session: dropCoordinator.session) else { return nil }
|
||||
|
||||
if coordinator.nodeFor(destIndexPath)?.representedObject is Folder {
|
||||
if dropCoordinator.proposal.intent == .insertAtDestinationIndexPath {
|
||||
@@ -70,15 +97,8 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// Now we start looking for the parent container
|
||||
@@ -86,8 +106,11 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
if let container = (destNode?.representedObject as? Container) ?? (destNode?.parent?.representedObject as? Container) {
|
||||
return container
|
||||
} else {
|
||||
// We didn't hit the corrected indexPath, but this at least gets the section right
|
||||
guard let section = dropCoordinator.destinationIndexPath?.section else { return nil }
|
||||
|
||||
// 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
|
||||
return coordinator.nodeFor(section)?.representedObject as? Account
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -96,7 +119,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
if source.account == destination.account {
|
||||
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
} else {
|
||||
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
copyWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +153,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
func copyWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
|
||||
if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) {
|
||||
|
||||
@@ -138,15 +161,7 @@ private extension MasterFeedViewController {
|
||||
destinationContainer.account?.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.presentError(error)
|
||||
}
|
||||
}
|
||||
BatchUpdate.shared.end()
|
||||
case .failure(let error):
|
||||
BatchUpdate.shared.end()
|
||||
self.presentError(error)
|
||||
@@ -159,15 +174,7 @@ private extension MasterFeedViewController {
|
||||
destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.presentError(error)
|
||||
}
|
||||
}
|
||||
BatchUpdate.shared.end()
|
||||
case .failure(let error):
|
||||
BatchUpdate.shared.end()
|
||||
self.presentError(error)
|
||||
@@ -179,3 +186,11 @@ private extension MasterFeedViewController {
|
||||
|
||||
|
||||
}
|
||||
|
||||
private extension Container {
|
||||
|
||||
func hasChildWebFeed(withURL url: String) -> Bool {
|
||||
return topLevelWebFeeds.contains(where: { $0.url == url })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ struct FeedNode: Hashable {
|
||||
var node: Node
|
||||
var feedID: FeedIdentifier
|
||||
|
||||
init(_ node: Node) {
|
||||
init?(_ node: Node) {
|
||||
guard let feed = node.representedObject as? Feed else { return nil }
|
||||
|
||||
self.node = node
|
||||
self.feedID = (node.representedObject as! Feed).feedID!
|
||||
self.feedID = feed.feedID!
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
@@ -622,8 +624,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
|
||||
func indexPathFor(_ node: Node) -> IndexPath? {
|
||||
guard let feedNode = FeedNode(node) else { return nil }
|
||||
|
||||
for i in 0..<shadowTable.count {
|
||||
if let row = shadowTable[i].feedNodes.firstIndex(of: FeedNode(node)) {
|
||||
if let row = shadowTable[i].feedNodes.firstIndex(of: feedNode) {
|
||||
return IndexPath(row: row, section: i)
|
||||
}
|
||||
}
|
||||
@@ -1519,10 +1523,12 @@ private extension SceneCoordinator {
|
||||
|
||||
if isExpanded(sectionNode) {
|
||||
for node in sectionNode.childNodes {
|
||||
feedNodes.append(FeedNode(node))
|
||||
guard let feedNode = FeedNode(node) else { continue }
|
||||
feedNodes.append(feedNode)
|
||||
if isExpanded(node) {
|
||||
for child in node.childNodes {
|
||||
feedNodes.append(FeedNode(child))
|
||||
guard let childNode = FeedNode(child) else { continue }
|
||||
feedNodes.append(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user