// // SearchTableViewController.swift // Tusker // // Created by Shadowfacts on 9/14/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import Combine import Pachyderm fileprivate let accountCell = "accountCell" fileprivate let statusCell = "statusCell" class SearchTableViewController: EnhancedTableViewController { var dataSource: UITableViewDiffableDataSource! let searchController = UISearchController(searchResultsController: nil) var activityIndicator: UIActivityIndicatorView! let searchSubject = PassthroughSubject() var currentQuery: String? init() { super.init(style: .grouped) title = NSLocalizedString("Search", comment: "search tab title") tabBarItem.image = UIImage(systemName: "magnifyingglass") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "AccountTableViewCell", bundle: .main), forCellReuseIdentifier: accountCell) tableView.register(UINib(nibName: "StatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in switch item { case let .account(id): let cell = tableView.dequeueReusableCell(withIdentifier: accountCell, for: indexPath) as! AccountTableViewCell cell.updateUI(accountID: id) cell.delegate = self return cell case let .status(id): let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! StatusTableViewCell cell.updateUI(statusID: id) cell.delegate = self return cell } }) searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.placeholder = NSLocalizedString("Search or Enter URL", comment: "search field placeholder") searchController.searchBar.delegate = self navigationItem.searchController = searchController definesPresentationContext = true activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.translatesAutoresizingMaskIntoConstraints = false activityIndicator.isHidden = true view.addSubview(activityIndicator) NSLayoutConstraint.activate([ activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), activityIndicator.topAnchor.constraint(equalTo: view.topAnchor, constant: 8) ]) _ = searchSubject .debounce(for: .milliseconds(500), scheduler: RunLoop.main) .map { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { $0 != self.currentQuery } .sink(receiveValue: performSearch(query:)) } func performSearch(query: String?) { guard let query = query, !query.isEmpty else { self.dataSource.apply(NSDiffableDataSourceSnapshot()) return } self.currentQuery = query if self.dataSource.snapshot().numberOfItems == 0 { activityIndicator.isHidden = false activityIndicator.startAnimating() } let request = MastodonController.client.search(query: query, resolve: true, limit: 10) MastodonController.client.run(request) { (response) in guard case let .success(results, _) = response else { fatalError() } DispatchQueue.main.async { self.activityIndicator.isHidden = true self.activityIndicator.stopAnimating() } guard self.currentQuery == query else { return } var snapshot = NSDiffableDataSourceSnapshot() if !results.accounts.isEmpty { snapshot.appendSections([.accounts]) snapshot.appendItems(results.accounts.map { Item.account($0.id) }, toSection: .accounts) MastodonCache.addAll(accounts: results.accounts) } if !results.statuses.isEmpty { snapshot.appendSections([.statuses]) snapshot.appendItems(results.statuses.map { Item.status($0.id) }, toSection: .statuses) MastodonCache.addAll(statuses: results.statuses) MastodonCache.addAll(accounts: results.statuses.map { $0.account }) } self.dataSource.apply(snapshot) } } } extension SearchTableViewController { enum Section: CaseIterable { case accounts case statuses var displayName: String { switch self { case .accounts: return NSLocalizedString("People", comment: "accounts search results section") case .statuses: return NSLocalizedString("Posts", comment: "statuses search results section") } } } enum Item: Hashable { case account(String) case status(String) } class DataSource: UITableViewDiffableDataSource { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return Section.allCases[section].displayName } } } extension SearchTableViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { searchSubject.send(searchController.searchBar.text) } } extension SearchTableViewController: UISearchBarDelegate { func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { // perform a search immedaitely when the search button is pressed performSearch(query: searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines)) } } extension SearchTableViewController: StatusTableViewCellDelegate { func statusCollapsedStateChanged() { tableView.beginUpdates() tableView.endUpdates() } }