mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Implement drag and drop feed arrangement.
This commit is contained in:
@@ -14,12 +14,10 @@ import Account
|
||||
class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
|
||||
private var coordinator: SceneCoordinator!
|
||||
private var errorHandler: ((Error) -> ())!
|
||||
|
||||
init(coordinator: SceneCoordinator, errorHandler: @escaping (Error) -> (), tableView: UITableView, cellProvider: @escaping UITableViewDiffableDataSource<Node, Node>.CellProvider) {
|
||||
init(coordinator: SceneCoordinator, tableView: UITableView, cellProvider: @escaping UITableViewDiffableDataSource<Node, Node>.CellProvider) {
|
||||
super.init(tableView: tableView, cellProvider: cellProvider)
|
||||
self.coordinator = coordinator
|
||||
self.errorHandler = errorHandler
|
||||
self.defaultRowAnimation = .middle
|
||||
}
|
||||
|
||||
@@ -30,140 +28,4 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
guard let node = itemIdentifier(for: indexPath) else {
|
||||
return false
|
||||
}
|
||||
return node.representedObject is WebFeed
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
|
||||
guard let sourceNode = itemIdentifier(for: sourceIndexPath), let webFeed = sourceNode.representedObject as? WebFeed 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 itemIdentifier(for: 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 Web Feed
|
||||
guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
if sameAccount(sourceNode, destParentNode!) {
|
||||
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
} else {
|
||||
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func sameAccount(_ node: Node, _ parentNode: Node) -> Bool {
|
||||
if let accountID = nodeAccountID(node), let parentAccountID = nodeAccountID(parentNode) {
|
||||
if accountID == parentAccountID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func nodeAccount(_ node: Node) -> Account? {
|
||||
if let account = node.representedObject as? Account {
|
||||
return account
|
||||
} else if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
} else if let webFeed = node.representedObject as? WebFeed {
|
||||
return webFeed.account
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func nodeAccountID(_ node: Node) -> String? {
|
||||
return nodeAccount(node)?.accountID
|
||||
}
|
||||
|
||||
func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
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.errorHandler(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.errorHandler(error)
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
BatchUpdate.shared.end()
|
||||
self.errorHandler(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.errorHandler(error)
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
BatchUpdate.shared.end()
|
||||
self.errorHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
33
iOS/MasterFeed/MasterFeedViewController+Drag.swift
Normal file
33
iOS/MasterFeed/MasterFeedViewController+Drag.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// MasterFeedViewController+Drag.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/20/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
import Account
|
||||
|
||||
extension MasterFeedViewController: UITableViewDragDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), let webFeed = node.representedObject as? WebFeed else {
|
||||
return [UIDragItem]()
|
||||
}
|
||||
|
||||
let data = webFeed.url.data(using: .utf8)
|
||||
let itemProvider = NSItemProvider()
|
||||
|
||||
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeURL as String, visibility: .all) { completion in
|
||||
completion(data, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
let dragItem = UIDragItem(itemProvider: itemProvider)
|
||||
dragItem.localObject = node
|
||||
return [dragItem]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
private var refreshProgressView: RefreshProgressView?
|
||||
private var addNewItemButton: UIBarButtonItem!
|
||||
|
||||
private lazy var dataSource = makeDataSource()
|
||||
lazy var dataSource = makeDataSource()
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
weak var coordinator: SceneCoordinator!
|
||||
|
||||
@@ -38,8 +38,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
}
|
||||
|
||||
navigationItem.rightBarButtonItem = editButtonItem
|
||||
|
||||
// Set the bar button item so that it doesn't show on the timeline view
|
||||
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
|
||||
|
||||
@@ -51,6 +49,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
tableView.register(MasterFeedTableViewSectionHeader.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
|
||||
tableView.dataSource = dataSource
|
||||
tableView.dragDelegate = self
|
||||
tableView.dropDelegate = self
|
||||
tableView.dragInteractionEnabled = true
|
||||
resetEstimatedRowHeight()
|
||||
tableView.separatorStyle = .none
|
||||
|
||||
@@ -630,7 +631,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func makeDataSource() -> UITableViewDiffableDataSource<Node, Node> {
|
||||
return MasterFeedDataSource(coordinator: coordinator, errorHandler: ErrorHandler.present(self), tableView: tableView, cellProvider: { [weak self] tableView, indexPath, node in
|
||||
return MasterFeedDataSource(coordinator: coordinator, 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
|
||||
|
||||
Reference in New Issue
Block a user