// // ProfileTableViewController.swift // Tusker // // Created by Shadowfacts on 8/27/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import Pachyderm import SafariServices class ProfileTableViewController: UITableViewController, PreferencesAdaptive { let router: AppRouter var accountID: String! { didSet { if shouldLoadOnAccountIDSet { DispatchQueue.main.async { self.updateAccountUI() } } } } var statusIDs: [String] = [] { didSet { DispatchQueue.main.async { self.tableView.reloadData() } } } var older: RequestRange? var newer: RequestRange? var shouldLoadOnAccountIDSet = false var loadingVC: LoadingViewController? = nil init(accountID: String?, router: AppRouter) { self.accountID = accountID self.router = router 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") } override func viewDidLoad() { super.viewDidLoad() tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "ProfileHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "headerCell") if let accountID = accountID { if MastodonCache.account(for: accountID) != nil { updateAccountUI() } else { loadingVC = LoadingViewController() add(loadingVC!) MastodonCache.account(for: accountID) { (account) in guard account != nil 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 } DispatchQueue.main.async { self.updateAccountUI() self.tableView.reloadData() } } } } else { loadingVC = LoadingViewController() add(loadingVC!) shouldLoadOnAccountIDSet = true } registerForPreviewing(with: self, sourceView: view) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) for cell in tableView.visibleCells { if let cell = cell as? PreferencesAdaptive { cell.updateUIForPreferences() } } if MastodonCache.account(for: accountID) != nil { updateUIForPreferences() } } override var previewActionItems: [UIPreviewActionItem] { var actions = [UIPreviewActionItem]() if let account = MastodonCache.account(for: accountID) { actions.append(UIPreviewAction(title: "Open in Safari", style: .default, handler: { (_, _) in let vc = self.router.safariViewController(for: account.url) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) })) actions.append(UIPreviewAction(title: "Share", style: .default, handler: { (_, _) in let vc = UIActivityViewController(activityItems: [account.url], applicationActivities: nil) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) })) actions.append(UIPreviewAction(title: "Send Message", style: .default, handler: { (_, _) in let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, router: self.router)) UIApplication.shared.delegate!.window!!.rootViewController!.present(vc, animated: true) })) } return actions } func updateAccountUI() { loadingVC?.remove() updateUIForPreferences() getStatuses() { response in guard case let .success(statuses, pagination) = response else { fatalError() } MastodonCache.addAll(statuses: statuses) self.statusIDs = statuses.map { $0.id } self.older = pagination?.older self.newer = pagination?.newer } } func updateUIForPreferences() { guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } navigationItem.title = account.realDisplayName } func getStatuses(for range: RequestRange = .default, completion: @escaping Client.Callback<[Status]>) { let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: false, excludeReplies: !Preferences.shared.showRepliesInProfiles) MastodonController.client.run(request, completion: completion) } func sendMessageMentioning() { guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, router: router)) present(vc, animated: true) } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: return accountID == nil || MastodonCache.account(for: accountID) == nil ? 0 : 1 case 1: return statusIDs.count default: return 0 } } 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.updateUI(for: accountID) cell.delegate = self return cell case 1: guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } let statusID = statusIDs[indexPath.row] cell.updateUI(for: statusID) cell.delegate = self return cell default: fatalError("Invalid section \(indexPath.section) for profile VC") } } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if indexPath.section == 1 && indexPath.row == statusIDs.count - 1 { guard let older = older else { return } getStatuses(for: older) { response in guard case let .success(newStatuses, pagination) = response else { fatalError() } self.older = pagination?.older MastodonCache.addAll(statuses: newStatuses) self.statusIDs.append(contentsOf: newStatuses.map { $0.id }) } } } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return tableView.cellForRow(at: indexPath) is TableViewSwipeActionProvider } 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.newer = pagination?.newer MastodonCache.addAll(statuses: newStatuses) self.statusIDs.insert(contentsOf: newStatuses.map { $0.id }, at: 0) DispatchQueue.main.async { self.refreshControl?.endRefreshing() } } } @objc func composePressed(_ sender: Any) { sendMessageMentioning() } } extension ProfileTableViewController: StatusTableViewCellDelegate {} extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate { func showMoreOptions() { let account = MastodonCache.account(for: accountID)! let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) alert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { _ in let vc = SFSafariViewController(url: account.url) self.present(vc, animated: true) })) alert.addAction(UIAlertAction(title: "Share", style: .default, handler: { _ in let vc = UIActivityViewController(activityItems: [account.url], applicationActivities: nil) self.present(vc, animated: true) })) alert.addAction(UIAlertAction(title: "Send Message", style: .default, handler: { _ in self.sendMessageMentioning() })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) MastodonCache.relationship(for: account.id) { (relationship) in guard let relationship = relationship else { DispatchQueue.main.async { self.present(alert, animated: true) } return } let title = relationship.following ? "Unfollow": "Follow" DispatchQueue.main.async { alert.addAction(UIAlertAction(title: title, style: .default, handler: { (_) in UIImpactFeedbackGenerator(style: .medium).impactOccurred() let request = (relationship.following ? Account.unfollow : Account.follow)(account.id) MastodonController.client.run(request, completion: { (response) in if case let .success(relationship, _) = response { MastodonCache.add(relationship: relationship) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) fatalError() } }) })) self.present(alert, animated: true) } } } }