Add multi-window drag and drop to all the things

This commit is contained in:
Shadowfacts 2020-12-14 18:44:41 -05:00
parent 522c9b2b03
commit 30297c2390
18 changed files with 153 additions and 24 deletions

View File

@ -82,6 +82,7 @@
D62275A624F1C81800B82A16 /* ComposeReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A524F1C81800B82A16 /* ComposeReplyView.swift */; }; D62275A624F1C81800B82A16 /* ComposeReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A524F1C81800B82A16 /* ComposeReplyView.swift */; };
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; }; D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; };
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A924F1E01C00B82A16 /* ComposeTextView.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 */; }; D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; }; D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; };
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.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 = "<group>"; }; D62275A524F1C81800B82A16 /* ComposeReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyView.swift; sourceTree = "<group>"; };
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyContentView.swift; sourceTree = "<group>"; }; D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyContentView.swift; sourceTree = "<group>"; };
D62275A924F1E01C00B82A16 /* ComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTextView.swift; sourceTree = "<group>"; }; D62275A924F1E01C00B82A16 /* ComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTextView.swift; sourceTree = "<group>"; };
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; }; D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = "<group>"; }; D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = "<group>"; };
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; }; D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
@ -1382,6 +1384,7 @@
D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */, D65C6BF425478A9C00A6E89C /* BackgroundableViewController.swift */,
D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */, D6B81F3B2560365300F6E31D /* RefreshableViewController.swift */,
D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */, D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */,
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */,
); );
path = Utilities; path = Utilities;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1895,6 +1898,7 @@
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */, D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */, D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */,
D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */, D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */,
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */,
D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */, D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */,

View File

@ -27,6 +27,8 @@ class BookmarksTableViewController: EnhancedTableViewController {
super.init(style: .plain) super.init(style: .plain)
dragEnabled = true
title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title") title = NSLocalizedString("Bookmarks", comment: "bookmarks screen title")
} }

View File

@ -36,6 +36,8 @@ class ConversationTableViewController: EnhancedTableViewController {
self.mastodonController = mastodonController self.mastodonController = mastodonController
super.init(style: .plain) super.init(style: .plain)
dragEnabled = true
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {

View File

@ -45,6 +45,8 @@ class DraftsTableViewController: UITableViewController {
tableView.register(UINib(nibName: "DraftTableViewCell", bundle: nil), forCellReuseIdentifier: "draftCell") tableView.register(UINib(nibName: "DraftTableViewCell", bundle: nil), forCellReuseIdentifier: "draftCell")
tableView.dragDelegate = self
drafts = DraftsManager.shared.sorted.filter { (draft) in drafts = DraftsManager.shared.sorted.filter { (draft) in
draft.accountID == account.id && draft != excludedDraft 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)]
}
}

View File

@ -26,6 +26,8 @@ class ExploreViewController: EnhancedTableViewController {
super.init(style: .insetGrouped) super.init(style: .insetGrouped)
dragEnabled = true
title = NSLocalizedString("Explore", comment: "explore tab title") title = NSLocalizedString("Explore", comment: "explore tab title")
tabBarItem.image = UIImage(systemName: "magnifyingglass") tabBarItem.image = UIImage(systemName: "magnifyingglass")
} }
@ -401,3 +403,35 @@ extension ExploreViewController: InstanceTimelineViewControllerDelegate {
dismiss(animated: true) 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)]
}
}

View File

@ -30,6 +30,8 @@ class NotificationsTableViewController: TimelineLikeTableViewController<Notifica
self.mastodonController = mastodonController self.mastodonController = mastodonController
super.init() super.init()
dragEnabled = true
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {

View File

@ -28,6 +28,8 @@ class ProfileStatusesViewController: TimelineLikeTableViewController<TimelineEnt
self.mastodonController = mastodonController self.mastodonController = mastodonController
super.init() super.init()
dragEnabled = true
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {

View File

@ -49,6 +49,8 @@ class SearchResultsViewController: EnhancedTableViewController {
super.init(style: .grouped) super.init(style: .grouped)
dragEnabled = true
title = NSLocalizedString("Search", comment: "search screen title") title = NSLocalizedString("Search", comment: "search screen title")
} }

View File

@ -46,6 +46,8 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
super.init(style: .grouped) super.init(style: .grouped)
dragEnabled = true
switch actionType { switch actionType {
case .favorite: case .favorite:
title = NSLocalizedString("Favorited By", comment: "status favorited by accounts list title") title = NSLocalizedString("Favorited By", comment: "status favorited by accounts list title")

View File

@ -25,6 +25,8 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
super.init() super.init()
dragEnabled = true
title = timeline.title title = timeline.title
tabBarItem.image = timeline.tabBarImage tabBarItem.image = timeline.tabBarImage
@ -53,8 +55,6 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
super.viewDidLoad() super.viewDidLoad()
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell")
tableView.dragDelegate = self
} }
override class func refreshCommandTitle() -> String { override class func refreshCommandTitle() -> 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]
}
}

View File

@ -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]
}

View File

@ -16,6 +16,16 @@ class EnhancedTableViewController: UITableViewController {
private var prevScrollViewContentOffset: CGPoint? private var prevScrollViewContentOffset: CGPoint?
private(set) var scrollViewDirection: CGFloat = 0 private(set) var scrollViewDirection: CGFloat = 0
var dragEnabled = false
override func viewDidLoad() {
super.viewDidLoad()
if dragEnabled {
tableView.dragDelegate = self
}
}
// MARK: Scroll View Delegate // MARK: Scroll View Delegate
override func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { 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 { extension EnhancedTableViewController: TabBarScrollableViewController {
func tabBarScrollToTop() { func tabBarScrollToTop() {
if scrollViewShouldScrollToTop(tableView) { if scrollViewShouldScrollToTop(tableView) {

View File

@ -24,11 +24,6 @@ class TimelineLikeTableViewController<Item>: EnhancedTableViewController, Refres
init() { init() {
super.init(style: .plain) 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())) addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: Self.refreshCommandTitle()))
} }
@ -46,6 +41,11 @@ class TimelineLikeTableViewController<Item>: EnhancedTableViewController, Refres
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 140 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 { if let prefetchSource = self as? UITableViewDataSourcePrefetching {
tableView.prefetchDataSource = prefetchSource tableView.prefetchDataSource = prefetchSource
} }

View File

@ -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)]
}
}

View File

@ -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)]
}
}

View File

@ -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)]
}
}

View File

@ -483,10 +483,13 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
extension BaseStatusTableViewCell: UIDragInteractionDelegate { extension BaseStatusTableViewCell: UIDragInteractionDelegate {
func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] { 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 [] 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)] return [UIDragItem(itemProvider: provider)]
} }
} }

View File

@ -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)]
}
}