From 30297c2390e3d0b9a80b667e73506330341f4651 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 14 Dec 2020 18:44:41 -0500 Subject: [PATCH] Add multi-window drag and drop to all the things --- Tusker.xcodeproj/project.pbxproj | 4 +++ .../BookmarksTableViewController.swift | 2 ++ .../ConversationTableViewController.swift | 2 ++ .../Drafts/DraftsTableViewController.swift | 11 ++++++ .../Explore/ExploreViewController.swift | 34 +++++++++++++++++++ .../NotificationsTableViewController.swift | 2 ++ .../ProfileStatusesViewController.swift | 2 ++ .../Search/SearchResultsViewController.swift | 2 ++ ...ActionAccountListTableViewController.swift | 2 ++ .../TimelineTableViewController.swift | 19 ++--------- .../Utilities/DraggableTableViewCell.swift | 13 +++++++ .../EnhancedTableViewController.swift | 19 +++++++++++ .../TimelineLikeTableViewController.swift | 10 +++--- .../Account Cell/AccountTableViewCell.swift | 13 +++++++ ...FollowNotificationGroupTableViewCell.swift | 13 +++++++ ...llowRequestNotificationTableViewCell.swift | 9 +++++ .../Status/BaseStatusTableViewCell.swift | 7 ++-- .../Status/TimelineStatusTableViewCell.swift | 13 +++++++ 18 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 Tusker/Screens/Utilities/DraggableTableViewCell.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 109eeeb4..b68b919d 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ D62275A624F1C81800B82A16 /* ComposeReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A524F1C81800B82A16 /* ComposeReplyView.swift */; }; D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; }; D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A924F1E01C00B82A16 /* ComposeTextView.swift */; }; + D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; }; D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; }; D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; }; D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; }; @@ -437,6 +438,7 @@ D62275A524F1C81800B82A16 /* ComposeReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyView.swift; sourceTree = ""; }; D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyContentView.swift; sourceTree = ""; }; D62275A924F1E01C00B82A16 /* ComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTextView.swift; sourceTree = ""; }; + D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = ""; }; D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = ""; }; D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = ""; }; D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = ""; }; @@ -1382,6 +1384,7 @@ D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */, D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */, D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */, + D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */, ); path = Utilities; sourceTree = ""; @@ -1895,6 +1898,7 @@ D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */, D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */, + D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */, D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */, D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */, diff --git a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift index 9c63b3b0..0f7baf72 100644 --- a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift +++ b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift @@ -27,6 +27,8 @@ class BookmarksTableViewController: EnhancedTableViewController { super.init(style: .plain) + dragEnabled = true + title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title") } diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index 993d0f98..34dbc9b2 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -36,6 +36,8 @@ class ConversationTableViewController: EnhancedTableViewController { self.mastodonController = mastodonController super.init(style: .plain) + + dragEnabled = true } required init?(coder aDecoder: NSCoder) { diff --git a/Tusker/Screens/Drafts/DraftsTableViewController.swift b/Tusker/Screens/Drafts/DraftsTableViewController.swift index 0b8a043d..70f6457c 100644 --- a/Tusker/Screens/Drafts/DraftsTableViewController.swift +++ b/Tusker/Screens/Drafts/DraftsTableViewController.swift @@ -45,6 +45,8 @@ class DraftsTableViewController: UITableViewController { tableView.register(UINib(nibName: "DraftTableViewCell", bundle: nil), forCellReuseIdentifier: "draftCell") + tableView.dragDelegate = self + drafts = DraftsManager.shared.sorted.filter { (draft) in draft.accountID == account.id && draft != excludedDraft } @@ -116,3 +118,12 @@ class DraftsTableViewController: UITableViewController { } } + +extension DraftsTableViewController: UITableViewDragDelegate { + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + let draft = self.draft(for: indexPath) + let activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: account.id) + let provider = NSItemProvider(object: activity) + return [UIDragItem(itemProvider: provider)] + } +} diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 4fdff7ab..31305cdb 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -26,6 +26,8 @@ class ExploreViewController: EnhancedTableViewController { super.init(style: .insetGrouped) + dragEnabled = true + title = NSLocalizedString("Explore", comment: "explore tab title") tabBarItem.image = UIImage(systemName: "magnifyingglass") } @@ -401,3 +403,35 @@ extension ExploreViewController: InstanceTimelineViewControllerDelegate { dismiss(animated: true) } } + +extension ExploreViewController { + override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + guard let item = dataSource.itemIdentifier(for: indexPath), + let accountID = mastodonController.accountInfo?.id else { + return [] + } + let provider: NSItemProvider + switch item { + case .bookmarks: + provider = NSItemProvider(object: UserActivityManager.bookmarksActivity()) + case let .list(list): + guard let activity = UserActivityManager.showTimelineActivity(timeline: .list(id: list.id), accountID: accountID) else { return [] } + provider = NSItemProvider(object: activity) + case let .savedHashtag(hashtag): + provider = NSItemProvider(object: hashtag.url as NSURL) + if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: accountID) { + provider.registerObject(activity, visibility: .all) + } + case let .savedInstance(url): + provider = NSItemProvider(object: url as NSURL) + // todo: should dragging public timelines into new windows be supported? + case .addList: + return [] + case .addSavedHashtag: + return [] + case .findInstance: + return [] + } + return [UIDragItem(itemProvider: provider)] + } +} diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index 02053d06..24792cea 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -30,6 +30,8 @@ class NotificationsTableViewController: TimelineLikeTableViewController String { @@ -175,18 +175,3 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching { } } } - -extension TimelineTableViewController: UITableViewDragDelegate { - func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - let id = item(for: indexPath).id - guard let status = mastodonController.persistentContainer.status(for: id), - let accountId = mastodonController.accountInfo?.id else { - return [] - } - let activity = UserActivityManager.showConversationActivity(mainStatusID: id, accountID: accountId) - let itemProvider = NSItemProvider(object: status.url! as NSURL) - itemProvider.registerObject(activity, visibility: .all) - let dragItem = UIDragItem(itemProvider: itemProvider) - return [dragItem] - } -} diff --git a/Tusker/Screens/Utilities/DraggableTableViewCell.swift b/Tusker/Screens/Utilities/DraggableTableViewCell.swift new file mode 100644 index 00000000..37c4dbf2 --- /dev/null +++ b/Tusker/Screens/Utilities/DraggableTableViewCell.swift @@ -0,0 +1,13 @@ +// +// DraggableTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 12/14/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit + +protocol DraggableTableViewCell: UITableViewCell { + func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] +} diff --git a/Tusker/Screens/Utilities/EnhancedTableViewController.swift b/Tusker/Screens/Utilities/EnhancedTableViewController.swift index 95cf0f1b..a7ba51fe 100644 --- a/Tusker/Screens/Utilities/EnhancedTableViewController.swift +++ b/Tusker/Screens/Utilities/EnhancedTableViewController.swift @@ -16,6 +16,16 @@ class EnhancedTableViewController: UITableViewController { private var prevScrollViewContentOffset: CGPoint? private(set) var scrollViewDirection: CGFloat = 0 + var dragEnabled = false + + override func viewDidLoad() { + super.viewDidLoad() + + if dragEnabled { + tableView.dragDelegate = self + } + } + // MARK: Scroll View Delegate override func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { @@ -96,6 +106,15 @@ extension EnhancedTableViewController { } +extension EnhancedTableViewController: UITableViewDragDelegate { + func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + guard let cell = tableView.cellForRow(at: indexPath) as? DraggableTableViewCell else { + return [] + } + return cell.dragItemsForBeginning(session: session) + } +} + extension EnhancedTableViewController: TabBarScrollableViewController { func tabBarScrollToTop() { if scrollViewShouldScrollToTop(tableView) { diff --git a/Tusker/Screens/Utilities/TimelineLikeTableViewController.swift b/Tusker/Screens/Utilities/TimelineLikeTableViewController.swift index 85888a0b..e8a1489f 100644 --- a/Tusker/Screens/Utilities/TimelineLikeTableViewController.swift +++ b/Tusker/Screens/Utilities/TimelineLikeTableViewController.swift @@ -24,11 +24,6 @@ class TimelineLikeTableViewController: EnhancedTableViewController, Refres init() { super.init(style: .plain) - #if !targetEnvironment(macCatalyst) - self.refreshControl = UIRefreshControl() - self.refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged) - #endif - addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: Self.refreshCommandTitle())) } @@ -46,6 +41,11 @@ class TimelineLikeTableViewController: EnhancedTableViewController, Refres tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 + #if !targetEnvironment(macCatalyst) + self.refreshControl = UIRefreshControl() + self.refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged) + #endif + if let prefetchSource = self as? UITableViewDataSourcePrefetching { tableView.prefetchDataSource = prefetchSource } diff --git a/Tusker/Views/Account Cell/AccountTableViewCell.swift b/Tusker/Views/Account Cell/AccountTableViewCell.swift index 6df6c309..03e52757 100644 --- a/Tusker/Views/Account Cell/AccountTableViewCell.swift +++ b/Tusker/Views/Account Cell/AccountTableViewCell.swift @@ -109,3 +109,16 @@ extension AccountTableViewCell: MenuPreviewProvider { ) } } + +extension AccountTableViewCell: DraggableTableViewCell { + func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { + guard let account = mastodonController.persistentContainer.account(for: accountID), + let currentAccountID = mastodonController.accountInfo?.id else { + return [] + } + let provider = NSItemProvider(object: account.url as NSURL) + let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID) + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] + } +} diff --git a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift index 6f089702..dd00926a 100644 --- a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift +++ b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift @@ -219,3 +219,16 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider { }) } } + +extension FollowNotificationGroupTableViewCell: DraggableTableViewCell { + func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { + guard group.notifications.count == 1 else { + return [] + } + let notification = group.notifications[0] + let provider = NSItemProvider(object: notification.account.url as NSURL) + let activity = UserActivityManager.showProfileActivity(id: notification.account.id, accountID: mastodonController.accountInfo!.id) + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] + } +} diff --git a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift index d9d28433..b6fb0608 100644 --- a/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift +++ b/Tusker/Views/Notifications/FollowRequestNotificationTableViewCell.swift @@ -181,3 +181,12 @@ extension FollowRequestNotificationTableViewCell: MenuPreviewProvider { }) } } + +extension FollowRequestNotificationTableViewCell: DraggableTableViewCell { + func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { + let provider = NSItemProvider(object: account.url as NSURL) + let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: mastodonController.accountInfo!.id) + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] + } +} diff --git a/Tusker/Views/Status/BaseStatusTableViewCell.swift b/Tusker/Views/Status/BaseStatusTableViewCell.swift index 14d04e10..0b286720 100644 --- a/Tusker/Views/Status/BaseStatusTableViewCell.swift +++ b/Tusker/Views/Status/BaseStatusTableViewCell.swift @@ -483,10 +483,13 @@ extension BaseStatusTableViewCell: MenuPreviewProvider { extension BaseStatusTableViewCell: UIDragInteractionDelegate { func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] { - guard let currentAccountID = mastodonController.accountInfo?.id else { + guard let currentAccountID = mastodonController.accountInfo?.id, + let account = mastodonController.persistentContainer.account(for: accountID) else { return [] } - let provider = NSItemProvider(object: UserActivityManager.showProfileActivity(id: accountID, accountID: currentAccountID)) + let provider = NSItemProvider(object: account.url as NSURL) + let activity = UserActivityManager.showProfileActivity(id: accountID, accountID: currentAccountID) + provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } } diff --git a/Tusker/Views/Status/TimelineStatusTableViewCell.swift b/Tusker/Views/Status/TimelineStatusTableViewCell.swift index 239dd1b7..79ea67a9 100644 --- a/Tusker/Views/Status/TimelineStatusTableViewCell.swift +++ b/Tusker/Views/Status/TimelineStatusTableViewCell.swift @@ -296,3 +296,16 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider { } } + +extension TimelineStatusTableViewCell: DraggableTableViewCell { + func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { + guard let status = mastodonController.persistentContainer.status(for: statusID), + let accountID = mastodonController.accountInfo?.id else { + return [] + } + let provider = NSItemProvider(object: status.url! as NSURL) + let activity = UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID) + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] + } +}