// // TimelineTableViewController.swift // Tusker // // Created by Shadowfacts on 8/15/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import Pachyderm typealias TimelineEntry = (id: String, state: StatusState) class TimelineTableViewController: DiffableTimelineLikeTableViewController { let timeline: Timeline weak var mastodonController: MastodonController! private var newer: RequestRange? private var older: RequestRange? init(for timeline: Timeline, mastodonController: MastodonController) { self.timeline = timeline self.mastodonController = mastodonController super.init() dragEnabled = true title = timeline.title tabBarItem.image = timeline.tabBarImage if let id = mastodonController.accountInfo?.id { userActivity = UserActivityManager.showTimelineActivity(timeline: timeline, accountID: id) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { guard let persistentContainer = mastodonController?.persistentContainer else { return } // decrement reference counts of any statuses we still have // if the app is currently being quit, this will not affect the persisted data because // the persistent container would already have been saved in SceneDelegate.sceneDidEnterBackground(_:) for case let .status(id: id, state: _) in dataSource.snapshot().itemIdentifiers { persistentContainer.status(for: id)?.decrementReferenceCount() } } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") } // MARK: - DiffableTimelineLikeTableViewController override class func refreshCommandTitle() -> String { return NSLocalizedString("Refresh Statuses", comment: "refresh status command discoverability title") } override func cellProvider(_ tableView: UITableView, _ indexPath: IndexPath, _ item: Item) -> UITableViewCell? { guard case let .status(id: id, state: state) = item, let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { return nil } cell.delegate = self cell.updateUI(statusID: id, state: state) return cell } override func loadInitialItems(completion: @escaping (LoadResult) -> Void) { guard let mastodonController = mastodonController else { completion(.failure(.noClient)) return } let request = Client.getStatuses(timeline: timeline) mastodonController.run(request) { response in switch response { case let .failure(error): completion(.failure(.client(error))) case let .success(statuses, pagination): self.newer = pagination?.newer self.older = pagination?.older self.mastodonController.persistentContainer.addAll(statuses: statuses) { var snapshot = Snapshot() snapshot.appendSections([.statuses]) snapshot.appendItems(statuses.map { .status(id: $0.id, state: .unknown) }) completion(.success(snapshot)) } } } } override func loadOlderItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { guard let older = older else { completion(.failure(.noOlder)) return } let request = Client.getStatuses(timeline: timeline, range: older) mastodonController.run(request) { response in switch response { case let .failure(error): completion(.failure(.client(error))) case let .success(statuses, pagination): self.older = pagination?.older self.mastodonController.persistentContainer.addAll(statuses: statuses) { var snapshot = currentSnapshot snapshot.appendItems(statuses.map { .status(id: $0.id, state: .unknown) }, toSection: .statuses) completion(.success(snapshot)) } } } } override func loadNewerItems(currentSnapshot: Snapshot, completion: @escaping (LoadResult) -> Void) { guard let newer = newer else { completion(.failure(.noNewer)) return } let request = Client.getStatuses(timeline: timeline, range: newer) mastodonController.run(request) { response in switch response { case let .failure(error): completion(.failure(.client(error))) case let .success(statuses, pagination): // if there are no new statuses, pagination is nil // if we were to then overwrite self.newer, future refresh would fail if let newer = pagination?.newer { self.newer = newer } self.mastodonController.persistentContainer.addAll(statuses: statuses) { var snapshot = currentSnapshot let identifiers = statuses.map { Item.status(id: $0.id, state: .unknown) } if let first = snapshot.itemIdentifiers(inSection: .statuses).first { snapshot.insertItems(identifiers, beforeItem: first) } else { snapshot.appendItems(identifiers, toSection: .statuses) } completion(.success(snapshot)) } } } } override func willRemoveItems(_ items: [Item]) { for case let .status(id: id, state: _) in items { mastodonController.persistentContainer.status(for: id)?.decrementReferenceCount() } } } extension TimelineTableViewController { enum Section: Hashable, CaseIterable { case statuses case footer } enum Item: Hashable { case status(id: String, state: StatusState) } } extension TimelineTableViewController: TuskerNavigationDelegate { var apiController: MastodonController { mastodonController } } extension TimelineTableViewController: StatusTableViewCellDelegate { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { cellHeightChanged() } } extension TimelineTableViewController: UITableViewDataSourcePrefetching, StatusTablePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let ids: [String] = indexPaths.compactMap { if case let .status(id: id, state: _) = dataSource.itemIdentifier(for: $0) { return id } else { return nil } } prefetchStatuses(with: ids) } func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { let ids: [String] = indexPaths.compactMap { if case let .status(id: id, state: _) = dataSource.itemIdentifier(for: $0) { return id } else { return nil } } cancelPrefetchingStatuses(with: ids) } }