// // EditListSearchFollowingViewController.swift // Tusker // // Created by Shadowfacts on 11/11/22. // Copyright © 2022 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class EditListSearchFollowingViewController: EnhancedTableViewController { private let mastodonController: MastodonController private let didSelectAccount: (String) -> Void private var dataSource: UITableViewDiffableDataSource! private var query: String? private var accountIDs: [String] = [] private var nextRange: RequestRange? init(mastodonController: MastodonController, didSelectAccount: @escaping (String) -> Void) { self.mastodonController = mastodonController self.didSelectAccount = didSelectAccount super.init(style: .grouped) } 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") dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in let cell = tableView.dequeueReusableCell(withIdentifier: "accountCell", for: indexPath) as! AccountTableViewCell cell.delegate = self cell.updateUI(accountID: itemIdentifier) return cell } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if dataSource.snapshot().numberOfItems == 0 { Task { await load() } } } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { print("will display: \(indexPath)") if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 { Task { await load() } } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let id = dataSource.itemIdentifier(for: indexPath) else { return } didSelectAccount(id) } private func load() async { do { let ownAccount = try await mastodonController.getOwnAccount() let req = Account.getFollowing(ownAccount.id, range: nextRange ?? .default) let (following, pagination) = try await mastodonController.run(req) await withCheckedContinuation { continuation in mastodonController.persistentContainer.addAll(accounts: following) { continuation.resume() } } accountIDs.append(contentsOf: following.lazy.map(\.id)) nextRange = pagination?.older updateDataSource(appending: following.map(\.id)) } catch { let config = ToastConfiguration(from: error, with: "Error Loading Following", in: self) { toast in toast.dismissToast(animated: true) await self.load() } self.showToast(configuration: config, animated: true) } } private func updateDataSourceForQueryChanged() { guard let query, !query.isEmpty else { let snapshot = NSDiffableDataSourceSnapshot() dataSource.apply(snapshot, animatingDifferences: true) return } let ids = filterAccounts(ids: accountIDs, with: query) var snapshot = dataSource.snapshot() if snapshot.indexOfSection(.accounts) == nil { snapshot.appendSections([.accounts]) } else { snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .accounts)) } snapshot.appendItems(ids) dataSource.apply(snapshot, animatingDifferences: true) // if there aren't any results for the current query, try to load more if ids.isEmpty { Task { await load() } } } private func updateDataSource(appending ids: [String]) { guard let query, !query.isEmpty else { let snapshot = NSDiffableDataSourceSnapshot() dataSource.apply(snapshot, animatingDifferences: true) return } let ids = filterAccounts(ids: ids, with: query) var snapshot = dataSource.snapshot() if snapshot.indexOfSection(.accounts) == nil { snapshot.appendSections([.accounts]) } let existing = snapshot.itemIdentifiers(inSection: .accounts) snapshot.appendItems(ids.filter { !existing.contains($0) }) dataSource.apply(snapshot, animatingDifferences: true) // if there aren't any results for the current query, try to load more if ids.isEmpty { Task { await load() } } } private func filterAccounts(ids: [String], with query: String) -> [String] { let req = AccountMO.fetchRequest() req.predicate = NSPredicate(format: "id in %@", ids) let accounts = try! mastodonController.persistentContainer.viewContext.fetch(req) return accounts .map { (account) -> (AccountMO, Bool) in let displayNameMatch = FuzzyMatcher.match(pattern: query, str: account.displayNameWithoutCustomEmoji) let usernameMatch = FuzzyMatcher.match(pattern: query, str: account.acct) return (account, displayNameMatch.matched || usernameMatch.matched) } .filter(\.1) .map(\.0.id) } func updateQuery(_ query: String) { self.query = query updateDataSourceForQueryChanged() } } extension EditListSearchFollowingViewController { enum Section { case accounts } } extension EditListSearchFollowingViewController: TuskerNavigationDelegate { var apiController: MastodonController! { mastodonController } } extension EditListSearchFollowingViewController: MenuActionProvider { }