From ce741d6e1fa0732d7ca6fe606070cc29c2ebc64c Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 5 Feb 2023 14:23:29 -0500 Subject: [PATCH] Extract trends to separate VC --- Tusker.xcodeproj/project.pbxproj | 12 +- Tusker/Scenes/AuxiliarySceneDelegate.swift | 2 +- .../Explore/InlineTrendsViewController.swift | 79 ++++++++++++ .../TrendsViewController.swift} | 119 +++++++----------- .../Main/MainSplitViewController.swift | 8 +- 5 files changed, 138 insertions(+), 82 deletions(-) create mode 100644 Tusker/Screens/Explore/InlineTrendsViewController.swift rename Tusker/Screens/{Search/SearchViewController.swift => Explore/TrendsViewController.swift} (87%) diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index bb3a433b..7eb0676d 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ D68ACE5D279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */; }; D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */; }; D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */; }; - D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525C24A3E8F00054355A /* SearchViewController.swift */; }; + D68E525D24A3E8F00054355A /* InlineTrendsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525C24A3E8F00054355A /* InlineTrendsViewController.swift */; }; D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */; }; D68E6F5F253C9B2D001A1B4C /* BaseEmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */; }; D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; }; @@ -298,6 +298,7 @@ D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */; }; D6C3F4F7298ED7F70009FCFF /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */; }; D6C3F4F9298EDBF20009FCFF /* ConversationTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F8298EDBF20009FCFF /* ConversationTree.swift */; }; + D6C3F4FB299035650009FCFF /* TrendsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4FA299035650009FCFF /* TrendsViewController.swift */; }; D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.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 */; }; @@ -626,7 +627,7 @@ D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerControlCollectionViewCell.swift; sourceTree = ""; }; D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuxiliarySceneDelegate.swift; sourceTree = ""; }; D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerRootViewController.swift; sourceTree = ""; }; - D68E525C24A3E8F00054355A /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + D68E525C24A3E8F00054355A /* InlineTrendsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTrendsViewController.swift; sourceTree = ""; }; D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSourceEmojiLabel.swift; sourceTree = ""; }; D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseEmojiLabel.swift; sourceTree = ""; }; D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = ""; }; @@ -708,6 +709,7 @@ D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPredicateStatusesViewController.swift; sourceTree = ""; }; D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = ""; }; D6C3F4F8298EDBF20009FCFF /* ConversationTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTree.swift; sourceTree = ""; }; + D6C3F4FA299035650009FCFF /* TrendsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendsViewController.swift; sourceTree = ""; }; D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = ""; }; 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 = ""; }; @@ -925,6 +927,7 @@ isa = PBXGroup; children = ( D6C82B4025C5BB7E0017F1E6 /* ExploreViewController.swift */, + D68E525C24A3E8F00054355A /* InlineTrendsViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, @@ -939,6 +942,7 @@ D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */, D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */, D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */, + D6C3F4FA299035650009FCFF /* TrendsViewController.swift */, ); path = Explore; sourceTree = ""; @@ -1410,7 +1414,6 @@ D6BC9DD8232D8BCA002CA326 /* Search */ = { isa = PBXGroup; children = ( - D68E525C24A3E8F00054355A /* SearchViewController.swift */, D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */, ); path = Search; @@ -1959,7 +1962,7 @@ D61F759229365C6C00C0B37F /* CollectionViewController.swift in Sources */, D6DD8FFD298495A8002AD3FD /* LogoutService.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, - D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */, + D68E525D24A3E8F00054355A /* InlineTrendsViewController.swift in Sources */, D61F75BB293C183100C0B37F /* HTMLConverter.swift in Sources */, D61F75A5293ABD6F00C0B37F /* EditFilterView.swift in Sources */, D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */, @@ -2178,6 +2181,7 @@ D6E77D0B286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift in Sources */, D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */, D65B4B58297203A700DABDFB /* ReportSelectRulesView.swift in Sources */, + D6C3F4FB299035650009FCFF /* TrendsViewController.swift in Sources */, D6B81F442560390300F6E31D /* MenuController.swift in Sources */, D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */, D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */, diff --git a/Tusker/Scenes/AuxiliarySceneDelegate.swift b/Tusker/Scenes/AuxiliarySceneDelegate.swift index c4b56e13..156f3b24 100644 --- a/Tusker/Scenes/AuxiliarySceneDelegate.swift +++ b/Tusker/Scenes/AuxiliarySceneDelegate.swift @@ -82,7 +82,7 @@ class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate { return NotificationsPageViewController(initialMode: mode, mastodonController: mastodonController) case .search: - return SearchViewController(mastodonController: mastodonController) + return InlineTrendsViewController(mastodonController: mastodonController) case .bookmarks: return BookmarksViewController(mastodonController: mastodonController) diff --git a/Tusker/Screens/Explore/InlineTrendsViewController.swift b/Tusker/Screens/Explore/InlineTrendsViewController.swift new file mode 100644 index 00000000..819b1454 --- /dev/null +++ b/Tusker/Screens/Explore/InlineTrendsViewController.swift @@ -0,0 +1,79 @@ +// +// InlineTrendsViewController.swift +// Tusker +// +// Created by Shadowfacts on 6/24/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit + +class InlineTrendsViewController: UIViewController { + + weak var mastodonController: MastodonController! + + var resultsController: SearchResultsViewController! + var searchController: UISearchController! + + var searchControllerStatusOnAppearance: Bool? = nil + + init(mastodonController: MastodonController) { + self.mastodonController = mastodonController + + super.init(nibName: nil, bundle: nil) + + title = NSLocalizedString("Explore", comment: "explore tab title") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + resultsController = SearchResultsViewController(mastodonController: mastodonController) + resultsController.exploreNavigationController = self.navigationController + searchController = UISearchController(searchResultsController: resultsController) + searchController.obscuresBackgroundDuringPresentation = true + if #available(iOS 16.0, *) { + searchController.scopeBarActivation = .onSearchActivation + } + searchController.searchBar.autocapitalizationType = .none + searchController.searchBar.delegate = resultsController + searchController.searchBar.scopeButtonTitles = SearchResultsViewController.Scope.allCases.map(\.title) + searchController.hidesNavigationBarDuringPresentation = false + definesPresentationContext = true + + navigationItem.searchController = searchController + navigationItem.hidesSearchBarWhenScrolling = false + if #available(iOS 16.0, *) { + navigationItem.preferredSearchBarPlacement = .stacked + } + + let trends = TrendsViewController(mastodonController: mastodonController) + trends.view.translatesAutoresizingMaskIntoConstraints = false + addChild(trends) + view.addSubview(trends.view) + NSLayoutConstraint.activate([ + trends.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + trends.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + trends.view.topAnchor.constraint(equalTo: view.topAnchor), + trends.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + trends.didMove(toParent: self) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // this is a workaround for the issue that setting isActive on a search controller that is not visible + // does not cause it to automatically become active once it becomes visible + // see FB7814561 + if let active = searchControllerStatusOnAppearance { + searchController.isActive = active + searchControllerStatusOnAppearance = nil + } + } + +} diff --git a/Tusker/Screens/Search/SearchViewController.swift b/Tusker/Screens/Explore/TrendsViewController.swift similarity index 87% rename from Tusker/Screens/Search/SearchViewController.swift rename to Tusker/Screens/Explore/TrendsViewController.swift index 6b25cafa..879862e8 100644 --- a/Tusker/Screens/Search/SearchViewController.swift +++ b/Tusker/Screens/Explore/TrendsViewController.swift @@ -1,42 +1,41 @@ // -// SearchViewController.swift +// TrendsViewController.swift // Tusker // -// Created by Shadowfacts on 6/24/20. -// Copyright © 2020 Shadowfacts. All rights reserved. +// Created by Shadowfacts on 2/5/23. +// Copyright © 2023 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import SafariServices -import WebURLFoundationExtras -class SearchViewController: UIViewController, CollectionViewController { - - weak var mastodonController: MastodonController! +class TrendsViewController: UIViewController, CollectionViewController { + + let mastodonController: MastodonController var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! - var resultsController: SearchResultsViewController! - var searchController: UISearchController! - - var searchControllerStatusOnAppearance: Bool? = nil - private var loadTask: Task? + private var isShowingTrends = false + private var shouldShowTrends: Bool { + mastodonController.instanceFeatures.trends && !Preferences.shared.hideDiscover + } + init(mastodonController: MastodonController) { self.mastodonController = mastodonController super.init(nibName: nil, bundle: nil) - title = NSLocalizedString("Explore", comment: "explore tab title") + title = "Trends" } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() @@ -92,53 +91,9 @@ class SearchViewController: UIViewController, CollectionViewController { dataSource = createDataSource() - resultsController = SearchResultsViewController(mastodonController: mastodonController) - resultsController.exploreNavigationController = self.navigationController - searchController = UISearchController(searchResultsController: resultsController) - searchController.obscuresBackgroundDuringPresentation = true - if #available(iOS 16.0, *) { - searchController.scopeBarActivation = .onSearchActivation - } - searchController.searchBar.autocapitalizationType = .none - searchController.searchBar.delegate = resultsController - searchController.searchBar.scopeButtonTitles = SearchResultsViewController.Scope.allCases.map(\.title) - searchController.hidesNavigationBarDuringPresentation = false - definesPresentationContext = true - - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - if #available(iOS 16.0, *) { - navigationItem.preferredSearchBarPlacement = .stacked - } - NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - clearSelectionOnAppear(animated: animated) - - loadTask?.cancel() - loadTask = Task(priority: .userInitiated) { - if (try? await mastodonController.getOwnInstance()) != nil { - await applySnapshot() - } - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - // this is a workaround for the issue that setting isActive on a search controller that is not visible - // does not cause it to automatically become active once it becomes visible - // see FB7814561 - if let active = searchControllerStatusOnAppearance { - searchController.isActive = active - searchControllerStatusOnAppearance = nil - } - } - private func createDataSource() -> UICollectionViewDiffableDataSource { let sectionHeaderCell = UICollectionView.SupplementaryRegistration(elementKind: UICollectionView.elementKindSectionHeader) { [unowned self] (headerView, collectionView, indexPath) in let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section] @@ -188,10 +143,27 @@ class SearchViewController: UIViewController, CollectionViewController { return dataSource } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + clearSelectionOnAppear(animated: animated) + + if loadTask == nil { + loadTask = Task(priority: .userInitiated) { + if (try? await mastodonController.getOwnInstance()) != nil { + await loadTrends() + } + } + } + } + @MainActor - private func applySnapshot() async { - guard mastodonController.instanceFeatures.trends, - !Preferences.shared.hideDiscover else { + private func loadTrends() async { + guard isShowingTrends != shouldShowTrends else { + return + } + isShowingTrends = shouldShowTrends + guard shouldShowTrends else { await dataSource.apply(NSDiffableDataSourceSnapshot()) return } @@ -244,9 +216,11 @@ class SearchViewController: UIViewController, CollectionViewController { } @objc private func preferencesChanged() { - loadTask?.cancel() - loadTask = Task { - await applySnapshot() + if isShowingTrends != shouldShowTrends { + loadTask?.cancel() + loadTask = Task { + await loadTrends() + } } } @@ -273,10 +247,9 @@ class SearchViewController: UIViewController, CollectionViewController { self.showToast(configuration: config, animated: true) } } - } -extension SearchViewController { +extension TrendsViewController { enum Section { case trendingHashtags case trendingLinks @@ -302,7 +275,7 @@ extension SearchViewController { case link(Card) case account(String, Suggestion.Source) - static func == (lhs: SearchViewController.Item, rhs: SearchViewController.Item) -> Bool { + static func == (lhs: Item, rhs: Item) -> Bool { switch (lhs, rhs) { case let (.status(a, _), .status(b, _)): return a == b @@ -336,7 +309,7 @@ extension SearchViewController { } } -extension SearchViewController: UICollectionViewDelegate { +extension TrendsViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let item = dataSource.itemIdentifier(for: indexPath) else { return @@ -443,7 +416,7 @@ extension SearchViewController: UICollectionViewDelegate { } } -extension SearchViewController: UICollectionViewDragDelegate { +extension TrendsViewController: UICollectionViewDragDelegate { func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { guard let item = dataSource.itemIdentifier(for: indexPath) else { return [] @@ -490,17 +463,17 @@ extension SearchViewController: UICollectionViewDragDelegate { } } -extension SearchViewController: TuskerNavigationDelegate { +extension TrendsViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } -extension SearchViewController: ToastableViewController { +extension TrendsViewController: ToastableViewController { } -extension SearchViewController: MenuActionProvider { +extension TrendsViewController: MenuActionProvider { } -extension SearchViewController: StatusCollectionViewCellDelegate { +extension TrendsViewController: StatusCollectionViewCellDelegate { func statusCellNeedsReconfigure(_ cell: StatusCollectionViewCell, animated: Bool, completion: (() -> Void)?) { if let indexPath = collectionView.indexPath(for: cell) { var snapshot = dataSource.snapshot() diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index c57e041f..693f193b 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -217,7 +217,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate { // Make sure viewDidLoad is called so that the searchController/resultsController have been initialized explore.loadViewIfNeeded() - let search = secondaryNavController.viewControllers.first as! SearchViewController + let search = secondaryNavController.viewControllers.first as! InlineTrendsViewController // Copy the search query from the search VC to the Explore VC's search controller. let query = search.searchController.searchBar.text ?? "" explore.searchController.searchBar.text = query @@ -286,7 +286,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate { if tabNavigationStack.count == 1 || ((tabNavigationStack.first as? ExploreViewController)?.searchController?.isActive ?? false) { exploreItem = .explore // reuse the existing VC, if there is one - let searchVC = getOrCreateNavigationStack(item: .explore).first! as! SearchViewController + let searchVC = getOrCreateNavigationStack(item: .explore).first! as! InlineTrendsViewController // load the view so that the search controller is accessible searchVC.loadViewIfNeeded() let explore = tabNavigationStack.first as! ExploreViewController @@ -379,7 +379,7 @@ fileprivate extension MainSidebarViewController.Item { case let .tab(tab): return tab.createViewController(mastodonController) case .explore: - return SearchViewController(mastodonController: mastodonController) + return InlineTrendsViewController(mastodonController: mastodonController) case .bookmarks: return BookmarksViewController(mastodonController: mastodonController) case .favorites: @@ -482,7 +482,7 @@ extension MainSplitViewController: TuskerRootViewController { select(item: .explore) } - guard let searchViewController = secondaryNavController.viewControllers.first as? SearchViewController else { + guard let searchViewController = secondaryNavController.viewControllers.first as? InlineTrendsViewController else { return }