From 73aceda97fdbd928ec42a854aea598f3bdf878c0 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 6 Feb 2021 13:48:31 -0500 Subject: [PATCH] Convert Explore screen to use list-style collection view --- Tusker.xcodeproj/project.pbxproj | 8 +- .../Explore/ExploreViewController.swift | 365 ++++++++++-------- 2 files changed, 198 insertions(+), 175 deletions(-) diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 0819c72d..9dd7d33b 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -97,7 +97,6 @@ D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; }; D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */; }; D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */; }; - D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943D23A564D400D38C68 /* ExploreViewController.swift */; }; D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; }; D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */; }; D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; }; @@ -272,6 +271,7 @@ D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; }; D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; }; D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */; }; + D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */; }; D6C82B5625C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C82B5425C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift */; }; D6C82B5725C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */; }; D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; }; @@ -460,7 +460,6 @@ D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = ""; }; D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbookmarkStatusActivity.swift; sourceTree = ""; }; D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = ""; }; - D627943D23A564D400D38C68 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = ""; }; D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = ""; }; D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTableViewController.swift; sourceTree = ""; }; D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = ""; }; @@ -633,6 +632,7 @@ D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = ""; }; D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = ""; }; + D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = ""; }; D6C82B5425C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandThreadTableViewCell.swift; sourceTree = ""; }; D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExpandThreadTableViewCell.xib; sourceTree = ""; }; D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = ""; }; @@ -910,7 +910,7 @@ D627943C23A5635D00D38C68 /* Explore */ = { isa = PBXGroup; children = ( - D627943D23A564D400D38C68 /* ExploreViewController.swift */, + D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, @@ -1996,7 +1996,6 @@ D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */, D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */, D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */, - D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */, D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */, D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */, D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */, @@ -2080,6 +2079,7 @@ D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */, D6A6C10525B6138A00298D0F /* StatusTablePrefetching.swift in Sources */, + D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */, D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */, D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */, D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */, diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 31305cdb..1c8482f8 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -10,23 +10,22 @@ import UIKit import Combine import Pachyderm -class ExploreViewController: EnhancedTableViewController { +class ExploreViewController: UIViewController, UICollectionViewDelegate { weak var mastodonController: MastodonController! - var dataSource: DataSource! + private var collectionView: UICollectionView! + private var dataSource: UICollectionViewDiffableDataSource! - var resultsController: SearchResultsViewController! - var searchController: UISearchController! + private(set) var resultsController: SearchResultsViewController! + private(set) var searchController: UISearchController! var searchControllerStatusOnAppearance: Bool? = nil init(mastodonController: MastodonController) { self.mastodonController = mastodonController - super.init(style: .insetGrouped) - - dragEnabled = true + super.init(nibName: nil, bundle: nil) title = NSLocalizedString("Explore", comment: "explore tab title") tabBarItem.image = UIImage(systemName: "magnifyingglass") @@ -38,64 +37,20 @@ class ExploreViewController: EnhancedTableViewController { override func viewDidLoad() { super.viewDidLoad() + + var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) + configuration.trailingSwipeActionsConfigurationProvider = self.trailingSwipeActionsForCell(at:) + configuration.headerMode = .supplementary + let layout = UICollectionViewCompositionalLayout.list(using: configuration) + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) + collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + collectionView.delegate = self + collectionView.dragDelegate = self + view.addSubview(collectionView) - tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: "basicCell") - - dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in - let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath) - - switch item { - case .bookmarks: - cell.imageView!.image = UIImage(systemName: "bookmark.fill") - cell.textLabel!.text = NSLocalizedString("Bookmarks", comment: "bookmarks nav item title") - cell.accessoryType = .disclosureIndicator - - case let .list(list): - cell.imageView!.image = UIImage(systemName: "list.bullet") - cell.textLabel!.text = list.title - cell.accessoryType = .disclosureIndicator - - case .addList: - cell.imageView!.image = UIImage(systemName: "plus") - cell.textLabel!.text = NSLocalizedString("New List...", comment: "new list nav item title") - cell.accessoryType = .none - - case let .savedHashtag(hashtag): - cell.imageView!.image = UIImage(systemName: "number") - cell.textLabel!.text = hashtag.name - cell.accessoryType = .disclosureIndicator - - case .addSavedHashtag: - cell.imageView!.image = UIImage(systemName: "plus") - cell.textLabel!.text = NSLocalizedString("Save Hashtag...", comment: "save hashtag nav item title") - cell.accessoryType = .none - - case let .savedInstance(url): - cell.imageView!.image = UIImage(systemName: "globe") - cell.textLabel!.text = url.host! - cell.accessoryType = .disclosureIndicator - - case .findInstance: - cell.imageView!.image = UIImage(systemName: "magnifyingglass") - cell.textLabel!.text = NSLocalizedString("Find An Instance...", comment: "find instance nav item title") - cell.accessoryType = .none - } - - return cell - }) - dataSource.exploreController = self + dataSource = createDataSource() + applyInitialSnapshot() - let account = mastodonController.accountInfo! - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.bookmarks, .lists, .savedHashtags, .savedInstances]) - snapshot.appendItems([.bookmarks], toSection: .bookmarks) - snapshot.appendItems([.addList], toSection: .lists) - snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags) - snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances) - // the initial, static items should not be displayed with an animation - dataSource.apply(snapshot, animatingDifferences: false) - resultsController = SearchResultsViewController(mastodonController: mastodonController) resultsController.exploreNavigationController = self.navigationController! searchController = UISearchController(searchResultsController: resultsController) @@ -109,8 +64,20 @@ class ExploreViewController: EnhancedTableViewController { NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) - reloadLists() + // Can't use UICollectionViewController's builtin version of this because it requires + // the collection view layout be passed into the constructor. Swipe actions for list collection views + // are created by passing a closure to the layout's configuration. This closure needs to capture + // `self`, so it can't be passed into the super constructor. + if let indexPaths = collectionView.indexPathsForSelectedItems { + for indexPath in indexPaths { + collectionView.deselectItem(at: indexPath, animated: true) + } + } } override func viewDidAppear(_ animated: Bool) { @@ -125,43 +92,101 @@ class ExploreViewController: EnhancedTableViewController { } } - func reloadLists() { + private func createDataSource() -> UICollectionViewDiffableDataSource { + let sectionHeaderCell = UICollectionView.SupplementaryRegistration(elementKind: UICollectionView.elementKindSectionHeader) { (headerView, collectionView, indexPath) in + let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section] + + var config = headerView.defaultContentConfiguration() + config.text = section.label + headerView.contentConfiguration = config + } + + let listCell = UICollectionView.CellRegistration { (cell, indexPath, item) in + var config = cell.defaultContentConfiguration() + config.text = item.label + config.image = item.image + cell.contentConfiguration = config + + switch item { + case .addList, .addSavedHashtag, .findInstance: + cell.accessories = [] + default: + cell.accessories = [.disclosureIndicator()] + } + } + + let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) in + return collectionView.dequeueConfiguredReusableCell(using: listCell, for: indexPath, item: item) + } + dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in + if elementKind == UICollectionView.elementKindSectionHeader { + return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderCell, for: indexPath) + } else { + return nil + } + } + return dataSource + } + + private func applyInitialSnapshot() { + let account = mastodonController.accountInfo! + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections(Section.allCases) + snapshot.appendItems([.bookmarks], toSection: .bookmarks) + snapshot.appendItems([.addList], toSection: .lists) + snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags) + snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags) + snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) }, toSection: .savedInstances) + snapshot.appendItems([.findInstance], toSection: .savedInstances) + dataSource.apply(snapshot, animatingDifferences: false) + + reloadLists() + } + + private func reloadLists() { let request = Client.getLists() mastodonController.run(request) { (response) in guard case let .success(lists, _) = response else { - fatalError() + return } var snapshot = self.dataSource.snapshot() snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .lists)) - snapshot.appendItems(lists.map { .list($0) } + [.addList], toSection: .lists) - + snapshot.appendItems(lists.map { .list($0) }, toSection: .lists) + snapshot.appendItems([.addList], toSection: .lists) + DispatchQueue.main.async { self.dataSource.apply(snapshot) } } } - @objc func savedHashtagsChanged() { + @objc private func savedHashtagsChanged() { let account = mastodonController.accountInfo! var snapshot = dataSource.snapshot() snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags)) - snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) } + [.addSavedHashtag], toSection: .savedHashtags) + snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags) + snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags) dataSource.apply(snapshot) } - @objc func savedInstancesChanged() { + @objc private func savedInstancesChanged() { let account = mastodonController.accountInfo! var snapshot = dataSource.snapshot() snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances)) - snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) } + [.findInstance], toSection: .savedInstances) + snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) }, toSection: .savedInstances) + snapshot.appendItems([.findInstance], toSection: .savedInstances) dataSource.apply(snapshot) } - func deleteList(_ list: List) { - let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title) + private func deleteList(_ list: List, completion: @escaping (Bool) -> Void) { + let titleFormat = NSLocalizedString("Are you sure you want to delete the '%@' list?", comment: "delete list alert title") + let title = String(format: titleFormat, list.title) let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "delete list alert cancel button"), style: .cancel, handler: nil)) + alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "delete list alert cancel button"), style: .cancel, handler: { (_) in + completion(false) + })) alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in let request = List.delete(list) @@ -174,6 +199,7 @@ class ExploreViewController: EnhancedTableViewController { snapshot.deleteItems([.list(list)]) DispatchQueue.main.async { self.dataSource.apply(snapshot) + completion(true) } } })) @@ -190,9 +216,38 @@ class ExploreViewController: EnhancedTableViewController { SavedDataManager.shared.remove(instance: instanceURL, for: account) } - // MARK: - Table view delegate + private func trailingSwipeActionsForCell(at indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let handler: UIContextualAction.Handler + switch dataSource.itemIdentifier(for: indexPath) { + case let .list(list): + handler = { (_, _, completion) in + self.deleteList(list, completion: completion) + } + + case let .savedHashtag(hashtag): + handler = { (_, _, completion) in + self.removeSavedHashtag(hashtag) + completion(true) + } + + case let .savedInstance(url): + handler = { (_, _, completion) in + self.removeSavedInstance(url) + completion(true) + } + + default: + return nil + } + + return UISwipeActionsConfiguration(actions: [ + UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "delete swipe action title"), handler: handler) + ]) + } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // MARK: - Collection View Delegate + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { switch dataSource.itemIdentifier(for: indexPath) { case nil: return @@ -204,7 +259,7 @@ class ExploreViewController: EnhancedTableViewController { show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil) case .addList: - tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + collectionView.deselectItem(at: indexPath, animated: true) let alert = UIAlertController(title: NSLocalizedString("New List", comment: "new list alert title"), message: NSLocalizedString("Choose a title for your new list", comment: "new list alert message"), preferredStyle: .alert) alert.addTextField(configurationHandler: nil) alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "new list alert cancel button"), style: .cancel, handler: nil)) @@ -232,7 +287,7 @@ class ExploreViewController: EnhancedTableViewController { show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil) case .addSavedHashtag: - tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + collectionView.deselectItem(at: indexPath, animated: true) let navController = UINavigationController(rootViewController: AddSavedHashtagViewController(mastodonController: mastodonController)) present(navController, animated: true) @@ -240,44 +295,13 @@ class ExploreViewController: EnhancedTableViewController { show(InstanceTimelineViewController(for: url, parentMastodonController: mastodonController), sender: nil) case .findInstance: - tableView.selectRow(at: nil, animated: true, scrollPosition: .none) + collectionView.deselectItem(at: indexPath, animated: true) let findController = FindInstanceViewController(parentMastodonController: mastodonController) findController.instanceTimelineDelegate = self let navController = UINavigationController(rootViewController: findController) present(navController, animated: true) } } - - override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { - return .delete - } - - override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - switch dataSource.itemIdentifier(for: indexPath) { - case .bookmarks: - return UIContextMenuConfiguration(identifier: nil, previewProvider: { - return BookmarksTableViewController(mastodonController: self.mastodonController) - }, actionProvider: nil) - - case let .list(list): - return UIContextMenuConfiguration(identifier: nil, previewProvider: { - return ListTimelineViewController(for: list, mastodonController: self.mastodonController) - }, actionProvider: nil) - - case let .savedHashtag(hashtag): - return UIContextMenuConfiguration(identifier: nil, previewProvider: { - return HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController) - }, actionProvider: nil) - - case let .savedInstance(url): - return UIContextMenuConfiguration(identifier: nil, previewProvider: { - return InstanceTimelineViewController(for: url, parentMastodonController: self.mastodonController) - }, actionProvider: nil) - - default: - return nil - } - } } @@ -287,7 +311,21 @@ extension ExploreViewController { case lists case savedHashtags case savedInstances + + var label: String? { + switch self { + case .bookmarks: + return nil + case .lists: + return NSLocalizedString("Lists", comment: "explore lists section title") + case .savedHashtags: + return NSLocalizedString("Saved Hashtags", comment: "explore saved hashtags section title") + case .savedInstances: + return NSLocalizedString("Instance Timelines", comment: "explore instance timelines section title") + } + } } + enum Item: Hashable { case bookmarks case list(List) @@ -296,8 +334,46 @@ extension ExploreViewController { case addSavedHashtag case savedInstance(URL) case findInstance - - static func == (lhs: ExploreViewController.Item, rhs: ExploreViewController.Item) -> Bool { + + var label: String { + switch self { + case .bookmarks: + return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title") + case let .list(list): + return list.title + case .addList: + return NSLocalizedString("New List...", comment: "new list nav item title") + case let .savedHashtag(hashtag): + return hashtag.name + case .addSavedHashtag: + return NSLocalizedString("Save Hashtag...", comment: "save hashtag nav item title") + case let .savedInstance(url): + return url.host! + case .findInstance: + return NSLocalizedString("Find An Instance...", comment: "find instance nav item title") + } + } + + var image: UIImage { + let name: String + switch self { + case .bookmarks: + name = "bookmark.fill" + case .list(_): + name = "list.bullet" + case .addList, .addSavedHashtag: + name = "plus" + case .savedHashtag(_): + name = "number" + case .savedInstance(_): + name = "globe" + case .findInstance: + name = "magnifyingglass" + } + return UIImage(systemName: name)! + } + + static func == (lhs: Item, rhs: Item) -> Bool { switch (lhs, rhs) { case (.bookmarks, .bookmarks): return true @@ -317,6 +393,7 @@ extension ExploreViewController { return false } } + func hash(into hasher: inout Hasher) { switch self { case .bookmarks: @@ -339,57 +416,6 @@ extension ExploreViewController { } } } - - class DataSource: UITableViewDiffableDataSource { - - weak var exploreController: ExploreViewController? - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch section { - case 1: - return NSLocalizedString("Lists", comment: "explore lists section title") - case 2: - return NSLocalizedString("Saved Hashtags", comment: "explore saved hashtags section title") - case 3: - return NSLocalizedString("Instance Timelines", comment: "explore instance timelines section title") - default: - return nil - } - } - - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - switch itemIdentifier(for: indexPath) { - case .list(_): - return true - case .savedHashtag(_): - return true - case .savedInstance(_): - return true - default: - return false - } - } - - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - guard editingStyle == .delete, - let exploreController = exploreController else { - return - } - - switch itemIdentifier(for: indexPath) { - case let .list(list): - exploreController.deleteList(list) - case let .savedHashtag(hashtag): - exploreController.removeSavedHashtag(hashtag) - case let .savedInstance(url): - exploreController.removeSavedInstance(url) - default: - return - } - - } - - } } extension ExploreViewController: InstanceTimelineViewControllerDelegate { @@ -404,12 +430,13 @@ extension ExploreViewController: InstanceTimelineViewControllerDelegate { } } -extension ExploreViewController { - override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { +extension ExploreViewController: UICollectionViewDragDelegate { + func collectionView(_ collectionView: UICollectionView, 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: @@ -425,11 +452,7 @@ extension ExploreViewController { 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: + case .addList, .addSavedHashtag, .findInstance: return [] } return [UIDragItem(itemProvider: provider)]