diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index d9ffd49f..b87b7e44 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -41,7 +41,7 @@ class MainSidebarViewController: UIViewController { } var exploreTabItems: [Item] { - var items: [Item] = [.explore, .bookmarks, .trendingStatuses, .profileDirectory] + var items: [Item] = [.explore, .bookmarks, .profileDirectory] let snapshot = dataSource.snapshot() for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) { items.append(.list(list)) @@ -195,9 +195,6 @@ class MainSidebarViewController: UIViewController { discoverSnapshot.append([ .profileDirectory, ], to: .discoverHeader) - if mastodonController.instanceFeatures.trendingStatusesAndLinks { - discoverSnapshot.insert([.trendingStatuses], before: .profileDirectory) - } dataSource.apply(discoverSnapshot, to: .discover) } @@ -388,7 +385,7 @@ extension MainSidebarViewController { enum Item: Hashable { case tab(MainTabBarViewController.Tab) case explore, bookmarks - case discoverHeader, trendingStatuses, profileDirectory + case discoverHeader, profileDirectory case listsHeader, list(List), addList case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag case savedInstancesHeader, savedInstance(URL), addSavedInstance @@ -403,8 +400,6 @@ extension MainSidebarViewController { return "Bookmarks" case .discoverHeader: return "Discover" - case .trendingStatuses: - return "Trending Posts" case .profileDirectory: return "Profile Directory" case .listsHeader: @@ -436,8 +431,6 @@ extension MainSidebarViewController { return "magnifyingglass" case .bookmarks: return "bookmark" - case .trendingStatuses: - return "square.text.square" case .profileDirectory: return "person.2.fill" case .list(_): diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index 68fda5b3..1e9c0b86 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -232,7 +232,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate { tabBarViewController.select(tab: .explore) - case .bookmarks, .trendingStatuses, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_): + case .bookmarks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_): tabBarViewController.select(tab: .explore) // Make sure the Explore VC doesn't show it's search bar when it appears, in case the user was previously // in compact mode and performing a search. @@ -309,7 +309,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate { case let instanceVC as InstanceTimelineViewController: exploreItem = .savedInstance(instanceVC.instanceURL) case is TrendingStatusesViewController: - exploreItem = .trendingStatuses + exploreItem = .explore case is TrendingHashtagsViewController: exploreItem = .explore case is TrendingLinksViewController: @@ -376,8 +376,6 @@ fileprivate extension MainSidebarViewController.Item { return SearchViewController(mastodonController: mastodonController) case .bookmarks: return BookmarksTableViewController(mastodonController: mastodonController) - case .trendingStatuses: - return TrendingStatusesViewController(mastodonController: mastodonController) case .profileDirectory: return ProfileDirectoryViewController(mastodonController: mastodonController) case let .list(list): diff --git a/Tusker/Screens/Search/SearchViewController.swift b/Tusker/Screens/Search/SearchViewController.swift index b9655ad0..2fc4a674 100644 --- a/Tusker/Screens/Search/SearchViewController.swift +++ b/Tusker/Screens/Search/SearchViewController.swift @@ -11,11 +11,11 @@ import Pachyderm import SafariServices import WebURLFoundationExtras -class SearchViewController: UIViewController { +class SearchViewController: UIViewController, CollectionViewController { weak var mastodonController: MastodonController! - private var collectionView: UICollectionView! + var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! var resultsController: SearchResultsViewController! @@ -23,6 +23,8 @@ class SearchViewController: UIViewController { var searchControllerStatusOnAppearance: Bool? = nil + private var loadTask: Task? + init(mastodonController: MastodonController) { self.mastodonController = mastodonController @@ -59,7 +61,13 @@ class SearchViewController: UIViewController { section.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(12)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading) ] + section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0) return section + + case .trendingStatuses: + var listConfig = UICollectionLayoutListConfiguration(appearance: .grouped) + listConfig.headerMode = .supplementary + return NSCollectionLayoutSection.list(using: listConfig, layoutEnvironment: environment) default: fatalError("unimplemented") @@ -100,7 +108,10 @@ class SearchViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - Task(priority: .userInitiated) { + clearSelectionOnAppear(animated: animated) + + loadTask?.cancel() + loadTask = Task(priority: .userInitiated) { if (try? await mastodonController.getOwnInstance()) != nil { await applySnapshot() } @@ -133,6 +144,11 @@ class SearchViewController: UIViewController { let trendingLinkCell = UICollectionView.CellRegistration(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { (cell, indexPath, card) in cell.updateUI(card: card) } + let statusCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in + cell.delegate = self + // TODO: filter trends + cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow, precomputedContent: nil) + } let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in switch item { @@ -142,8 +158,8 @@ class SearchViewController: UIViewController { case let .link(card): return collectionView.dequeueConfiguredReusableCell(using: trendingLinkCell, for: indexPath, item: card) - default: - fatalError("todo") + case let .status(id, state): + return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, state)) } } dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in @@ -165,27 +181,41 @@ class SearchViewController: UIViewController { } var snapshot = NSDiffableDataSourceSnapshot() - + let hashtagsReq = Client.getTrendingHashtags(limit: 5) async let hashtags = try? mastodonController.run(hashtagsReq).0 - let linksReq = Client.getTrendingLinks(limit: 10) - async let links = try? mastodonController.run(linksReq).0 - + if let hashtags = await hashtags { snapshot.appendSections([.trendingHashtags]) snapshot.appendItems(hashtags.map { .tag($0) }, toSection: .trendingHashtags) } - - if let links = await links { - snapshot.appendSections([.trendingLinks]) - snapshot.appendItems(links.map { .link($0) }, toSection: .trendingLinks) + + if mastodonController.instanceFeatures.trendingStatusesAndLinks { + let linksReq = Client.getTrendingLinks(limit: 10) + async let links = try? mastodonController.run(linksReq).0 + let statusesReq = Client.getTrendingStatuses(limit: 10) + async let statuses = try? mastodonController.run(statusesReq).0 + + if let links = await links { + snapshot.appendSections([.trendingLinks]) + snapshot.appendItems(links.map { .link($0) }, toSection: .trendingLinks) + } + + if let statuses = await statuses { + await mastodonController.persistentContainer.addAll(statuses: statuses) + snapshot.appendSections([.trendingStatuses]) + snapshot.appendItems(statuses.map { .status($0.id, .unknown) }, toSection: .trendingStatuses) + } + } + + if !Task.isCancelled { + await dataSource.apply(snapshot) } - - await dataSource.apply(snapshot) } @objc private func preferencesChanged() { - Task { + loadTask?.cancel() + loadTask = Task { await applySnapshot() } } @@ -196,8 +226,8 @@ extension SearchViewController { enum Section { case trendingHashtags case trendingLinks - case trendingStatuses case profileSuggestions + case trendingStatuses var title: String { switch self { @@ -206,20 +236,20 @@ extension SearchViewController { case .trendingLinks: return "Trending Links" case .trendingStatuses: - return "Trending Statuses" + return "Trending Posts" case .profileSuggestions: return "Suggested Accounts" } } } enum Item: Equatable, Hashable { - case status(String) + case status(String, CollapseState) case tag(Hashtag) case link(Card) static func == (lhs: SearchViewController.Item, rhs: SearchViewController.Item) -> Bool { switch (lhs, rhs) { - case let (.status(a), .status(b)): + case let (.status(a, _), .status(b, _)): return a == b case let (.tag(a), .tag(b)): return a == b @@ -232,7 +262,7 @@ extension SearchViewController { func hash(into hasher: inout Hasher) { switch self { - case let .status(id): + case let .status(id, _): hasher.combine("status") hasher.combine(id) case let .tag(tag): @@ -260,8 +290,8 @@ extension SearchViewController: UICollectionViewDelegate { selected(url: url) } - default: - fatalError("todo") + case let .status(id, state): + selected(status: id, state: state.copy()) } } @@ -348,8 +378,16 @@ extension SearchViewController: UICollectionViewDragDelegate { } return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))] - default: - fatalError("todo") + case let .status(id, _): + guard let status = mastodonController.persistentContainer.status(for: id), + let url = status.url else { + return [] + } + let provider = NSItemProvider(object: url as NSURL) + let activity = UserActivityManager.showConversationActivity(mainStatusID: id, accountID: mastodonController.accountInfo!.id) + activity.displaysAuxiliaryScene = true + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] } } } @@ -363,3 +401,17 @@ extension SearchViewController: ToastableViewController { extension SearchViewController: MenuActionProvider { } + +extension SearchViewController: StatusCollectionViewCellDelegate { + func statusCellNeedsReconfigure(_ cell: StatusCollectionViewCell, animated: Bool, completion: (() -> Void)?) { + if let indexPath = collectionView.indexPath(for: cell) { + var snapshot = dataSource.snapshot() + snapshot.reconfigureItems([dataSource.itemIdentifier(for: indexPath)!]) + dataSource.apply(snapshot, animatingDifferences: animated, completion: completion) + } + } + + func statusCellShowFiltered(_ cell: StatusCollectionViewCell) { + // TODO: filtering + } +}