2018-08-28 18:29:06 +00:00
|
|
|
//
|
|
|
|
// ConversationTableViewController.swift
|
|
|
|
// Tusker
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 8/28/18.
|
|
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
2019-01-19 19:31:31 +00:00
|
|
|
import SafariServices
|
2018-09-11 14:52:21 +00:00
|
|
|
import Pachyderm
|
2018-08-28 18:29:06 +00:00
|
|
|
|
2018-11-10 16:48:09 +00:00
|
|
|
class ConversationTableViewController: EnhancedTableViewController {
|
2018-08-28 18:29:06 +00:00
|
|
|
|
2019-11-19 03:23:15 +00:00
|
|
|
static let showPostsImage = UIImage(systemName: "eye.fill")!
|
|
|
|
static let hidePostsImage = UIImage(systemName: "eye.slash.fill")!
|
|
|
|
|
2020-01-05 20:25:07 +00:00
|
|
|
let mastodonController: MastodonController
|
|
|
|
|
2019-11-28 23:36:58 +00:00
|
|
|
let mainStatusID: String
|
|
|
|
let mainStatusState: StatusState
|
2020-03-02 01:06:27 +00:00
|
|
|
var statuses: [(id: String, state: StatusState)] = []
|
2018-08-28 18:29:06 +00:00
|
|
|
|
2019-11-19 03:23:15 +00:00
|
|
|
var showStatusesAutomatically = false
|
|
|
|
var visibilityBarButtonItem: UIBarButtonItem!
|
|
|
|
|
2020-01-05 20:25:07 +00:00
|
|
|
init(for mainStatusID: String, state: StatusState = .unknown, mastodonController: MastodonController) {
|
2018-10-20 16:03:18 +00:00
|
|
|
self.mainStatusID = mainStatusID
|
2019-11-28 23:36:58 +00:00
|
|
|
self.mainStatusState = state
|
2020-01-05 20:25:07 +00:00
|
|
|
self.mastodonController = mastodonController
|
2018-10-21 02:07:04 +00:00
|
|
|
|
|
|
|
super.init(style: .plain)
|
2018-10-20 16:03:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2018-08-28 18:29:06 +00:00
|
|
|
override func viewDidLoad() {
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
|
|
|
tableView.delegate = self
|
|
|
|
tableView.dataSource = self
|
|
|
|
|
2019-11-19 17:08:11 +00:00
|
|
|
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
2018-08-28 18:29:06 +00:00
|
|
|
tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell")
|
|
|
|
|
2019-02-08 18:37:38 +00:00
|
|
|
tableView.prefetchDataSource = self
|
|
|
|
|
2019-11-19 03:23:15 +00:00
|
|
|
visibilityBarButtonItem = UIBarButtonItem(image: ConversationTableViewController.showPostsImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
|
|
|
|
navigationItem.rightBarButtonItem = visibilityBarButtonItem
|
|
|
|
|
2019-11-28 23:36:58 +00:00
|
|
|
statuses = [(mainStatusID, mainStatusState)]
|
|
|
|
|
2020-01-06 00:54:28 +00:00
|
|
|
guard let mainStatus = mastodonController.cache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") }
|
2018-08-28 18:29:06 +00:00
|
|
|
|
2018-09-17 23:22:37 +00:00
|
|
|
let request = Status.getContext(mainStatus)
|
2020-01-05 20:25:07 +00:00
|
|
|
mastodonController.run(request) { response in
|
2018-09-11 14:52:21 +00:00
|
|
|
guard case let .success(context, _) = response else { fatalError() }
|
2018-09-18 01:57:46 +00:00
|
|
|
let parents = self.getDirectParents(of: mainStatus, from: context.ancestors)
|
2020-01-06 00:54:28 +00:00
|
|
|
self.mastodonController.cache.addAll(statuses: parents)
|
|
|
|
self.mastodonController.cache.addAll(statuses: context.descendants)
|
2019-11-28 23:36:58 +00:00
|
|
|
self.statuses = parents.map { ($0.id, .unknown) } + self.statuses + context.descendants.map { ($0.id, .unknown) }
|
|
|
|
let indexPath = IndexPath(row: parents.count, section: 0)
|
2018-08-28 18:29:06 +00:00
|
|
|
DispatchQueue.main.async {
|
2020-03-02 01:06:27 +00:00
|
|
|
let ancestorsIndexPaths = (0..<parents.count).map { IndexPath(row: $0, section: 0) }
|
|
|
|
let descendantsIndexPaths = ((parents.count + 1)..<(self.statuses.count)).map { IndexPath(row: $0, section: 0) }
|
|
|
|
// despite its name, UITableView.RowAnimation.none actually uses the automatic animation, so we force-disable it
|
|
|
|
UIView.performWithoutAnimation {
|
|
|
|
self.tableView.insertRows(at: ancestorsIndexPaths + descendantsIndexPaths, with: .none)
|
|
|
|
}
|
2018-08-28 18:29:06 +00:00
|
|
|
self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-03 00:05:47 +00:00
|
|
|
|
2018-08-28 18:29:06 +00:00
|
|
|
func getDirectParents(of status: Status, from statuses: [Status]) -> [Status] {
|
|
|
|
var statuses = statuses
|
|
|
|
var parents: [Status] = []
|
|
|
|
var currentStatus: Status? = status
|
|
|
|
while currentStatus != nil {
|
|
|
|
guard let index = statuses.firstIndex(where: { $0.id == currentStatus!.inReplyToID }) else { break }
|
|
|
|
let parent = statuses.remove(at: index)
|
|
|
|
parents.insert(parent, at: 0)
|
|
|
|
currentStatus = parent
|
|
|
|
}
|
|
|
|
return parents
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Table view data source
|
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
2018-08-28 18:29:06 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
2019-11-28 23:36:58 +00:00
|
|
|
return statuses.count
|
2018-08-28 18:29:06 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
2019-11-28 23:36:58 +00:00
|
|
|
let (id, state) = statuses[indexPath.row]
|
2018-08-28 18:29:06 +00:00
|
|
|
|
2019-11-28 23:36:58 +00:00
|
|
|
if id == mainStatusID {
|
2018-08-28 18:29:06 +00:00
|
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() }
|
|
|
|
cell.selectionStyle = .none
|
2019-11-19 03:23:15 +00:00
|
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
2018-08-28 18:29:06 +00:00
|
|
|
cell.delegate = self
|
2020-01-06 00:54:28 +00:00
|
|
|
cell.updateUI(statusID: id, state: state)
|
2018-08-28 18:29:06 +00:00
|
|
|
return cell
|
|
|
|
} else {
|
2019-11-19 17:08:11 +00:00
|
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
2019-11-19 03:23:15 +00:00
|
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
2018-08-28 18:29:06 +00:00
|
|
|
cell.delegate = self
|
2020-01-06 00:54:28 +00:00
|
|
|
cell.updateUI(statusID: id, state: state)
|
2018-08-28 18:29:06 +00:00
|
|
|
return cell
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-14 16:59:31 +00:00
|
|
|
// MARK: - Table view delegate
|
2018-09-15 17:01:13 +00:00
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
2019-06-14 02:31:36 +00:00
|
|
|
return true
|
2018-09-15 17:01:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
2019-06-14 02:31:36 +00:00
|
|
|
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.leadingSwipeActionsConfiguration()
|
2018-09-15 17:01:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 01:10:25 +00:00
|
|
|
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
2019-06-14 02:31:36 +00:00
|
|
|
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
|
2018-09-15 17:01:13 +00:00
|
|
|
}
|
2019-11-19 03:23:15 +00:00
|
|
|
|
|
|
|
@objc func toggleVisibilityButtonPressed() {
|
|
|
|
showStatusesAutomatically = !showStatusesAutomatically
|
|
|
|
|
2019-11-28 23:58:47 +00:00
|
|
|
for (_, state) in statuses where state.collapsible == true {
|
|
|
|
state.collapsed = !showStatusesAutomatically
|
|
|
|
}
|
|
|
|
|
2019-11-19 03:23:15 +00:00
|
|
|
for cell in tableView.visibleCells {
|
2019-11-19 17:08:11 +00:00
|
|
|
guard let cell = cell as? BaseStatusTableViewCell,
|
2019-11-19 03:23:15 +00:00
|
|
|
cell.collapsible else { continue }
|
|
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
|
|
|
cell.setCollapsed(!showStatusesAutomatically, animated: false)
|
|
|
|
}
|
|
|
|
|
2019-11-28 23:36:58 +00:00
|
|
|
// recalculate cell heights
|
|
|
|
tableView.beginUpdates()
|
|
|
|
tableView.endUpdates()
|
|
|
|
|
2019-11-19 03:23:15 +00:00
|
|
|
if showStatusesAutomatically {
|
|
|
|
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
|
|
|
|
} else {
|
|
|
|
visibilityBarButtonItem.image = ConversationTableViewController.showPostsImage
|
|
|
|
}
|
|
|
|
}
|
2018-08-28 18:29:06 +00:00
|
|
|
|
|
|
|
}
|
2018-09-03 22:46:20 +00:00
|
|
|
|
2019-09-09 22:40:23 +00:00
|
|
|
extension ConversationTableViewController: StatusTableViewCellDelegate {
|
2020-01-05 20:25:07 +00:00
|
|
|
var apiController: MastodonController { mastodonController }
|
2019-11-28 23:36:58 +00:00
|
|
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
2019-09-09 22:40:23 +00:00
|
|
|
// causes the table view to recalculate the cell heights
|
|
|
|
tableView.beginUpdates()
|
|
|
|
tableView.endUpdates()
|
|
|
|
}
|
|
|
|
}
|
2019-02-08 18:37:38 +00:00
|
|
|
|
|
|
|
extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
|
|
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
|
|
|
for indexPath in indexPaths {
|
2020-01-06 00:54:28 +00:00
|
|
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
2020-01-25 15:06:27 +00:00
|
|
|
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
2019-02-08 18:37:38 +00:00
|
|
|
for attachment in status.attachments {
|
2020-01-25 15:06:27 +00:00
|
|
|
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
2019-02-08 18:37:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
|
|
|
for indexPath in indexPaths {
|
2020-01-06 00:54:28 +00:00
|
|
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
2020-01-25 15:06:27 +00:00
|
|
|
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
2019-02-08 18:37:38 +00:00
|
|
|
for attachment in status.attachments {
|
2020-01-25 15:06:27 +00:00
|
|
|
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
2019-02-08 18:37:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|