// // ExploreViewController.swift // Tusker // // Created by Shadowfacts on 12/14/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import Combine import Pachyderm class ExploreViewController: EnhancedTableViewController { var dataSource: DataSource! var resultsController: SearchResultsViewController! var searchController: UISearchController! init() { super.init(style: .insetGrouped) title = NSLocalizedString("Explore", comment: "explore 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: "BasicTableViewCell", bundle: .main), forCellReuseIdentifier: "basicCell") dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath) 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 = nil 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 } return cell }) dataSource.exploreController = self var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.bookmarks, .lists]) snapshot.appendItems([.bookmarks], toSection: .bookmarks) snapshot.appendItems([.addList], toSection: .lists) // the initial, static items should not be displayed with an animation UIView.performWithoutAnimation { dataSource.apply(snapshot) } resultsController = SearchResultsViewController() resultsController.exploreNavigationController = self.navigationController! searchController = UISearchController(searchResultsController: resultsController) searchController.searchResultsUpdater = resultsController searchController.searchBar.autocapitalizationType = .none searchController.searchBar.delegate = resultsController definesPresentationContext = true navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false reloadLists() } func reloadLists() { let request = MastodonController.client.getLists() MastodonController.client.run(request) { (response) in guard case let .success(lists, _) = response else { fatalError() } var snapshot = self.dataSource.snapshot() snapshot.deleteSections([.lists]) snapshot.appendSections([.lists]) snapshot.appendItems(lists.map { .list($0) } + [.addList], toSection: .lists) DispatchQueue.main.async { self.dataSource.apply(snapshot) } } } // MARK: - Table view delegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch dataSource.itemIdentifier(for: indexPath) { case nil: return case .bookmarks: show(BookmarksTableViewController(), sender: nil) case let .list(list): show(ListTimelineViewController(for: list), sender: nil) case .addList: tableView.selectRow(at: nil, animated: true, scrollPosition: .none) 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.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "new list alert cancel button"), style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: NSLocalizedString("Create List", comment: "new list create button"), style: .default, handler: { (_) in guard let title = alert.textFields?.first?.text else { fatalError() } let request = MastodonController.client.createList(title: title) MastodonController.client.run(request) { (response) in guard case let .success(list, _) = response else { fatalError() } self.reloadLists() DispatchQueue.main.async { self.show(ListTimelineViewController(for: list), sender: nil) } } })) present(alert, animated: true) } } override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { return .delete } } extension ExploreViewController { enum Section: CaseIterable { case bookmarks case lists } enum Item: Hashable { case bookmarks case list(List) case addList static func == (lhs: ExploreViewController.Item, rhs: ExploreViewController.Item) -> Bool { switch (lhs, rhs) { case (.bookmarks, .bookmarks): return true case let (.list(a), .list(b)): return a.id == b.id case (.addList, .addList): return true default: return false } } func hash(into hasher: inout Hasher) { switch self { case .bookmarks: hasher.combine("bookmarks") case let .list(list): hasher.combine("list") hasher.combine(list.id) case .addList: hasher.combine("addList") } } } class DataSource: UITableViewDiffableDataSource { 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") default: return nil } } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { if case .list(_) = itemIdentifier(for: indexPath) { return true } return false } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete, case let .list(list) = itemIdentifier(for: indexPath) else { return } let title = String(format: NSLocalizedString("Are you sure want to delete the '%@' list?", comment: "delete list alert title"), list.title) 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("Delete List", comment: "delete list alert confirm button"), style: .destructive, handler: { (_) in let request = List.delete(list) MastodonController.client.run(request) { (response) in guard case .success(_, _) = response else { fatalError() } var snapshot = self.snapshot() snapshot.deleteItems([.list(list)]) DispatchQueue.main.async { self.apply(snapshot) } } })) self.exploreController?.present(alert, animated: true) } } }