Convert Explore screen to use list-style collection view
This commit is contained in:
parent
669d55500a
commit
73aceda97f
|
@ -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 */,
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue