diff --git a/Tusker/MastodonCache.swift b/Tusker/MastodonCache.swift index d06a64afc9..98ea5a37d3 100644 --- a/Tusker/MastodonCache.swift +++ b/Tusker/MastodonCache.swift @@ -7,6 +7,7 @@ // import Foundation +import Combine import Pachyderm class MastodonCache { @@ -16,6 +17,9 @@ class MastodonCache { private static var relationships = [String: Relationship]() private static var notifications = [String: Pachyderm.Notification]() + static let statusSubject = PassthroughSubject() + static let accountSubject = PassthroughSubject() + // MARK: - Statuses static func status(for id: String) -> Status? { return statuses[id] @@ -28,6 +32,8 @@ class MastodonCache { add(status: reblog) add(account: reblog.account) } + + statusSubject.send(status) } static func status(for id: String, completion: @escaping (Status?) -> Void) { @@ -57,6 +63,7 @@ class MastodonCache { static func set(account: Account, for id: String) { accounts[id] = account + accountSubject.send(account) } static func account(for id: String, completion: @escaping (Account?) -> Void) { diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index 6e9c2edc1a..2b7d70e605 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -116,12 +116,12 @@ class ConversationTableViewController: EnhancedTableViewController { if statusID == mainStatusID { guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() } cell.selectionStyle = .none - cell.updateUI(for: statusID) + cell.updateUI(statusID: statusID) cell.delegate = self return cell } else { guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } - cell.updateUI(for: statusID) + cell.updateUI(statusID: statusID) cell.delegate = self return cell } diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index b0ac73b182..1268c942b1 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -92,8 +92,7 @@ class NotificationsTableViewController: EnhancedTableViewController { switch notification.kind { case .mention: guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } - let status = notification.status! - cell.updateUI(for: status.id) + cell.updateUI(statusID: notification.status!.id) cell.delegate = self return cell case .favourite, .reblog: diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift index fb8176f4d7..54410b044c 100644 --- a/Tusker/Screens/Profile/ProfileTableViewController.swift +++ b/Tusker/Screens/Profile/ProfileTableViewController.swift @@ -183,8 +183,7 @@ class ProfileTableViewController: EnhancedTableViewController, PreferencesAdapti 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.updateUI(statusID: statusIDs[indexPath.row]) cell.delegate = self return cell default: diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift index 92100d7278..fb36e354f9 100644 --- a/Tusker/Screens/Timeline/TimelineTableViewController.swift +++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift @@ -101,8 +101,8 @@ class TimelineTableViewController: EnhancedTableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } - - cell.updateUI(for: statusID(for: indexPath)) + + cell.updateUI(statusID: statusID(for: indexPath)) cell.delegate = self return cell diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift index 1d19c54dca..691bfc870e 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift @@ -7,6 +7,7 @@ // import UIKit +import Combine import Pachyderm class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive { @@ -47,6 +48,14 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive var avatarURL: URL? var updateTimestampWorkItem: DispatchWorkItem? + var statusUpdater: Cancellable? + var accountUpdater: Cancellable? + + deinit { + statusUpdater?.cancel() + accountUpdater?.cancel() + } + override func awakeFromNib() { displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) @@ -55,6 +64,16 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive attachmentsView.delegate = self attachmentsView.layer.cornerRadius = 5 attachmentsView.layer.masksToBounds = true + + statusUpdater = MastodonCache.statusSubject + .filter { $0.id == self.statusID } + .receive(on: DispatchQueue.main) + .sink(receiveValue: updateStatusState(status:)) + + accountUpdater = MastodonCache.accountSubject + .filter { $0.id == self.accountID } + .receive(on: DispatchQueue.main) + .sink(receiveValue: updateUI(account:)) } func updateUIForPreferences() { @@ -64,11 +83,11 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive displayNameLabel.text = account.realDisplayName } - func updateUI(for statusID: String) { - self.statusID = statusID - + func updateUI(statusID: String) { guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } + self.statusID = status.id + let account: Account if let reblog = status.reblog { account = reblog.account @@ -76,9 +95,25 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive account = status.account } self.accountID = account.id - + updateUI(account: account) updateUIForPreferences() + updateTimestamp() + + attachmentsView.updateUI(status: status) + + let realStatus = status.reblog ?? status + updateStatusState(status: realStatus) + + contentLabel.statusID = statusID + } + + private func updateStatusState(status: Status) { + favorited = status.favourited ?? false + reblogged = status.reblogged ?? false + } + + private func updateUI(account: Account) { usernameLabel.text = "@\(account.acct)" avatarImageView.image = nil avatarURL = account.avatar @@ -89,15 +124,6 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive self.avatarURL = nil } } - updateTimestamp() - - attachmentsView.updateUI(status: status) - - let realStatus = status.reblog ?? status - favorited = realStatus.favourited ?? false - reblogged = realStatus.reblogged ?? false - - contentLabel.statusID = statusID } func updateTimestamp() { diff --git a/Tusker/Views/Status/StatusTableViewCell.swift b/Tusker/Views/Status/StatusTableViewCell.swift index 6097540920..fc57f92143 100644 --- a/Tusker/Views/Status/StatusTableViewCell.swift +++ b/Tusker/Views/Status/StatusTableViewCell.swift @@ -7,6 +7,7 @@ // import UIKit +import Combine import Pachyderm protocol StatusTableViewCellDelegate: TuskerNavigationDelegate { @@ -50,6 +51,16 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { var updateTimestampWorkItem: DispatchWorkItem? var attachmentDataTasks: [URLSessionDataTask] = [] + var statusUpdater: Cancellable? + var accountUpdater: Cancellable? + var rebloggerAccountUpdater: Cancellable? + + deinit { + statusUpdater?.cancel() + accountUpdater?.cancel() + rebloggerAccountUpdater?.cancel() + } + override func awakeFromNib() { displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) @@ -59,8 +70,26 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { attachmentsView.delegate = self attachmentsView.layer.cornerRadius = 5 attachmentsView.layer.masksToBounds = true + + statusUpdater = MastodonCache.statusSubject + .filter { $0.id == self.statusID || $0.id == self.reblogStatusID } + .receive(on: DispatchQueue.main) + .sink(receiveValue: updateStatusState(status:)) + + accountUpdater = MastodonCache.accountSubject + .filter { $0.id == self.accountID } + .receive(on: DispatchQueue.main) + .sink(receiveValue: updateUI(account:)) + + rebloggerAccountUpdater = MastodonCache.accountSubject + .filter { $0.id == self.rebloggerID } + .receive(on: DispatchQueue.main) + .sink(receiveValue: { (_) in + // this method is responsible for setting the reblog label text + self.updateUIForPreferences() + }) } - + func updateUIForPreferences() { guard let account = MastodonCache.account(for: accountID) else { fatalError("") } avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) @@ -70,9 +99,10 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { } displayNameLabel.text = account.realDisplayName } - - func updateUI(for statusID: String) { + + func updateUI(statusID: String) { guard var status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } + self.statusID = statusID if let reblogID = status.reblog?.id, let reblog = MastodonCache.status(for: reblogID) { @@ -87,10 +117,26 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { } let account = status.account self.accountID = account.id - self.statusID = status.id - + updateUI(account: account) + updateUIForPreferences() - + + updateTimestamp() + + attachmentsView.updateUI(status: status) + + let realStatus = status.reblog ?? status + updateStatusState(status: realStatus) + + contentLabel.statusID = status.id + } + + private func updateStatusState(status: Status) { + favorited = status.favourited ?? false + reblogged = status.reblogged ?? false + } + + private func updateUI(account: Account) { usernameLabel.text = "@\(account.acct)" avatarImageView.image = nil avatarURL = account.avatar @@ -101,15 +147,6 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { self.avatarURL = nil } } - updateTimestamp() - - attachmentsView.updateUI(status: status) - - let realStatus = status.reblog ?? status - favorited = realStatus.favourited ?? false - reblogged = realStatus.reblogged ?? false - - contentLabel.statusID = status.id } func updateTimestamp() { @@ -247,7 +284,6 @@ extension StatusTableViewCell: TableViewSwipeActionProvider { } completion(true) MastodonCache.add(status: status) - self.updateUI(for: self.reblogStatusID ?? self.statusID) } }) } @@ -275,7 +311,6 @@ extension StatusTableViewCell: TableViewSwipeActionProvider { } completion(true) MastodonCache.add(status: status) - self.updateUI(for: self.reblogStatusID ?? self.statusID) } }) }