// // InstanceSelectorTableViewController.swift // Tusker // // Created by Shadowfacts on 9/15/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import Combine import Pachyderm protocol InstanceSelectorTableViewControllerDelegate { func didSelectInstance(url: URL) } fileprivate let instanceCell = "instanceCell" class InstanceSelectorTableViewController: UITableViewController { var delegate: InstanceSelectorTableViewControllerDelegate? var dataSource: DataSource! var searchController: UISearchController! var recommendedInstances: [InstanceSelector.Instance] = [] let urlCheckerSubject = PassthroughSubject() var currentQuery: String? init() { super.init(style: .grouped) title = NSLocalizedString("Choose Your Instance", comment: "onboarding screen title") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell) tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 120 dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in switch item { case let .selected(instance): let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell cell.updateUI(instance: instance) return cell case let .recommended(instance): let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell cell.updateUI(instance: instance) return cell } }) searchController = UISearchController(searchResultsController: nil) searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.searchTextField.autocapitalizationType = .none navigationItem.searchController = searchController definesPresentationContext = true _ = urlCheckerSubject .debounce(for: .seconds(1), scheduler: RunLoop.main) .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) } .sink(receiveValue: updateSpecificInstance) loadRecommendedInstances() } private func updateSpecificInstance(domain: String) { var components = URLComponents(string: domain)! if components.scheme != "https" && components.scheme != "http" { components.scheme = "https" } components.path = "/" let client = Client(baseURL: components.url!) let request = client.getInstance() client.run(request) { (response) in var snapshot = self.dataSource.snapshot() snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .selected)) if case let .success(instance, _) = response { if !snapshot.sectionIdentifiers.contains(.selected) { snapshot.appendSections([.selected]) } snapshot.appendItems([.selected(instance)], toSection: .selected) DispatchQueue.main.async { self.dataSource.apply(snapshot) } } } } private func loadRecommendedInstances() { InstanceSelector.getInstances(category: nil) { (response) in guard case let .success(instances, _) = response else { fatalError() } self.recommendedInstances = instances self.filterRecommendedResults() } } func filterRecommendedResults() { let filteredInstances: [InstanceSelector.Instance] if let currentQuery = currentQuery, !currentQuery.isEmpty { filteredInstances = recommendedInstances.filter { $0.domain.contains(currentQuery) || $0.description.lowercased().contains(currentQuery) } } else { filteredInstances = recommendedInstances } var snapshot = self.dataSource.snapshot() snapshot.deleteSections([.recommendedInstances]) snapshot.appendSections([.recommendedInstances]) snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances) DispatchQueue.main.async { self.dataSource.apply(snapshot) } } // MARK: - Table view delegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let delegate = delegate, let item = dataSource.itemIdentifier(for: indexPath) else { return } switch item { case let .selected(instance): delegate.didSelectInstance(url: URL(string: instance.uri)!) case let .recommended(instance): var components = URLComponents() components.scheme = "https" components.host = instance.domain components.path = "/" delegate.didSelectInstance(url: components.url!) } } } extension InstanceSelectorTableViewController { enum Section { case selected case recommendedInstances } enum Item: Equatable, Hashable { case selected(Instance) case recommended(InstanceSelector.Instance) static func ==(lhs: Item, rhs: Item) -> Bool { if case let .selected(instance) = lhs, case let .selected(other) = rhs { return instance.uri == other.uri } else if case let .recommended(instance) = lhs, case let .recommended(other) = rhs { return instance.domain == other.domain } return false } func hash(into hasher: inout Hasher) { switch self { case let .selected(instance): hasher.combine(Section.selected) hasher.combine(instance.uri) case let .recommended(instance): hasher.combine(Section.recommendedInstances) hasher.combine(instance.domain) } } } class DataSource: UITableViewDiffableDataSource { } } extension InstanceSelectorTableViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { currentQuery = searchController.searchBar.text?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() filterRecommendedResults() urlCheckerSubject.send(currentQuery) } }