// // ProfileStatusesViewController.swift // Tusker // // Created by Shadowfacts on 7/3/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class ProfileStatusesViewController: TimelineLikeTableViewController { weak var mastodonController: MastodonController! private(set) var headerView: ProfileHeaderView! var accountID: String! let kind: Kind private var older: RequestRange? private var newer: RequestRange? init(accountID: String?, kind: Kind, mastodonController: MastodonController) { self.accountID = accountID self.kind = kind self.mastodonController = mastodonController super.init() dragEnabled = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") } func updateUI(account: AccountMO) { loadInitial() } override class func refreshCommandTitle() -> String { return NSLocalizedString("Refresh Statuses", comment: "refresh statuses command discoverability title") } override func headerSectionsCount() -> Int { return 1 } override func loadInitial() { guard accountID != nil else { return } if !loaded { loadPinnedStatuses() } super.loadInitial() } private func loadPinnedStatuses() { guard kind == .statuses else { return } getPinnedStatuses { (response) in guard case let .success(statuses, _) = response, !statuses.isEmpty else { // todo: error message return } self.mastodonController.persistentContainer.addAll(statuses: statuses) { let items = statuses.map { ($0.id, StatusState.unknown) } DispatchQueue.main.async { UIView.performWithoutAnimation { if self.sections.count < 1 { self.sections.append(items) self.tableView.insertSections(IndexSet(integer: 0), with: .none) } else { self.sections[0] = items self.tableView.reloadSections(IndexSet(integer: 0), with: .none) } } } } } } override func loadInitialItems(completion: @escaping ([TimelineEntry]) -> Void) { getStatuses { (response) in guard case let .success(statuses, pagination) = response, !statuses.isEmpty else { // todo: error message completion([]) return } self.older = pagination?.older self.newer = pagination?.newer self.mastodonController.persistentContainer.addAll(statuses: statuses) { completion(statuses.map { ($0.id, .unknown) }) } } } override func loadOlder(completion: @escaping ([TimelineEntry]) -> Void) { guard let older = older else { completion([]) return } getStatuses(for: older) { (response) in guard case let .success(statuses, pagination) = response else { // todo: error message completion([]) return } self.older = pagination?.older self.mastodonController.persistentContainer.addAll(statuses: statuses) { completion(statuses.map { ($0.id, .unknown) }) } } } override func loadNewer(completion: @escaping ([TimelineEntry]) -> Void) { guard let newer = newer else { completion([]) return } getStatuses(for: newer) { (response) in guard case let .success(statuses, pagination) = response else { // todo: error message completion([]) return } if let newer = pagination?.newer { self.newer = newer } self.mastodonController.persistentContainer.addAll(statuses: statuses) { completion(statuses.map { ($0.id, .unknown) }) } } } private func getStatuses(for range: RequestRange = .default, completion: @escaping Client.Callback<[Status]>) { let request: Request<[Status]> switch kind { case .statuses: request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: false, excludeReplies: true) case .withReplies: request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: false, excludeReplies: false) case .onlyMedia: request = Account.getStatuses(accountID, range: range, onlyMedia: true, pinned: false, excludeReplies: false) } mastodonController.run(request, completion: completion) } private func getPinnedStatuses(completion: @escaping Client.Callback<[Status]>) { let request = Account.getStatuses(accountID, range: .default, onlyMedia: false, pinned: true, excludeReplies: false) mastodonController.run(request, completion: completion) } override func refresh() { super.refresh() if kind == .statuses { getPinnedStatuses { (response) in guard case let .success(newPinnedStatues, _) = response else { // todo: error message return } self.mastodonController.persistentContainer.addAll(statuses: newPinnedStatues) { // if the user refreshes before the initial pinned statuses request completes, self.sections will be empty let oldPinnedStatuses = self.sections.isEmpty ? [] : self.sections[0] let pinnedStatues = newPinnedStatues.map { (status) -> TimelineEntry in let state: StatusState if let (_, oldState) = oldPinnedStatuses.first(where: { $0.id == status.id }) { state = oldState } else { state = .unknown } return (status.id, state) } DispatchQueue.main.async { UIView.performWithoutAnimation { if self.sections.count < 1 { self.sections.append(pinnedStatues) self.tableView.insertSections(IndexSet(integer: 0), with: .none) } else { self.sections[0] = pinnedStatues self.tableView.reloadSections(IndexSet(integer: 0), with: .none) } } } } } } } // MARK: - UITableViewDatasource override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! TimelineStatusTableViewCell cell.delegate = self cell.showPinned = indexPath.section == 0 let (id, state) = item(for: indexPath) cell.updateUI(statusID: id, state: state) return cell } } extension ProfileStatusesViewController { enum Kind { case statuses, withReplies, onlyMedia } } extension ProfileStatusesViewController: TuskerNavigationDelegate { var apiController: MastodonController { mastodonController } } extension ProfileStatusesViewController: StatusTableViewCellDelegate { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { cellHeightChanged() } } extension ProfileStatusesViewController: UITableViewDataSourcePrefetching, StatusTablePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let ids = indexPaths.map { item(for: $0).id } prefetchStatuses(with: ids) } func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { let ids: [String] = indexPaths.compactMap { guard $0.section < sections.count, $0.row < sections[$0.section].count else { return nil } return item(for: $0).id } cancelPrefetchingStatuses(with: ids) } }