From 0f1a13d2a74bf12e70a6c0150165f1986d975c3e Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 28 Aug 2018 14:29:06 -0400 Subject: [PATCH] Conversation view --- Tusker.xcodeproj/project.pbxproj | 30 ++++- Tusker/Extensions/Status+Equatable.swift | 15 +++ ...ntroller+StatusTableViewCellDelegate.swift | 8 ++ Tusker/Storyboards/Conversation.storyboard | 43 +++++++ .../ConversationViewController.swift | 114 ++++++++++++++++++ .../ProfileTableViewController.swift | 10 +- .../TimelineTableViewController.swift | 8 +- .../ConversationMainStatusTableViewCell.swift | 91 ++++++++++++++ .../ConversationMainStatusTableViewCell.xib | 70 +++++++++++ Tusker/Views/StatusTableViewCell.swift | 13 +- 10 files changed, 390 insertions(+), 12 deletions(-) create mode 100644 Tusker/Extensions/Status+Equatable.swift rename Tusker/{Views => Extensions}/UIViewController+StatusTableViewCellDelegate.swift (71%) create mode 100644 Tusker/Storyboards/Conversation.storyboard create mode 100644 Tusker/View Controllers/ConversationViewController.swift create mode 100644 Tusker/Views/ConversationMainStatusTableViewCell.swift create mode 100644 Tusker/Views/ConversationMainStatusTableViewCell.xib diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index a677aaa5..bcd8d04c 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; + D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; }; + D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; }; D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* StatusTableViewCell.xib */; }; D667E5E3213499F70057A976 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E2213499F70057A976 /* Profile.storyboard */; }; D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */; }; @@ -21,6 +23,9 @@ D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */; }; D667E5EF2134C39F0057A976 /* StatusContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */; }; D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */; }; + D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5F22135BC260057A976 /* Conversation.storyboard */; }; + D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationViewController.swift */; }; + D667E5F82135C3040057A976 /* Status+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Status+Equatable.swift */; }; D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; }; D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; }; @@ -77,6 +82,8 @@ D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Onboarding.storyboard; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; + D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConversationMainStatusTableViewCell.xib; sourceTree = ""; }; + D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMainStatusTableViewCell.swift; sourceTree = ""; }; D667E5E02134937B0057A976 /* StatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = ""; }; D667E5E2213499F70057A976 /* Profile.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Profile.storyboard; sourceTree = ""; }; D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = ""; }; @@ -84,6 +91,9 @@ D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderTableViewCell.swift; sourceTree = ""; }; D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = ""; }; D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+StatusTableViewCellDelegate.swift"; sourceTree = ""; }; + D667E5F22135BC260057A976 /* Conversation.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Conversation.storyboard; sourceTree = ""; }; + D667E5F42135BCD50057A976 /* ConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewController.swift; sourceTree = ""; }; + D667E5F72135C3040057A976 /* Status+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+Equatable.swift"; sourceTree = ""; }; D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; }; D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -131,6 +141,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D667E5F62135C2ED0057A976 /* Extensions */ = { + isa = PBXGroup; + children = ( + D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */, + D667E5F72135C3040057A976 /* Status+Equatable.swift */, + ); + path = Extensions; + sourceTree = ""; + }; D6BED1722126661300F02DA0 /* Views */ = { isa = PBXGroup; children = ( @@ -138,9 +157,10 @@ D667E5EE2134C39F0057A976 /* StatusContentLabel.swift */, D667E5E02134937B0057A976 /* StatusTableViewCell.xib */, D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */, - D667E5F02134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift */, D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */, D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */, + D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */, + D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */, ); path = Views; sourceTree = ""; @@ -174,6 +194,7 @@ 04DACE89212CA6B7009840C4 /* Timeline.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */, 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */, + D667E5F62135C2ED0057A976 /* Extensions */, D6F953F121251A2F00CF0F2B /* Controllers */, D6F953E9212519B800CF0F2B /* View Controllers */, D6BED1722126661300F02DA0 /* Views */, @@ -210,6 +231,7 @@ D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */, 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */, D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */, + D667E5F42135BCD50057A976 /* ConversationViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -221,6 +243,7 @@ D6F953ED21251A0700CF0F2B /* Timeline.storyboard */, D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */, D667E5E2213499F70057A976 /* Profile.storyboard */, + D667E5F22135BC260057A976 /* Conversation.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -338,10 +361,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */, D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */, D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */, D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */, D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */, + D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */, D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */, D6D4DDD5212518A000E1C4BB /* Main.storyboard in Resources */, D667E5E3213499F70057A976 /* Profile.storyboard in Resources */, @@ -372,7 +397,9 @@ files = ( 04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, + D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */, D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */, + D667E5F82135C3040057A976 /* Status+Equatable.swift in Sources */, 04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */, D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */, D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, @@ -382,6 +409,7 @@ D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */, D64A0CD32132153900640E3B /* HTMLContentLabel.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+StatusTableViewCellDelegate.swift in Sources */, + D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */, D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */, D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, ); diff --git a/Tusker/Extensions/Status+Equatable.swift b/Tusker/Extensions/Status+Equatable.swift new file mode 100644 index 00000000..ec1b5457 --- /dev/null +++ b/Tusker/Extensions/Status+Equatable.swift @@ -0,0 +1,15 @@ +// +// Status+Equatable.swift +// Tusker +// +// Created by Shadowfacts on 8/28/18. +// Copyright © 2018 Shadowfacts. All rights reserved. +// + +import MastodonKit + +extension Status: Equatable { + public static func ==(lhs: Status, rhs: Status) -> Bool { + return lhs.id == rhs.id + } +} diff --git a/Tusker/Views/UIViewController+StatusTableViewCellDelegate.swift b/Tusker/Extensions/UIViewController+StatusTableViewCellDelegate.swift similarity index 71% rename from Tusker/Views/UIViewController+StatusTableViewCellDelegate.swift rename to Tusker/Extensions/UIViewController+StatusTableViewCellDelegate.swift index 98ce64a3..8bf0ff2f 100644 --- a/Tusker/Views/UIViewController+StatusTableViewCellDelegate.swift +++ b/Tusker/Extensions/UIViewController+StatusTableViewCellDelegate.swift @@ -33,4 +33,12 @@ extension UIViewController: StatusTableViewCellDelegate { present(vc, animated: true) } + func selected(status: Status) { + guard let navigationController = navigationController else { + fatalError("Can't show conversation VC when not in navigation controller") + } + let vc = ConversationViewController.create(for: status) + navigationController.pushViewController(vc, animated: true) + } + } diff --git a/Tusker/Storyboards/Conversation.storyboard b/Tusker/Storyboards/Conversation.storyboard new file mode 100644 index 00000000..d6871a2c --- /dev/null +++ b/Tusker/Storyboards/Conversation.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/View Controllers/ConversationViewController.swift b/Tusker/View Controllers/ConversationViewController.swift new file mode 100644 index 00000000..506d5860 --- /dev/null +++ b/Tusker/View Controllers/ConversationViewController.swift @@ -0,0 +1,114 @@ +// +// ConversationTableViewController.swift +// Tusker +// +// Created by Shadowfacts on 8/28/18. +// Copyright © 2018 Shadowfacts. All rights reserved. +// + +import UIKit +import MastodonKit + +class ConversationViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + static func create(for mainStatus: Status) -> ConversationViewController { + guard let conversationController = UIStoryboard(name: "Conversation", bundle: nil).instantiateInitialViewController() as? ConversationViewController else { fatalError() } + conversationController.mainStatus = mainStatus + return conversationController + } + + @IBOutlet weak var tableView: UITableView! + + var mainStatus: Status! + + var statuses: [Status] = [] { + didSet { + DispatchQueue.main.async { + self.tableView.reloadData() + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.delegate = self + tableView.dataSource = self + + tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") + tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell") + + statuses = [mainStatus] + + let req = Statuses.context(id: mainStatus.id) + MastodonController.shared.client.run(req) { result in + guard case let .success(context, _) = result else { fatalError() } + var statuses = self.getDirectParents(of: self.mainStatus, from: context.ancestors) + statuses.append(self.mainStatus) + statuses.append(contentsOf: context.descendants) + self.statuses = statuses + let indexPath = IndexPath(row: statuses.firstIndex(of: self.mainStatus)!, section: 0) + DispatchQueue.main.async { + self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false) + } + } + } + + 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: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + + // MARK: - Table view data source + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return statuses.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let status = statuses[indexPath.row] + + if status == mainStatus { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() } + cell.selectionStyle = .none + cell.updateUI(for: status) + cell.delegate = self + return cell + } else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } + cell.updateUI(for: status) + cell.delegate = self + return cell + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let status = statuses[indexPath.row] + guard status != mainStatus else { return } + guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() } + cell.didSelect() + } + +} diff --git a/Tusker/View Controllers/ProfileTableViewController.swift b/Tusker/View Controllers/ProfileTableViewController.swift index 1938577d..f8b7b948 100644 --- a/Tusker/View Controllers/ProfileTableViewController.swift +++ b/Tusker/View Controllers/ProfileTableViewController.swift @@ -85,13 +85,13 @@ class ProfileTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: - let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as! ProfileHeaderTableViewCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as? ProfileHeaderTableViewCell else { fatalError() } cell.selectionStyle = .none cell.updateUI(for: account) cell.delegate = self return cell case 1: - let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! StatusTableViewCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } let status = statuses[indexPath.row] cell.updateUI(for: status) cell.delegate = self @@ -101,6 +101,12 @@ class ProfileTableViewController: UITableViewController { } } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard indexPath.section == 1 else { return } + guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() } + cell.didSelect() + } + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if indexPath.section == 1 && indexPath.row == statuses.count - 1 { guard let older = older else { return } diff --git a/Tusker/View Controllers/TimelineTableViewController.swift b/Tusker/View Controllers/TimelineTableViewController.swift index 7b6a960f..3da87cf9 100644 --- a/Tusker/View Controllers/TimelineTableViewController.swift +++ b/Tusker/View Controllers/TimelineTableViewController.swift @@ -8,7 +8,6 @@ import UIKit import MastodonKit -import SafariServices class TimelineTableViewController: UITableViewController { @@ -83,7 +82,7 @@ class TimelineTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! StatusTableViewCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } let status = statuses[indexPath.row] @@ -94,6 +93,11 @@ class TimelineTableViewController: UITableViewController { return cell } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath) as? StatusTableViewCell else { fatalError() } + cell.didSelect() + } + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if indexPath.row == statuses.count - 1 { guard let older = older else { return } diff --git a/Tusker/Views/ConversationMainStatusTableViewCell.swift b/Tusker/Views/ConversationMainStatusTableViewCell.swift new file mode 100644 index 00000000..51570f90 --- /dev/null +++ b/Tusker/Views/ConversationMainStatusTableViewCell.swift @@ -0,0 +1,91 @@ +// +// ConversationMainStatusTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 8/28/18. +// Copyright © 2018 Shadowfacts. All rights reserved. +// + +import UIKit +import MastodonKit + +class ConversationMainStatusTableViewCell: UITableViewCell { + + var delegate: StatusTableViewCellDelegate? + + @IBOutlet weak var displayNameLabel: UILabel! + @IBOutlet weak var usernameLabel: UILabel! + @IBOutlet weak var contentLabel: StatusContentLabel! + @IBOutlet weak var avatarImageView: UIImageView! + + var status: Status! + var account: Account! + + var avatarURL: URL? + + override func awakeFromNib() { + displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) + displayNameLabel.isUserInteractionEnabled = true + usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) + usernameLabel.isUserInteractionEnabled = true + avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) + avatarImageView.isUserInteractionEnabled = true + avatarImageView.layer.cornerRadius = 5 + avatarImageView.layer.masksToBounds = true + } + + func updateUI(for status: Status) { + self.status = status + + let account: Account + if let reblog = status.reblog { + account = reblog.account + } else { + account = status.account + } + self.account = account + + displayNameLabel.text = account.displayName + usernameLabel.text = "@\(account.acct)" + avatarImageView.image = nil + if let url = URL(string: account.avatar) { + avatarURL = url + AvatarCache.shared.get(url) { image in + DispatchQueue.main.async { + self.avatarImageView.image = image + self.avatarURL = nil + } + } + } + + contentLabel.status = status + contentLabel.delegate = self + } + + override func prepareForReuse() { + if let url = avatarURL { + AvatarCache.shared.cancel(url) + } + } + + @objc func accountPressed() { + delegate?.selected(account: account) + } + +} + +extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate { + + func selected(mention: Mention) { + delegate?.selected(mention: mention) + } + + func selected(tag: MastodonKit.Tag) { + delegate?.selected(tag: tag) + } + + func selected(url: URL) { + delegate?.selected(url: url) + } + +} diff --git a/Tusker/Views/ConversationMainStatusTableViewCell.xib b/Tusker/Views/ConversationMainStatusTableViewCell.xib new file mode 100644 index 00000000..22ecf244 --- /dev/null +++ b/Tusker/Views/ConversationMainStatusTableViewCell.xib @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/StatusTableViewCell.swift b/Tusker/Views/StatusTableViewCell.swift index 41e7fe16..bf5c1edc 100644 --- a/Tusker/Views/StatusTableViewCell.swift +++ b/Tusker/Views/StatusTableViewCell.swift @@ -8,7 +8,6 @@ import UIKit import MastodonKit -import SwiftSoup protocol StatusTableViewCellDelegate { @@ -20,6 +19,8 @@ protocol StatusTableViewCellDelegate { func selected(url: URL) + func selected(status: Status) + } class StatusTableViewCell: UITableViewCell { @@ -36,12 +37,6 @@ class StatusTableViewCell: UITableViewCell { var avatarURL: URL? - var layoutManager: NSLayoutManager! - var textContainer: NSTextContainer! - var textStorage: NSTextStorage! - - var links: [NSRange: URL] = [:] - override func awakeFromNib() { displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) displayNameLabel.isUserInteractionEnabled = true @@ -91,6 +86,10 @@ class StatusTableViewCell: UITableViewCell { delegate?.selected(account: account) } + func didSelect() { + delegate?.selected(status: status) + } + } extension StatusTableViewCell: HTMLContentLabelDelegate {