2019-11-21 03:28:50 +01:00
|
|
|
//
|
|
|
|
// MasterFeedViewController+Drop.swift
|
|
|
|
// NetNewsWire-iOS
|
|
|
|
//
|
|
|
|
// Created by Maurice Parker on 11/20/19.
|
|
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import RSCore
|
|
|
|
import Account
|
|
|
|
import RSTree
|
|
|
|
|
|
|
|
extension MasterFeedViewController: UITableViewDropDelegate {
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
|
2019-11-21 20:49:05 +01:00
|
|
|
return session.localDragSession != nil
|
2019-11-21 03:28:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
|
2020-11-02 21:35:48 +01:00
|
|
|
guard let destIndexPath = destinationIndexPath, destIndexPath.section > 0, tableView.hasActiveDrag else {
|
|
|
|
return UITableViewDropProposal(operation: .forbidden)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let destIdentifier = dataSource.itemIdentifier(for: destIndexPath) else {
|
|
|
|
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let destAccount = destIdentifier.account, let destCell = tableView.cellForRow(at: destIndexPath) else {
|
|
|
|
return UITableViewDropProposal(operation: .forbidden)
|
2019-11-21 20:22:33 +01:00
|
|
|
}
|
2020-11-01 02:14:50 +01:00
|
|
|
|
|
|
|
// Validate account specific behaviors...
|
|
|
|
if destAccount.behaviors.contains(.disallowFeedInMultipleFolders),
|
|
|
|
let sourceFeedID = (session.localDragSession?.items.first?.localObject as? MasterFeedTableViewIdentifier)?.feedID,
|
|
|
|
let sourceWebFeed = AccountManager.shared.existingFeed(with: sourceFeedID) as? WebFeed,
|
|
|
|
sourceWebFeed.account?.accountID != destAccount.accountID && destAccount.hasWebFeed(withURL: sourceWebFeed.url) {
|
|
|
|
return UITableViewDropProposal(operation: .forbidden)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the correct drop proposal
|
2020-06-16 01:03:20 +02:00
|
|
|
if destIdentifier.isFolder {
|
2019-11-23 02:59:25 +01:00
|
|
|
if session.location(in: destCell).y >= 0 {
|
|
|
|
return UITableViewDropProposal(operation: .move, intent: .insertIntoDestinationIndexPath)
|
|
|
|
} else {
|
|
|
|
return UITableViewDropProposal(operation: .move, intent: .unspecified)
|
|
|
|
}
|
2019-11-21 20:22:33 +01:00
|
|
|
} else {
|
|
|
|
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
|
2019-11-21 03:28:50 +01:00
|
|
|
}
|
2019-11-21 20:22:33 +01:00
|
|
|
|
2019-11-21 03:28:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, performDropWith dropCoordinator: UITableViewDropCoordinator) {
|
|
|
|
guard let dragItem = dropCoordinator.items.first?.dragItem,
|
2020-06-16 01:03:20 +02:00
|
|
|
let sourceIdentifier = dragItem.localObject as? MasterFeedTableViewIdentifier,
|
|
|
|
let sourceParentContainerID = sourceIdentifier.parentContainerID,
|
|
|
|
let source = AccountManager.shared.existingContainer(with: sourceParentContainerID),
|
2019-11-21 20:22:33 +01:00
|
|
|
let destIndexPath = dropCoordinator.destinationIndexPath else {
|
2019-11-21 03:28:50 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-21 20:22:33 +01:00
|
|
|
let isFolderDrop: Bool = {
|
2020-06-16 01:03:20 +02:00
|
|
|
if let propDestIdentifier = dataSource.itemIdentifier(for: destIndexPath), let propCell = tableView.cellForRow(at: destIndexPath) {
|
|
|
|
return propDestIdentifier.isFolder && dropCoordinator.session.location(in: propCell).y >= 0
|
2019-11-21 20:22:33 +01:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}()
|
|
|
|
|
2019-11-21 03:28:50 +01:00
|
|
|
// Based on the drop we have to determine a node to start looking for a parent container.
|
2020-06-16 01:03:20 +02:00
|
|
|
let destIdentifier: MasterFeedTableViewIdentifier? = {
|
2019-11-21 20:22:33 +01:00
|
|
|
|
2019-11-26 20:01:07 +01:00
|
|
|
if isFolderDrop {
|
|
|
|
return dataSource.itemIdentifier(for: destIndexPath)
|
2019-11-21 03:28:50 +01:00
|
|
|
} else {
|
2019-11-26 20:01:07 +01:00
|
|
|
if destIndexPath.row == 0 {
|
2020-06-16 01:03:20 +02:00
|
|
|
return dataSource.itemIdentifier(for: IndexPath(row: 0, section: destIndexPath.section))
|
2019-11-26 20:01:07 +01:00
|
|
|
} else if destIndexPath.row > 0 {
|
|
|
|
return dataSource.itemIdentifier(for: IndexPath(row: destIndexPath.row - 1, section: destIndexPath.section))
|
2019-11-21 20:22:33 +01:00
|
|
|
} else {
|
2019-11-26 20:01:07 +01:00
|
|
|
return nil
|
2019-11-21 20:22:33 +01:00
|
|
|
}
|
2019-11-21 03:28:50 +01:00
|
|
|
}
|
2019-11-26 20:01:07 +01:00
|
|
|
|
2019-11-21 03:28:50 +01:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Now we start looking for the parent container
|
2020-06-16 01:03:20 +02:00
|
|
|
let destinationContainer: Container? = {
|
|
|
|
if let containerID = destIdentifier?.containerID ?? destIdentifier?.parentContainerID {
|
|
|
|
return AccountManager.shared.existingContainer(with: containerID)
|
2019-11-21 03:28:50 +01:00
|
|
|
} else {
|
2020-11-02 21:35:48 +01:00
|
|
|
// 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
|
2019-11-21 03:28:50 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-06-16 01:03:20 +02:00
|
|
|
guard let destination = destinationContainer else { return }
|
|
|
|
guard case .webFeed(_, let webFeedID) = sourceIdentifier.feedID else { return }
|
|
|
|
guard let webFeed = source.existingWebFeed(withWebFeedID: webFeedID) else { return }
|
2019-11-21 03:28:50 +01:00
|
|
|
|
2020-06-16 01:03:20 +02:00
|
|
|
if source.account == destination.account {
|
2019-11-21 03:28:50 +01:00
|
|
|
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
|
|
|
} else {
|
|
|
|
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
2019-11-23 23:38:07 +01:00
|
|
|
guard sourceContainer !== destinationContainer else { return }
|
|
|
|
|
2019-11-21 03:28:50 +01:00
|
|
|
BatchUpdate.shared.start()
|
|
|
|
sourceContainer.account?.moveWebFeed(feed, from: sourceContainer, to: destinationContainer) { result in
|
|
|
|
BatchUpdate.shared.end()
|
|
|
|
switch result {
|
|
|
|
case .success:
|
|
|
|
break
|
|
|
|
case .failure(let error):
|
|
|
|
self.presentError(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
|
|
|
|
|
|
|
if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) {
|
|
|
|
|
|
|
|
BatchUpdate.shared.start()
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case .failure(let error):
|
|
|
|
BatchUpdate.shared.end()
|
|
|
|
self.presentError(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
BatchUpdate.shared.start()
|
|
|
|
destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case .failure(let error):
|
|
|
|
BatchUpdate.shared.end()
|
|
|
|
self.presentError(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|