// // ProfileTableViewController.swift // Tusker // // Created by Shadowfacts on 8/27/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import SafariServices class ProfileTableViewController: EnhancedTableViewController { weak var mastodonController: MastodonController! var accountID: String! var pinnedStatuses: [(id: String, state: StatusState)] = [] var timelineSegments: [[(id: String, state: StatusState)]] = [] var older: RequestRange? var newer: RequestRange? private var loadingVC: LoadingViewController? = nil private var loaded = false init(accountID: String?, mastodonController: MastodonController) { self.mastodonController = mastodonController self.accountID = accountID super.init(style: .plain) self.refreshControl = UIRefreshControl() refreshControl!.addTarget(self, action: #selector(refreshStatuses(_:)), for: .valueChanged) navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composePressed(_:))) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemeneted") } deinit { if let id = accountID, let container = mastodonController?.persistentContainer { container.backgroundContext.perform { container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount() } } } override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "ProfileHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "headerCell") tableView.prefetchDataSource = self if accountID == nil { loadingVC = LoadingViewController() embedChild(loadingVC!) } NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if !loaded, let accountID = accountID { loaded = true loadingVC?.removeViewAndController() loadingVC = nil if mastodonController.persistentContainer.account(for: accountID) != nil { updateAccountUI() } else { loadingVC = LoadingViewController() embedChild(loadingVC!) let request = Client.getAccount(id: accountID) mastodonController.run(request) { (response) in guard case let .success(account, _) = response else { let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in self.navigationController!.popViewController(animated: true) })) DispatchQueue.main.async { self.present(alert, animated: true) } return } self.mastodonController.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) { (_) in DispatchQueue.main.async { self.updateAccountUI() self.tableView.reloadData() } } } } } } func updateAccountUI() { updateUIForPreferences() getStatuses(onlyPinned: true) { (response) in guard case let .success(statuses, _) = response else { fatalError() } self.mastodonController.persistentContainer.addAll(statuses: statuses) { self.pinnedStatuses = statuses.map { ($0.id, .unknown) } let indexPaths = (0..) { let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: onlyPinned, excludeReplies: !Preferences.shared.showRepliesInProfiles) mastodonController.run(request, completion: completion) } func sendMessageMentioning() { guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController)) present(vc, animated: true) } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { // 1 section for header, 1 section for pinned, rest for timeline return 2 + timelineSegments.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return accountID == nil || mastodonController.persistentContainer.account(for: accountID) == nil ? 0 : 1 } else if section == 1 { return pinnedStatuses.count } else { return timelineSegments[section - 2].count } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: guard let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as? ProfileHeaderTableViewCell else { fatalError() } cell.selectionStyle = .none cell.delegate = self cell.updateUI(for: accountID) return cell case 1: guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } let (id, state) = pinnedStatuses[indexPath.row] cell.showPinned = true cell.delegate = self cell.updateUI(statusID: id, state: state) return cell default: guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } let (id, state) = timelineSegments[indexPath.section - 2][indexPath.row] cell.delegate = self cell.updateUI(statusID: id, state: state) return cell } } // MARK: - Table view delegate override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // todo: if scrolling up, remove statuses at bottom like timeline VC // load older statuses if at bottom if timelineSegments.count > 0 && indexPath.section - 1 == timelineSegments.count && indexPath.row == timelineSegments[indexPath.section - 2].count - 1 { guard let older = older else { return } getStatuses(for: older) { response in guard case let .success(newStatuses, pagination) = response else { fatalError() } self.mastodonController.persistentContainer.addAll(statuses: newStatuses) { self.older = pagination?.older DispatchQueue.main.async { let start = self.timelineSegments[indexPath.section - 2].count let indexPaths = (0.. 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 refreshStatuses(_ sender: Any) { guard let newer = newer else { return } getStatuses(for: newer) { response in guard case let .success(newStatuses, pagination) = response else { fatalError() } self.mastodonController.persistentContainer.addAll(statuses: newStatuses) { if let newer = pagination?.newer { self.newer = newer } let indexPaths = (0.. 1 { let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id guard let status = mastodonController.persistentContainer.status(for: statusID) else { continue } _ = ImageCache.avatars.get(status.account.avatar, completion: nil) for attachment in status.attachments { _ = ImageCache.attachments.get(attachment.url, completion: nil) } } } func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { for indexPath in indexPaths where indexPath.section > 1 { let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id guard let status = mastodonController.persistentContainer.status(for: statusID) else { continue } ImageCache.avatars.cancelWithoutCallback(status.account.avatar) for attachment in status.attachments { ImageCache.attachments.cancelWithoutCallback(attachment.url) } } } }