208 lines
8.4 KiB
Swift
208 lines
8.4 KiB
Swift
//
|
|
// ConversationTableViewController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 8/28/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import SafariServices
|
|
import Pachyderm
|
|
|
|
class ConversationTableViewController: EnhancedTableViewController {
|
|
|
|
static let showPostsImage = UIImage(systemName: "eye.fill")!
|
|
static let hidePostsImage = UIImage(systemName: "eye.slash.fill")!
|
|
|
|
weak var mastodonController: MastodonController!
|
|
|
|
let mainStatusID: String
|
|
let mainStatusState: StatusState
|
|
var statuses: [(id: String, state: StatusState)] = [] {
|
|
didSet {
|
|
DispatchQueue.main.async {
|
|
self.tableView.reloadData()
|
|
}
|
|
}
|
|
}
|
|
|
|
var showStatusesAutomatically = false
|
|
var visibilityBarButtonItem: UIBarButtonItem!
|
|
|
|
init(for mainStatusID: String, state: StatusState = .unknown, mastodonController: MastodonController) {
|
|
self.mainStatusID = mainStatusID
|
|
self.mainStatusState = state
|
|
self.mastodonController = mastodonController
|
|
|
|
super.init(style: .plain)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
guard let persistentContainer = mastodonController?.persistentContainer else { return }
|
|
for (id, _) in statuses {
|
|
persistentContainer.status(for: id)?.decrementReferenceCount()
|
|
}
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
tableView.delegate = self
|
|
tableView.dataSource = self
|
|
|
|
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
|
tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell")
|
|
|
|
tableView.prefetchDataSource = self
|
|
|
|
visibilityBarButtonItem = UIBarButtonItem(image: ConversationTableViewController.showPostsImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
|
|
navigationItem.rightBarButtonItem = visibilityBarButtonItem
|
|
|
|
statuses = [(mainStatusID, mainStatusState)]
|
|
|
|
guard let mainStatus = self.mastodonController.persistentContainer.status(for: self.mainStatusID) else {
|
|
fatalError("Missing cached status \(self.mainStatusID)")
|
|
}
|
|
let mainStatusInReplyToID = mainStatus.inReplyToID
|
|
mainStatus.incrementReferenceCount()
|
|
|
|
let request = Status.getContext(mainStatusID)
|
|
mastodonController.run(request) { response in
|
|
guard case let .success(context, _) = response else { fatalError() }
|
|
|
|
let parents = self.getDirectParents(inReplyTo: mainStatusInReplyToID, from: context.ancestors)
|
|
let parentStatuses = context.ancestors.filter { parents.contains($0.id) }
|
|
self.mastodonController.persistentContainer.addAll(statuses: parentStatuses) {
|
|
self.mastodonController.persistentContainer.addAll(statuses: context.descendants) {
|
|
self.statuses = parents.map { ($0, .unknown) } + self.statuses + context.descendants.map { ($0.id, .unknown) }
|
|
let indexPath = IndexPath(row: parents.count, section: 0)
|
|
DispatchQueue.main.async {
|
|
self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getDirectParents(inReplyTo inReplyToID: String?, from statuses: [Status]) -> [String] {
|
|
var statuses = statuses
|
|
var parents = [String]()
|
|
|
|
var parentID: String? = inReplyToID
|
|
|
|
while parentID != nil, let parentIndex = statuses.firstIndex(where: { $0.id == parentID }) {
|
|
let parentStatus = statuses.remove(at: parentIndex)
|
|
parents.insert(parentStatus.id, at: 0)
|
|
parentID = parentStatus.inReplyToID
|
|
}
|
|
|
|
return parents
|
|
}
|
|
|
|
// MARK: - Table view data source
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
return 1
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
return statuses.count
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let (id, state) = statuses[indexPath.row]
|
|
|
|
if id == mainStatusID {
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() }
|
|
cell.selectionStyle = .none
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
|
cell.delegate = self
|
|
cell.updateUI(statusID: id, state: state)
|
|
return cell
|
|
} else {
|
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
|
cell.showReplyIndicator = false
|
|
cell.delegate = self
|
|
cell.updateUI(statusID: id, state: state)
|
|
return cell
|
|
}
|
|
}
|
|
|
|
// MARK: - Table view delegate
|
|
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
return true
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
|
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.leadingSwipeActionsConfiguration()
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
|
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
|
|
}
|
|
|
|
@objc func toggleVisibilityButtonPressed() {
|
|
showStatusesAutomatically = !showStatusesAutomatically
|
|
|
|
for (_, state) in statuses where state.collapsible == true {
|
|
state.collapsed = !showStatusesAutomatically
|
|
}
|
|
|
|
for cell in tableView.visibleCells {
|
|
guard let cell = cell as? BaseStatusTableViewCell,
|
|
cell.collapsible else { continue }
|
|
cell.showStatusAutomatically = showStatusesAutomatically
|
|
cell.setCollapsed(!showStatusesAutomatically, animated: false)
|
|
}
|
|
|
|
// recalculate cell heights
|
|
tableView.beginUpdates()
|
|
tableView.endUpdates()
|
|
|
|
if showStatusesAutomatically {
|
|
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
|
|
} else {
|
|
visibilityBarButtonItem.image = ConversationTableViewController.showPostsImage
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension ConversationTableViewController: StatusTableViewCellDelegate {
|
|
var apiController: MastodonController { mastodonController }
|
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
|
// causes the table view to recalculate the cell heights
|
|
tableView.beginUpdates()
|
|
tableView.endUpdates()
|
|
}
|
|
}
|
|
|
|
extension ConversationTableViewController: UITableViewDataSourcePrefetching {
|
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
|
for indexPath in indexPaths {
|
|
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].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 {
|
|
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].id) else { continue }
|
|
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
|
for attachment in status.attachments where attachment.kind == .image {
|
|
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
|
}
|
|
}
|
|
}
|
|
}
|