// // EditListAccountsViewController.swift // Tusker // // Created by Shadowfacts on 12/17/19. // Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import Combine class EditListAccountsViewController: EnhancedTableViewController { private var list: List let mastodonController: MastodonController var changedAccounts = false var dataSource: DataSource! var nextRange: RequestRange? var searchResultsController: SearchResultsViewController! var searchController: UISearchController! private var listRenamedCancellable: AnyCancellable? init(list: List, mastodonController: MastodonController) { self.list = list self.mastodonController = mastodonController super.init(style: .plain) listChanged() listRenamedCancellable = mastodonController.$lists .compactMap { $0.first { $0.id == list.id } } .removeDuplicates(by: { $0.title == $1.title }) .sink { [unowned self] in self.list = $0 self.listChanged() } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemeneted") } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "AccountTableViewCell", bundle: .main), forCellReuseIdentifier: "accountCell") tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 66 tableView.allowsSelection = false tableView.backgroundColor = .appGroupedBackground dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in guard case let .account(id) = item else { fatalError() } let cell = tableView.dequeueReusableCell(withIdentifier: "accountCell", for: indexPath) as! AccountTableViewCell cell.delegate = self cell.updateUI(accountID: id) cell.configurationUpdateHandler = { cell, state in var config = UIBackgroundConfiguration.listGroupedCell().updated(for: state) if state.isHighlighted || state.isSelected { config.backgroundColor = .appSelectedCellBackground } else { config.backgroundColor = .appGroupedCellBackground } cell.backgroundConfiguration = config } return cell }) dataSource.editListAccountsController = self searchResultsController = SearchResultsViewController(mastodonController: mastodonController, scope: .people) searchResultsController.following = true searchResultsController.delegate = self searchController = UISearchController(searchResultsController: searchResultsController) searchController.hidesNavigationBarDuringPresentation = false searchController.searchResultsUpdater = searchResultsController searchController.searchBar.autocapitalizationType = .none searchController.searchBar.placeholder = NSLocalizedString("Search accounts you follow", comment: "edit list search field placeholder") searchController.searchBar.delegate = searchResultsController definesPresentationContext = true navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false if #available(iOS 16.0, *) { navigationItem.preferredSearchBarPlacement = .stacked } navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Rename", comment: "rename list button title"), style: .plain, target: self, action: #selector(renameButtonPressed)) Task { await loadAccounts() } } private func listChanged() { title = String(format: NSLocalizedString("Edit %@", comment: "edit list screen title"), list.title) } func loadAccounts() async { do { let request = List.getAccounts(list.id) let (accounts, pagination) = try await mastodonController.run(request) self.nextRange = pagination?.older await withCheckedContinuation { continuation in mastodonController.persistentContainer.addAll(accounts: accounts) { continuation.resume() } } var snapshot = self.dataSource.snapshot() if snapshot.indexOfSection(.accounts) == nil { snapshot.appendSections([.accounts]) } else { snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .accounts)) } snapshot.appendItems(accounts.map { .account(id: $0.id) }) await dataSource.apply(snapshot) } catch { let config = ToastConfiguration(from: error, with: "Error Loading Accounts", in: self) { [unowned self] toast in toast.dismissToast(animated: true) await self.loadAccounts() } self.showToast(configuration: config, animated: true) } } private func addAccount(id: String) async { changedAccounts = true do { let req = List.add(list.id, accounts: [id]) _ = try await mastodonController.run(req) self.searchController.isActive = false await self.loadAccounts() } catch { let config = ToastConfiguration(from: error, with: "Error Adding Account", in: self) { [unowned self] toast in toast.dismissToast(animated: true) await self.addAccount(id: id) } self.showToast(configuration: config, animated: true) } } private func removeAccount(id: String) async { changedAccounts = true do { let request = List.remove(list.id, accounts: [id]) _ = try await mastodonController.run(request) await self.loadAccounts() } catch { let config = ToastConfiguration(from: error, with: "Error Removing Account", in: self) { [unowned self] toast in toast.dismissToast(animated: true) await self.removeAccount(id: id) } self.showToast(configuration: config, animated: true) } } // MARK: - Table view delegate override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { return .delete } // MARK: - Interaction @objc func renameButtonPressed() { RenameListService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) }).run() } } extension EditListAccountsViewController { enum Section: Hashable { case accounts } enum Item: Hashable { case account(id: String) } class DataSource: UITableViewDiffableDataSource { weak var editListAccountsController: EditListAccountsViewController? override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete, case let .account(id) = itemIdentifier(for: indexPath) else { return } Task { await self.editListAccountsController?.removeAccount(id: id) } } } } extension EditListAccountsViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } extension EditListAccountsViewController: ToastableViewController { } extension EditListAccountsViewController: MenuActionProvider { } extension EditListAccountsViewController: SearchResultsViewControllerDelegate { func selectedSearchResult(account accountID: String) { Task { await addAccount(id: accountID) } } }