// // 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: TimelineLikeTableViewController { 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 section in sections { for (id, _) in section { persistentContainer.status(for: id)?.decrementReferenceCount() } } } override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") } override class func refreshCommandTitle() -> String { return NSLocalizedString("Refresh Statuses", comment: "refresh status command discoverability title") } override func willRemoveRows(at indexPaths: [IndexPath]) { for indexPath in indexPaths { let id = item(for: indexPath).id mastodonController.persistentContainer.status(for: id)?.decrementReferenceCount() } } override func loadInitialItems(completion: @escaping ([TimelineEntry]) -> Void) { let request = Client.getStatuses(timeline: timeline) mastodonController?.run(request) { (response) in guard case let .success(statuses, pagination) = response else { fatalError() } self.newer = pagination?.newer self.older = pagination?.older 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 } let request = Client.getStatuses(timeline: timeline, range: older) mastodonController.run(request) { (response) in guard case let .success(statuses, pagination) = response else { fatalError() } 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 } let request = Client.getStatuses(timeline: timeline, range: newer) mastodonController.run(request) { (response) in guard case let .success(statuses, pagination) = response else { fatalError() } // if there are no new statuses, pagination is nil // if we were to then overwrite self.newer, future refreshes would fail if let newer = pagination?.newer { self.newer = newer } self.mastodonController?.persistentContainer.addAll(statuses: statuses) { completion(statuses.map { ($0.id, .unknown) }) } } } // MARK: - UITableViewDataSource override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } let (id, state) = item(for: indexPath) cell.delegate = self cell.updateUI(statusID: id, state: state) return cell } } extension TimelineTableViewController: TuskerNavigationDelegate { var apiController: MastodonController { mastodonController } } extension TimelineTableViewController: StatusTableViewCellDelegate { func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) { cellHeightChanged() } } extension TimelineTableViewController: UITableViewDataSourcePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { for indexPath in indexPaths { guard let status = mastodonController.persistentContainer.status(for: item(for: indexPath).id) else { continue } _ = ImageCache.avatars.get(status.account.avatar, completion: nil) for attachment in status.attachments where attachment.kind == .image { _ = ImageCache.attachments.get(attachment.url, completion: nil) } } } func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { for indexPath in indexPaths { // todo: this means when removing cells, we can't cancel prefetching // is this an issue? guard indexPath.section < sections.count, indexPath.row < sections[indexPath.section].count, let status = mastodonController.persistentContainer.status(for: item(for: indexPath).id) else { continue } ImageCache.avatars.cancelWithoutCallback(status.account.avatar) for attachment in status.attachments where attachment.kind == .image { ImageCache.attachments.cancelWithoutCallback(attachment.url) } } } }