|
|
@ -11,15 +11,8 @@ import Kingfisher |
|
|
|
import UIKit |
|
|
|
import SafariServices |
|
|
|
|
|
|
|
enum StatusType { |
|
|
|
case ancestor, status, descendant |
|
|
|
} |
|
|
|
|
|
|
|
class StatusTableViewController: AbstractStatusTableViewController, UIGestureRecognizerDelegate { |
|
|
|
public var status: StatusMO? = nil |
|
|
|
|
|
|
|
var ancestors: [StatusMO] = [] |
|
|
|
var descendants: [StatusMO] = [] |
|
|
|
var feedbackGenerator: UINotificationFeedbackGenerator? = nil |
|
|
|
|
|
|
|
override func viewDidLoad() { |
|
|
@ -27,11 +20,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec |
|
|
|
|
|
|
|
feedbackGenerator = UINotificationFeedbackGenerator() |
|
|
|
navigationItem.title = "Toot" |
|
|
|
|
|
|
|
initializeFetchedResultsController() |
|
|
|
} |
|
|
|
|
|
|
|
override func viewDidAppear(_ animated: Bool) { |
|
|
|
super.viewDidAppear(animated) |
|
|
|
loadStatuses() |
|
|
|
|
|
|
|
fetchStatuses { error in |
|
|
|
if error != nil { |
|
|
@ -162,8 +156,6 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec |
|
|
|
CoreDataManager.shared.saveContext() |
|
|
|
|
|
|
|
self.loading = false |
|
|
|
self.loadStatuses() |
|
|
|
|
|
|
|
completion(nil) |
|
|
|
} |
|
|
|
} |
|
|
@ -171,7 +163,85 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func loadStatuses() { |
|
|
|
override func updateCell(_ cell: AbstractStatusTableViewCell, withStatusAt indexPath: IndexPath) { |
|
|
|
guard let status = fetchedResultsController?.object(at: indexPath) else { |
|
|
|
fatalError("CoreData error") |
|
|
|
} |
|
|
|
|
|
|
|
cell.statusView.delegate = self |
|
|
|
cell.statusView.update(withStatus: status) |
|
|
|
cell.statusView.topDividerView.isHidden = indexPath.row == 0 |
|
|
|
cell.statusView.bottomDividerView.isHidden = false |
|
|
|
cell.statusView.topLoadMoreView.isHidden = true |
|
|
|
cell.statusView.bottomLoadMoreView.isHidden = true |
|
|
|
cell.statusView.replyView.isHidden = true |
|
|
|
} |
|
|
|
|
|
|
|
func updateMainCell(_ cell: MainStatusTableViewCell, withStatus status: StatusMO) { |
|
|
|
cell.avatarImageView.setRoundedCorners() |
|
|
|
|
|
|
|
func updateAccountView(status: StatusMO) { |
|
|
|
if let account = status.account { |
|
|
|
let options: KingfisherOptionsInfo = SettingsManager.automaticallyPlayGIFs ? [] : [.onlyLoadFirstFrame] |
|
|
|
cell.avatarImageView.kf.setImage(with: account.avatarURL!, options: options) |
|
|
|
cell.displayNameLabel.text = account.displayName |
|
|
|
cell.usernameLabel.text = "@\(account.acct!)" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if let reblog = status.reblog { |
|
|
|
updateAccountView(status: reblog) |
|
|
|
} else { |
|
|
|
updateAccountView(status: status) |
|
|
|
} |
|
|
|
|
|
|
|
if let content = status.content { |
|
|
|
cell.contentTextView.attributedText = content.htmlAttributed(size: 16) |
|
|
|
} |
|
|
|
|
|
|
|
cell.attachmentsView.backgroundColor = UIColor.white |
|
|
|
cell.attachmentsView.isHidden = true |
|
|
|
|
|
|
|
if let attachments = status.attachments, attachments.count > 0 { |
|
|
|
cell.attachmentsView.isHidden = false |
|
|
|
cell.attachmentsView.isUserInteractionEnabled = true |
|
|
|
|
|
|
|
let attachmentManager = AttachmentManager() |
|
|
|
attachmentManager.delegate = self |
|
|
|
attachmentManager.setupAttachmentView(cell.attachmentsView, withAttachments: attachments) |
|
|
|
} |
|
|
|
|
|
|
|
let dateFormatter = DateFormatter() |
|
|
|
dateFormatter.dateStyle = .medium |
|
|
|
dateFormatter.timeStyle = .none |
|
|
|
|
|
|
|
cell.timestampDateLabel.text = dateFormatter.string(from: status.createdAt!) |
|
|
|
|
|
|
|
dateFormatter.dateStyle = .none |
|
|
|
dateFormatter.timeStyle = .short |
|
|
|
|
|
|
|
cell.timestampTimeLabel.text = dateFormatter.string(from: status.createdAt!) |
|
|
|
|
|
|
|
cell.repliesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.repliesCount), number: .decimal) |
|
|
|
cell.boostsLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.reblogsCount), number: .decimal) |
|
|
|
cell.favoritesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.favoritesCount), number: .decimal) |
|
|
|
|
|
|
|
if status.reblogged { |
|
|
|
cell.boostsImageView.image = UIImage(named: "Boost Bold") |
|
|
|
} else { |
|
|
|
cell.boostsImageView.image = UIImage(named: "Boost Regular") |
|
|
|
} |
|
|
|
|
|
|
|
if status.favorited { |
|
|
|
cell.favoritesImageView.image = UIImage(named: "Star Filled") |
|
|
|
} else { |
|
|
|
cell.favoritesImageView.image = UIImage(named: "Star Regular") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
extension StatusTableViewController: NSFetchedResultsControllerDelegate { |
|
|
|
func initializeFetchedResultsController() { |
|
|
|
guard let status = status else { |
|
|
|
return |
|
|
|
} |
|
|
@ -179,54 +249,57 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec |
|
|
|
let request = NSFetchRequest<StatusMO>(entityName: "Status") |
|
|
|
request.predicate = NSPredicate(format: "id == %@ OR ANY ancestors == %@ OR ANY descendants == %@", status.id!, status, status) |
|
|
|
request.sortDescriptors = [ |
|
|
|
NSSortDescriptor(key: "createdAt", ascending: false), |
|
|
|
NSSortDescriptor(key: "createdAt", ascending: true), |
|
|
|
] |
|
|
|
|
|
|
|
ancestors = [] |
|
|
|
descendants = [] |
|
|
|
fetchedResultsController = NSFetchedResultsController( |
|
|
|
fetchRequest: request, |
|
|
|
managedObjectContext: CoreDataManager.shared.context, |
|
|
|
sectionNameKeyPath: nil, |
|
|
|
cacheName: nil |
|
|
|
) |
|
|
|
|
|
|
|
fetchedResultsController!.delegate = self |
|
|
|
|
|
|
|
do { |
|
|
|
let statuses = try CoreDataManager.shared.context.fetch(request) |
|
|
|
statuses.forEach { s in |
|
|
|
switch s.createdAt!.compare(status.createdAt!) { |
|
|
|
case .orderedAscending: |
|
|
|
ancestors.append(s) |
|
|
|
case .orderedSame: |
|
|
|
return |
|
|
|
case .orderedDescending: |
|
|
|
descendants.append(s) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ancestors.reverse() |
|
|
|
descendants.reverse() |
|
|
|
|
|
|
|
self.tableView.reloadData() |
|
|
|
try fetchedResultsController!.performFetch() |
|
|
|
} catch { |
|
|
|
print("\(error)") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override func boostTapped() { |
|
|
|
loadStatuses() |
|
|
|
} |
|
|
|
|
|
|
|
override func favoriteTapped() { |
|
|
|
loadStatuses() |
|
|
|
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { |
|
|
|
tableView.beginUpdates() |
|
|
|
} |
|
|
|
|
|
|
|
override func revealTapped() { |
|
|
|
loadStatuses() |
|
|
|
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { |
|
|
|
switch type { |
|
|
|
case NSFetchedResultsChangeType.insert: |
|
|
|
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.none) |
|
|
|
case NSFetchedResultsChangeType.delete: |
|
|
|
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.none) |
|
|
|
case NSFetchedResultsChangeType.update: |
|
|
|
if let cell = tableView.cellForRow(at: indexPath!) as? StatusTableViewCell { |
|
|
|
updateCell(cell, withStatusAt: indexPath!) |
|
|
|
} |
|
|
|
|
|
|
|
if let cell = tableView.cellForRow(at: indexPath!) as? MainStatusTableViewCell { |
|
|
|
updateMainCell(cell, withStatus: anObject as! StatusMO) |
|
|
|
} |
|
|
|
case NSFetchedResultsChangeType.move: |
|
|
|
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade) |
|
|
|
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override func hideTapped() { |
|
|
|
loadStatuses() |
|
|
|
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { |
|
|
|
tableView.endUpdates() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
extension StatusTableViewController { |
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int { |
|
|
|
return 3 |
|
|
|
return 1 |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { |
|
|
@ -236,45 +309,28 @@ extension StatusTableViewController { |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { |
|
|
|
switch indexPath.section { |
|
|
|
case 0: return CellHeightManager.get(status: self.ancestors[indexPath.row]) |
|
|
|
case 2: return CellHeightManager.get(status: self.descendants[indexPath.row]) |
|
|
|
default: return UITableView.automaticDimension |
|
|
|
guard let status = fetchedResultsController?.object(at: indexPath) else { |
|
|
|
return UITableView.automaticDimension |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { |
|
|
|
let height = CGFloat(25) |
|
|
|
|
|
|
|
switch section { |
|
|
|
case 0: return ancestors.count > 0 ? height : 0 |
|
|
|
case 2: return descendants.count > 0 ? height : 0 |
|
|
|
default: return height |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { |
|
|
|
switch section { |
|
|
|
case 0: return ancestors.count > 0 ? "In Reply To" : nil |
|
|
|
case 2: return descendants.count > 0 ? "Replies" : nil |
|
|
|
default: return "Toot" |
|
|
|
if status == self.status { |
|
|
|
return UITableView.automaticDimension |
|
|
|
} |
|
|
|
|
|
|
|
return CellHeightManager.get(status: status) |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
|
|
|
switch section { |
|
|
|
case 0: return ancestors.count |
|
|
|
case 2: return descendants.count |
|
|
|
default: return 1 |
|
|
|
guard let count = fetchedResultsController?.fetchedObjects?.count else { |
|
|
|
return 0 |
|
|
|
} |
|
|
|
|
|
|
|
return count |
|
|
|
} |
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
|
|
|
var status: StatusMO |
|
|
|
switch indexPath.section { |
|
|
|
case 0: status = self.ancestors[indexPath.row] |
|
|
|
case 2: status = self.descendants[indexPath.row] |
|
|
|
default: status = self.status! |
|
|
|
guard let status = fetchedResultsController?.object(at: indexPath) else { |
|
|
|
fatalError("CoreData error") |
|
|
|
} |
|
|
|
|
|
|
|
if status == self.status { |
|
|
@ -282,66 +338,9 @@ extension StatusTableViewController { |
|
|
|
fatalError("Unable to find reusable cell") |
|
|
|
} |
|
|
|
|
|
|
|
updateMainCell(cell, withStatus: status) |
|
|
|
cell.contentTextView.delegate = self |
|
|
|
cell.avatarImageView.setRoundedCorners() |
|
|
|
|
|
|
|
func updateAccountView(status: StatusMO) { |
|
|
|
if let account = status.account { |
|
|
|
let options: KingfisherOptionsInfo = SettingsManager.automaticallyPlayGIFs ? [] : [.onlyLoadFirstFrame] |
|
|
|
cell.avatarImageView.kf.setImage(with: account.avatarURL!, options: options) |
|
|
|
cell.displayNameLabel.text = account.displayName |
|
|
|
cell.usernameLabel.text = "@\(account.acct!)" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if let reblog = status.reblog { |
|
|
|
updateAccountView(status: reblog) |
|
|
|
} else { |
|
|
|
updateAccountView(status: status) |
|
|
|
} |
|
|
|
|
|
|
|
if let content = status.content { |
|
|
|
cell.contentTextView.attributedText = content.htmlAttributed(size: 16) |
|
|
|
} |
|
|
|
|
|
|
|
cell.attachmentsView.backgroundColor = UIColor.white |
|
|
|
cell.attachmentsView.isHidden = true |
|
|
|
|
|
|
|
if let attachments = status.attachments, attachments.count > 0 { |
|
|
|
cell.attachmentsView.isHidden = false |
|
|
|
cell.attachmentsView.isUserInteractionEnabled = true |
|
|
|
|
|
|
|
let attachmentManager = AttachmentManager() |
|
|
|
attachmentManager.delegate = self |
|
|
|
attachmentManager.setupAttachmentView(cell.attachmentsView, withAttachments: attachments) |
|
|
|
} |
|
|
|
|
|
|
|
let dateFormatter = DateFormatter() |
|
|
|
dateFormatter.dateStyle = .medium |
|
|
|
dateFormatter.timeStyle = .none |
|
|
|
|
|
|
|
cell.timestampDateLabel.text = dateFormatter.string(from: status.createdAt!) |
|
|
|
|
|
|
|
dateFormatter.dateStyle = .none |
|
|
|
dateFormatter.timeStyle = .short |
|
|
|
|
|
|
|
cell.timestampTimeLabel.text = dateFormatter.string(from: status.createdAt!) |
|
|
|
|
|
|
|
cell.repliesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.repliesCount), number: .decimal) |
|
|
|
cell.boostsLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.reblogsCount), number: .decimal) |
|
|
|
cell.favoritesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.favoritesCount), number: .decimal) |
|
|
|
|
|
|
|
if status.reblogged { |
|
|
|
cell.boostsImageView.image = UIImage(named: "Boost Bold") |
|
|
|
} else { |
|
|
|
cell.boostsImageView.image = UIImage(named: "Boost Regular") |
|
|
|
} |
|
|
|
|
|
|
|
if status.favorited { |
|
|
|
cell.favoritesImageView.image = UIImage(named: "Star Filled") |
|
|
|
} else { |
|
|
|
cell.favoritesImageView.image = UIImage(named: "Star Regular") |
|
|
|
} |
|
|
|
|
|
|
|
return cell |
|
|
|
} else { |
|
|
@ -349,13 +348,8 @@ extension StatusTableViewController { |
|
|
|
fatalError("Unable to find reusable cell") |
|
|
|
} |
|
|
|
|
|
|
|
updateCell(cell, withStatusAt: indexPath) |
|
|
|
cell.statusView.delegate = self |
|
|
|
cell.statusView.update(withStatus: status) |
|
|
|
cell.statusView.topDividerView.isHidden = indexPath.row == 0 |
|
|
|
cell.statusView.bottomDividerView.isHidden = false |
|
|
|
cell.statusView.topLoadMoreView.isHidden = true |
|
|
|
cell.statusView.bottomLoadMoreView.isHidden = true |
|
|
|
cell.statusView.replyView.isHidden = true |
|
|
|
|
|
|
|
return cell |
|
|
|
} |
|
|
|