Convert Explore screen to use list-style collection view

This commit is contained in:
Shadowfacts 2021-02-06 13:48:31 -05:00
parent 669d55500a
commit 73aceda97f
2 changed files with 198 additions and 175 deletions

View File

@ -97,7 +97,6 @@
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; }; D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; };
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */; }; D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */; };
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.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 */; }; D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; };
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */; }; D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */; };
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.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 */; }; D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.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 */; }; 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 */; }; D6C82B5625C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C82B5425C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift */; };
D6C82B5725C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */; }; D6C82B5725C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */; };
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; }; 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 = "<group>"; }; D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbookmarkStatusActivity.swift; sourceTree = "<group>"; }; D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; }; D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; };
D627943D23A564D400D38C68 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = "<group>"; };
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; }; D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; };
D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTableViewController.swift; sourceTree = "<group>"; }; D627944923A6AD6100D38C68 /* BookmarksTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTableViewController.swift; sourceTree = "<group>"; };
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; }; D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
@ -633,6 +632,7 @@
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; }; D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; }; D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; }; D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsContainerView.swift; sourceTree = "<group>"; };
D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewController.swift; sourceTree = "<group>"; };
D6C82B5425C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandThreadTableViewCell.swift; sourceTree = "<group>"; }; D6C82B5425C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandThreadTableViewCell.swift; sourceTree = "<group>"; };
D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExpandThreadTableViewCell.xib; sourceTree = "<group>"; }; D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExpandThreadTableViewCell.xib; sourceTree = "<group>"; };
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = "<group>"; }; D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = "<group>"; };
@ -910,7 +910,7 @@
D627943C23A5635D00D38C68 /* Explore */ = { D627943C23A5635D00D38C68 /* Explore */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D627943D23A564D400D38C68 /* ExploreViewController.swift */, D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */,
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */,
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */,
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
@ -1996,7 +1996,6 @@
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */, D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */, D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */, D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */, D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */, D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */,
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */, D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
@ -2080,6 +2079,7 @@
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */, D6412B0924B0291E00F5412E /* MyProfileViewController.swift in Sources */,
D6A6C10525B6138A00298D0F /* StatusTablePrefetching.swift in Sources */, D6A6C10525B6138A00298D0F /* StatusTablePrefetching.swift in Sources */,
D6C82B4125C5BB7E0017F1E6 /* ExploreViewController.swift in Sources */,
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */, D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */,
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */, D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */,
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */, D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */,

View File

@ -10,23 +10,22 @@ import UIKit
import Combine import Combine
import Pachyderm import Pachyderm
class ExploreViewController: EnhancedTableViewController { class ExploreViewController: UIViewController, UICollectionViewDelegate {
weak var mastodonController: MastodonController! weak var mastodonController: MastodonController!
var dataSource: DataSource! private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
var resultsController: SearchResultsViewController! private(set) var resultsController: SearchResultsViewController!
var searchController: UISearchController! private(set) var searchController: UISearchController!
var searchControllerStatusOnAppearance: Bool? = nil var searchControllerStatusOnAppearance: Bool? = nil
init(mastodonController: MastodonController) { init(mastodonController: MastodonController) {
self.mastodonController = mastodonController self.mastodonController = mastodonController
super.init(style: .insetGrouped) super.init(nibName: nil, bundle: nil)
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")
@ -39,62 +38,18 @@ class ExploreViewController: EnhancedTableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
tableView.register(UINib(nibName: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: "basicCell") 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)
dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in dataSource = createDataSource()
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath) applyInitialSnapshot()
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
let account = mastodonController.accountInfo!
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
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 = SearchResultsViewController(mastodonController: mastodonController)
resultsController.exploreNavigationController = self.navigationController! resultsController.exploreNavigationController = self.navigationController!
@ -109,8 +64,20 @@ class ExploreViewController: EnhancedTableViewController {
NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil)
}
reloadLists() override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 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) { override func viewDidAppear(_ animated: Bool) {
@ -125,16 +92,69 @@ class ExploreViewController: EnhancedTableViewController {
} }
} }
func reloadLists() { private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(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<UICollectionViewListCell, Item> { (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<Section, Item>(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<Section, Item>()
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() let request = Client.getLists()
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(lists, _) = response else { guard case let .success(lists, _) = response else {
fatalError() return
} }
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .lists)) 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 { DispatchQueue.main.async {
self.dataSource.apply(snapshot) self.dataSource.apply(snapshot)
@ -142,26 +162,31 @@ class ExploreViewController: EnhancedTableViewController {
} }
} }
@objc func savedHashtagsChanged() { @objc private func savedHashtagsChanged() {
let account = mastodonController.accountInfo! let account = mastodonController.accountInfo!
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags)) 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) dataSource.apply(snapshot)
} }
@objc func savedInstancesChanged() { @objc private func savedInstancesChanged() {
let account = mastodonController.accountInfo! let account = mastodonController.accountInfo!
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances)) 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) dataSource.apply(snapshot)
} }
func deleteList(_ list: List) { private func deleteList(_ list: List, completion: @escaping (Bool) -> Void) {
let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title) 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) 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 alert.addAction(UIAlertAction(title: NSLocalizedString("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in
let request = List.delete(list) let request = List.delete(list)
@ -174,6 +199,7 @@ class ExploreViewController: EnhancedTableViewController {
snapshot.deleteItems([.list(list)]) snapshot.deleteItems([.list(list)])
DispatchQueue.main.async { DispatchQueue.main.async {
self.dataSource.apply(snapshot) self.dataSource.apply(snapshot)
completion(true)
} }
} }
})) }))
@ -190,9 +216,38 @@ class ExploreViewController: EnhancedTableViewController {
SavedDataManager.shared.remove(instance: instanceURL, for: account) 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)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 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)
])
}
// MARK: - Collection View Delegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch dataSource.itemIdentifier(for: indexPath) { switch dataSource.itemIdentifier(for: indexPath) {
case nil: case nil:
return return
@ -204,7 +259,7 @@ class ExploreViewController: EnhancedTableViewController {
show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil) show(ListTimelineViewController(for: list, mastodonController: mastodonController), sender: nil)
case .addList: 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) 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.addTextField(configurationHandler: nil)
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "new list alert cancel button"), style: .cancel, handler: 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) show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
case .addSavedHashtag: case .addSavedHashtag:
tableView.selectRow(at: nil, animated: true, scrollPosition: .none) collectionView.deselectItem(at: indexPath, animated: true)
let navController = UINavigationController(rootViewController: AddSavedHashtagViewController(mastodonController: mastodonController)) let navController = UINavigationController(rootViewController: AddSavedHashtagViewController(mastodonController: mastodonController))
present(navController, animated: true) present(navController, animated: true)
@ -240,7 +295,7 @@ class ExploreViewController: EnhancedTableViewController {
show(InstanceTimelineViewController(for: url, parentMastodonController: mastodonController), sender: nil) show(InstanceTimelineViewController(for: url, parentMastodonController: mastodonController), sender: nil)
case .findInstance: case .findInstance:
tableView.selectRow(at: nil, animated: true, scrollPosition: .none) collectionView.deselectItem(at: indexPath, animated: true)
let findController = FindInstanceViewController(parentMastodonController: mastodonController) let findController = FindInstanceViewController(parentMastodonController: mastodonController)
findController.instanceTimelineDelegate = self findController.instanceTimelineDelegate = self
let navController = UINavigationController(rootViewController: findController) let navController = UINavigationController(rootViewController: findController)
@ -248,37 +303,6 @@ class ExploreViewController: EnhancedTableViewController {
} }
} }
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
}
}
} }
extension ExploreViewController { extension ExploreViewController {
@ -287,7 +311,21 @@ extension ExploreViewController {
case lists case lists
case savedHashtags case savedHashtags
case savedInstances 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 { enum Item: Hashable {
case bookmarks case bookmarks
case list(List) case list(List)
@ -297,7 +335,45 @@ extension ExploreViewController {
case savedInstance(URL) case savedInstance(URL)
case findInstance 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) { switch (lhs, rhs) {
case (.bookmarks, .bookmarks): case (.bookmarks, .bookmarks):
return true return true
@ -317,6 +393,7 @@ extension ExploreViewController {
return false return false
} }
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
switch self { switch self {
case .bookmarks: case .bookmarks:
@ -339,57 +416,6 @@ extension ExploreViewController {
} }
} }
} }
class DataSource: UITableViewDiffableDataSource<Section, Item> {
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 { extension ExploreViewController: InstanceTimelineViewControllerDelegate {
@ -404,12 +430,13 @@ extension ExploreViewController: InstanceTimelineViewControllerDelegate {
} }
} }
extension ExploreViewController { extension ExploreViewController: UICollectionViewDragDelegate {
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard let item = dataSource.itemIdentifier(for: indexPath), guard let item = dataSource.itemIdentifier(for: indexPath),
let accountID = mastodonController.accountInfo?.id else { let accountID = mastodonController.accountInfo?.id else {
return [] return []
} }
let provider: NSItemProvider let provider: NSItemProvider
switch item { switch item {
case .bookmarks: case .bookmarks:
@ -425,11 +452,7 @@ extension ExploreViewController {
case let .savedInstance(url): case let .savedInstance(url):
provider = NSItemProvider(object: url as NSURL) provider = NSItemProvider(object: url as NSURL)
// todo: should dragging public timelines into new windows be supported? // todo: should dragging public timelines into new windows be supported?
case .addList: case .addList, .addSavedHashtag, .findInstance:
return []
case .addSavedHashtag:
return []
case .findInstance:
return [] return []
} }
return [UIDragItem(itemProvider: provider)] return [UIDragItem(itemProvider: provider)]