From 24a1e7ceb99d4e9c0b47fe15f806d63d38bbdbb4 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 19 Nov 2019 12:08:11 -0500 Subject: [PATCH] Unify most of TimelineStatus and ConverastionMainStatus cell code Closes #54 --- Tusker.xcodeproj/project.pbxproj | 24 +- .../ConversationTableViewController.swift | 6 +- .../NotificationsTableViewController.swift | 4 +- .../Profile/ProfileTableViewController.swift | 6 +- .../Search/SearchTableViewController.swift | 4 +- ...ActionAccountListTableViewController.swift | 4 +- .../TimelineTableViewController.swift | 4 +- ...ll.swift => BaseStatusTableViewCell.swift} | 274 ++++------------ .../ConversationMainStatusTableViewCell.swift | 307 ++---------------- .../ConversationMainStatusTableViewCell.xib | 18 +- Tusker/Views/Status/StatusCell.swift | 16 - .../Status/TimelineStatusTableViewCell.swift | 223 +++++++++++++ ...ll.xib => TimelineStatusTableViewCell.xib} | 36 +- 13 files changed, 350 insertions(+), 576 deletions(-) rename Tusker/Views/Status/{StatusTableViewCell.swift => BaseStatusTableViewCell.swift} (58%) delete mode 100644 Tusker/Views/Status/StatusCell.swift create mode 100644 Tusker/Views/Status/TimelineStatusTableViewCell.swift rename Tusker/Views/Status/{StatusTableViewCell.xib => TimelineStatusTableViewCell.xib} (96%) diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 29ed6f73..1b581a18 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -28,8 +28,8 @@ D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; }; D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D60BAFB82383921D00EED893 /* StatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60BAFB72383921D00EED893 /* StatusCell.swift */; }; D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; }; + D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; }; D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; }; D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; }; D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -112,7 +112,7 @@ D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; }; D667383C23299340000A2373 /* InstanceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667383B23299340000A2373 /* InstanceType.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; - D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* StatusTableViewCell.xib */; }; + D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; }; D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */; }; D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */; }; D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */; }; @@ -180,7 +180,7 @@ D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; }; D6BC9DDA232D8BE5002CA326 /* SearchTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */; }; 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 */; }; + D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; }; D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; }; D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */; }; D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; }; @@ -288,8 +288,8 @@ D60A548B21ED515800F1F87C /* GMImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GMImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D60A548D21ED515800F1F87C /* GMImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GMImagePicker.h; sourceTree = ""; }; D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D60BAFB72383921D00EED893 /* StatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCell.swift; sourceTree = ""; }; D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = ""; }; + D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = ""; }; D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = ""; }; D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -372,7 +372,7 @@ D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; sourceTree = ""; }; D667383B23299340000A2373 /* InstanceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceType.swift; sourceTree = ""; }; D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = ""; }; - D667E5E02134937B0057A976 /* StatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = ""; }; + D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineStatusTableViewCell.xib; sourceTree = ""; }; D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = ""; }; D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileHeaderTableViewCell.xib; sourceTree = ""; }; D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderTableViewCell.swift; sourceTree = ""; }; @@ -439,7 +439,7 @@ D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = ""; }; D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTableViewController.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 = ""; }; + D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = ""; }; D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = ""; }; D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = ""; }; D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; @@ -829,11 +829,11 @@ D641C78A213DD926004B4513 /* Status */ = { isa = PBXGroup; children = ( - D667E5E02134937B0057A976 /* StatusTableViewCell.xib */, - D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */, + D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */, + D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */, + D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */, D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */, D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */, - D60BAFB72383921D00EED893 /* StatusCell.swift */, ); path = Status; sourceTree = ""; @@ -1459,7 +1459,7 @@ 0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */, D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */, D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */, - D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */, + D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */, D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1579,6 +1579,7 @@ D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */, D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, + D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */, D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */, D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */, D6A3BC852321F6C100FD64D5 /* AccountListTableViewController.swift in Sources */, @@ -1600,11 +1601,10 @@ D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */, D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, - D60BAFB82383921D00EED893 /* StatusCell.swift in Sources */, D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */, D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */, D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */, - D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */, + D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */, 0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */, D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */, diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index 99b79fb6..ec4abee2 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -43,7 +43,7 @@ class ConversationTableViewController: EnhancedTableViewController { tableView.delegate = self tableView.dataSource = self - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "ConversationMainStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "mainStatusCell") tableView.prefetchDataSource = self @@ -103,7 +103,7 @@ class ConversationTableViewController: EnhancedTableViewController { cell.delegate = self return cell } else { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } cell.showStatusAutomatically = showStatusesAutomatically cell.updateUI(statusID: statusID) cell.delegate = self @@ -132,7 +132,7 @@ class ConversationTableViewController: EnhancedTableViewController { showStatusesAutomatically = !showStatusesAutomatically for cell in tableView.visibleCells { - guard var cell = cell as? UITableViewCell & StatusCell, + guard let cell = cell as? BaseStatusTableViewCell, cell.collapsible else { continue } cell.showStatusAutomatically = showStatusesAutomatically cell.setCollapsed(!showStatusesAutomatically, animated: false) diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift index 7e7170a1..3b85b016 100644 --- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift @@ -48,7 +48,7 @@ class NotificationsTableViewController: EnhancedTableViewController { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: statusCell) + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "ActionNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: actionGroupCell) tableView.register(UINib(nibName: "FollowNotificationGroupTableViewCell", bundle: nil), forCellReuseIdentifier: followGroupCell) @@ -88,7 +88,7 @@ class NotificationsTableViewController: EnhancedTableViewController { switch group.kind { case .mention: guard let notification = MastodonCache.notification(for: group.notificationIDs.first!), - let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? StatusTableViewCell else { + let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } cell.updateUI(statusID: notification.status!.id) diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift index 1ecd8e35..3653521a 100644 --- a/Tusker/Screens/Profile/ProfileTableViewController.swift +++ b/Tusker/Screens/Profile/ProfileTableViewController.swift @@ -63,7 +63,7 @@ class ProfileTableViewController: EnhancedTableViewController { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "ProfileHeaderTableViewCell", bundle: nil), forCellReuseIdentifier: "headerCell") tableView.prefetchDataSource = self @@ -165,14 +165,14 @@ class ProfileTableViewController: EnhancedTableViewController { cell.updateUI(for: accountID) return cell case 1: - guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } let statusID = pinnedStatusIDs[indexPath.row] cell.showPinned = true cell.updateUI(statusID: statusID) cell.delegate = self return cell default: - guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } let statusID = timelineSegments[indexPath.section - 2][indexPath.row] cell.updateUI(statusID: statusID) cell.delegate = self diff --git a/Tusker/Screens/Search/SearchTableViewController.swift b/Tusker/Screens/Search/SearchTableViewController.swift index 7280f090..7455149b 100644 --- a/Tusker/Screens/Search/SearchTableViewController.swift +++ b/Tusker/Screens/Search/SearchTableViewController.swift @@ -39,7 +39,7 @@ class SearchTableViewController: EnhancedTableViewController { super.viewDidLoad() tableView.register(UINib(nibName: "AccountTableViewCell", bundle: .main), forCellReuseIdentifier: accountCell) - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "HashtagTableViewCell", bundle: .main), forCellReuseIdentifier: hashtagCell) dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in @@ -55,7 +55,7 @@ class SearchTableViewController: EnhancedTableViewController { cell.delegate = self return cell case let .status(id): - let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! StatusTableViewCell + let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! TimelineStatusTableViewCell cell.updateUI(statusID: id) cell.delegate = self return cell diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift index ade0d579..111430e4 100644 --- a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift +++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift @@ -54,7 +54,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController { override func viewDidLoad() { super.viewDidLoad() - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: statusCell) tableView.register(UINib(nibName: "AccountTableViewCell", bundle: .main), forCellReuseIdentifier: accountCell) tableView.rowHeight = UITableView.automaticDimension @@ -108,7 +108,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: - guard let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? StatusTableViewCell else { fatalError() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } cell.updateUI(statusID: statusID) cell.delegate = self return cell diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift index 83b2db5f..61a3a476 100644 --- a/Tusker/Screens/Timeline/TimelineTableViewController.swift +++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift @@ -65,7 +65,7 @@ class TimelineTableViewController: EnhancedTableViewController { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 140 - tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") + tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell") tableView.prefetchDataSource = self @@ -92,7 +92,7 @@ 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() } + guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() } cell.updateUI(statusID: statusID(for: indexPath)) cell.delegate = self diff --git a/Tusker/Views/Status/StatusTableViewCell.swift b/Tusker/Views/Status/BaseStatusTableViewCell.swift similarity index 58% rename from Tusker/Views/Status/StatusTableViewCell.swift rename to Tusker/Views/Status/BaseStatusTableViewCell.swift index b5b6ffab..c81d3274 100644 --- a/Tusker/Views/Status/StatusTableViewCell.swift +++ b/Tusker/Views/Status/BaseStatusTableViewCell.swift @@ -1,65 +1,52 @@ // -// StatusTableViewCell.swift +// BaseStatusTableViewCell.swift // Tusker // -// Created by Shadowfacts on 8/16/18. -// Copyright © 2018 Shadowfacts. All rights reserved. +// Created by Shadowfacts on 11/19/19. +// Copyright © 2019 Shadowfacts. All rights reserved. // import UIKit -import Combine import Pachyderm +import Combine protocol StatusTableViewCellDelegate: TuskerNavigationDelegate { func statusCollapsedStateChanged() } -class StatusTableViewCell: UITableViewCell, StatusCell { +class BaseStatusTableViewCell: UITableViewCell { - static let relativeDateFormatter: RelativeDateTimeFormatter = { - let formatter = RelativeDateTimeFormatter() - formatter.dateTimeStyle = .numeric - formatter.unitsStyle = .short - return formatter - }() - var delegate: StatusTableViewCellDelegate? { didSet { contentLabel.navigationDelegate = delegate } } + @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var displayNameLabel: UILabel! @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var contentWarningLabel: UILabel! @IBOutlet weak var collapseButton: UIButton! @IBOutlet weak var contentLabel: StatusContentLabel! - @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var reblogLabel: UILabel! - @IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var attachmentsView: AttachmentsContainerView! @IBOutlet weak var replyButton: UIButton! @IBOutlet weak var favoriteButton: UIButton! @IBOutlet weak var reblogButton: UIButton! @IBOutlet weak var moreButton: UIButton! - @IBOutlet weak var pinImageView: UIImageView! var statusID: String! var accountID: String! - var reblogStatusID: String? - var rebloggerID: String? - - var favorited: Bool = false { + + var favorited = false { didSet { favoriteButton.tintColor = favorited ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor } } - var reblogged: Bool = false { + var reblogged = false { didSet { reblogButton.tintColor = reblogged ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor } } - var showPinned: Bool = false var collapsible = false { didSet { @@ -67,36 +54,36 @@ class StatusTableViewCell: UITableViewCell, StatusCell { } } var collapsed = false - var showStatusAutomatically = false var avatarURL: URL? - var updateTimestampWorkItem: DispatchWorkItem? var attachmentDataTasks: [URLSessionDataTask] = [] - var statusUpdater: Cancellable? - var accountUpdater: Cancellable? - var rebloggerAccountUpdater: Cancellable? - + private var statusUpdater: Cancellable? + private var accountUpdater: Cancellable? + deinit { statusUpdater?.cancel() accountUpdater?.cancel() - rebloggerAccountUpdater?.cancel() } override func awakeFromNib() { + super.awakeFromNib() + displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) - reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed))) avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) + avatarImageView.layer.masksToBounds = true + attachmentsView.delegate = self attachmentsView.layer.cornerRadius = 5 attachmentsView.layer.masksToBounds = true + collapseButton.layer.masksToBounds = true collapseButton.layer.cornerRadius = 5 - accessibilityElements = [reblogLabel!, displayNameLabel!, contentWarningLabel!, collapseButton!, contentLabel!, attachmentsView!] + accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentLabel!, attachmentsView!] attachmentsView.isAccessibilityElement = true NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) @@ -110,70 +97,45 @@ class StatusTableViewCell: UITableViewCell, StatusCell { .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: updateRebloggerLabel(reblogger:)) } - + func updateUI(statusID: String) { - guard var status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } + guard let status = MastodonCache.status(for: statusID) else { + fatalError("Missing cached status") + } self.statusID = statusID - if let rebloggedStatusID = status.reblog?.id, - let rebloggedStatus = MastodonCache.status(for: rebloggedStatusID) { - reblogStatusID = statusID - rebloggerID = status.account.id - status = rebloggedStatus - self.statusID = rebloggedStatus.id - reblogLabel.isHidden = false - } else { - reblogStatusID = nil - rebloggerID = nil - reblogLabel.isHidden = true - } let account = status.account self.accountID = account.id updateUI(account: account) - + updateUIForPreferences() - - updateTimestamp() - + attachmentsView.updateUI(status: status) attachmentsView.isAccessibilityElement = status.attachments.count > 0 attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count) - - let realStatus = status.reblog ?? status - updateStatusState(status: realStatus) - - contentLabel.statusID = status.id + + updateStatusState(status: status) + + contentLabel.statusID = statusID collapsible = !status.spoilerText.isEmpty var shouldCollapse = collapsible contentWarningLabel.text = status.spoilerText contentWarningLabel.isHidden = status.spoilerText.isEmpty - if !shouldCollapse, let text = contentLabel.text, text.count > 500 { collapsible = true shouldCollapse = true } - if collapsible && showStatusAutomatically { shouldCollapse = false } - setCollapsed(shouldCollapse, animated: false) - - let pinned = status.pinned ?? false - pinImageView.isHidden = !(pinned && showPinned) - timestampLabel.isHidden = !pinImageView.isHidden } - private func updateStatusState(status: Status) { + func updateStatusState(status: Status) { favorited = status.favourited ?? false reblogged = status.reblogged ?? false @@ -189,89 +151,47 @@ class StatusTableViewCell: UITableViewCell, StatusCell { } } - private func updateUI(account: Account) { + func updateUI(account: Account) { usernameLabel.text = "@\(account.acct)" avatarImageView.image = nil avatarURL = account.avatar ImageCache.avatars.get(account.avatar) { (data) in - guard let data = data else { return } + guard let data = data, self.avatarURL == account.avatar else { return } DispatchQueue.main.async { self.avatarImageView.image = UIImage(data: data) self.avatarURL = nil } } } - + @objc func updateUIForPreferences() { - guard let account = MastodonCache.account(for: accountID) else { fatalError("") } + guard let account = MastodonCache.account(for: accountID) else { return } avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) - if let rebloggerID = rebloggerID, - let reblogger = MastodonCache.account(for: rebloggerID) { - updateRebloggerLabel(reblogger: reblogger) - } displayNameLabel.text = account.realDisplayName } - func updateRebloggerLabel(reblogger: Account) { - reblogLabel.text = "Reblogged by \(reblogger.realDisplayName)" - } - - func updateTimestamp() { - guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } - - timestampLabel.text = status.createdAt.timeAgoString() - timestampLabel.accessibilityLabel = StatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date()) - - let delay: DispatchTimeInterval? - switch status.createdAt.timeAgo().1 { - case .second: - delay = .seconds(10) - case .minute: - delay = .seconds(60) - default: - delay = nil - } - if let delay = delay { - updateTimestampWorkItem = DispatchWorkItem { - self.updateTimestamp() - } - DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: updateTimestampWorkItem!) - } else { - updateTimestampWorkItem = nil - } - } - override func prepareForReuse() { - if let url = avatarURL { - ImageCache.avatars.cancel(url) + super.prepareForReuse() + + if let avatarURL = avatarURL { + ImageCache.avatars.cancel(avatarURL) } - updateTimestampWorkItem?.cancel() - updateTimestampWorkItem = nil attachmentsView.attachmentViews.allObjects.forEach { $0.removeFromSuperview() } showStatusAutomatically = false - showPinned = false } - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - if selected { - delegate?.selected(status: statusID) - } - } - - @IBAction func collapseButtonPressed(_ sender: Any) { + @IBAction func collapseButtonPressed() { setCollapsed(!collapsed, animated: true) delegate?.statusCollapsedStateChanged() } func setCollapsed(_ collapsed: Bool, animated: Bool) { self.collapsed = collapsed - + contentLabel.isHidden = collapsed attachmentsView.isHidden = attachmentsView.attachments.count == 0 || collapsed - - let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up") + + let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")! if animated, let buttonImageView = collapseButton.imageView { // we need to use a keyframe animation for this, because we want to control the direction the chevron rotates @@ -297,22 +217,14 @@ class StatusTableViewCell: UITableViewCell, StatusCell { } else { collapseButton.accessibilityLabel = NSLocalizedString("Collapse Status", comment: "collapse status button accessibility label") } + } - @IBAction func replyPressed(_ sender: Any) { + @IBAction func replyPressed() { delegate?.reply(to: statusID) } - @objc func accountPressed() { - delegate?.selected(account: accountID) - } - - @objc func reblogLabelPressed() { - guard let rebloggerID = rebloggerID else { return } - delegate?.selected(account: rebloggerID) - } - - @IBAction func favoritePressed(_ sender: Any) { + @IBAction func favoritePressed() { guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } let oldValue = favorited @@ -337,7 +249,7 @@ class StatusTableViewCell: UITableViewCell, StatusCell { } } - @IBAction func reblogPressed(_ sender: Any) { + @IBAction func reblogPressed() { guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } let oldValue = reblogged @@ -361,94 +273,20 @@ class StatusTableViewCell: UITableViewCell, StatusCell { } } - @IBAction func morePressed(_ sender: Any) { + @IBAction func morePressed() { delegate?.showMoreOptions(forStatus: statusID) } -} - -extension StatusTableViewCell: TableViewSwipeActionProvider { - - func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { - guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } - - let favoriteTitle: String - let favoriteRequest: Request - let favoriteColor: UIColor - if status.favourited ?? false { - favoriteTitle = "Unfavorite" - favoriteRequest = Status.unfavourite(status) - favoriteColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) - - } else { - favoriteTitle = "Favorite" - favoriteRequest = Status.favourite(status) - favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1) - } - let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in - MastodonController.client.run(favoriteRequest, completion: { response in - DispatchQueue.main.async { - guard case let .success(status, _) = response else { - completion(false) - return - } - completion(true) - MastodonCache.add(status: status) - } - }) - } - favorite.image = UIImage(systemName: "star.fill") - favorite.backgroundColor = favoriteColor - - let reblogTitle: String - let reblogRequest: Request - let reblogColor: UIColor - if status.reblogged ?? false { - reblogTitle = "Unreblog" - reblogRequest = Status.unreblog(status) - reblogColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) - } else { - reblogTitle = "Reblog" - reblogRequest = Status.reblog(status) - reblogColor = tintColor - } - let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in - MastodonController.client.run(reblogRequest, completion: { response in - DispatchQueue.main.async { - guard case let .success(status, _) = response else { - completion(false) - return - } - completion(true) - MastodonCache.add(status: status) - } - }) - } - reblog.image = UIImage(systemName: "repeat") - reblog.backgroundColor = reblogColor - - return UISwipeActionsConfiguration(actions: [favorite, reblog]) + @objc func accountPressed() { + delegate?.selected(account: accountID) } - func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { - let reply = UIContextualAction(style: .normal, title: "Reply") { (action, view, completion) in - completion(true) - self.delegate?.reply(to: self.statusID) - } - reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill") - reply.backgroundColor = tintColor - let more = UIContextualAction(style: .normal, title: "More") { (action, view, completion) in - completion(true) - self.delegate?.showMoreOptions(forStatus: self.statusID) - } - more.image = UIImage(systemName: "ellipsis") - more.backgroundColor = .gray - return UISwipeActionsConfiguration(actions: [reply, more]) + func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { + return nil } - } -extension StatusTableViewCell: AttachmentViewDelegate { +extension BaseStatusTableViewCell: AttachmentViewDelegate { func showAttachmentsGallery(startingAt index: Int) { guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:)) @@ -456,14 +294,13 @@ extension StatusTableViewCell: AttachmentViewDelegate { } } -extension StatusTableViewCell: MenuPreviewProvider { - +extension BaseStatusTableViewCell: MenuPreviewProvider { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { if avatarImageView.frame.contains(location) { - return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) }) + return (content: { ProfileTableViewController(accountID: self.accountID)}, actions: { self.actionsForProfile(accountID: self.accountID) }) } else if attachmentsView.frame.contains(location) { let attachmentsViewLocation = attachmentsView.convert(location, from: self) - if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView, + if let attachmentView = attachmentsView.attachmentViews.allObjects.first(where: { $0.frame.contains(attachmentsViewLocation) }), let image = attachmentView.image { let description = attachmentView.attachment.description return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] }) @@ -484,7 +321,6 @@ extension StatusTableViewCell: MenuPreviewProvider { } ) } - return (content: { ConversationTableViewController(for: self.statusID) }, actions: { self.actionsForStatus(statusID: self.statusID) }) + return self.getStatusCellPreviewProviders(for: location, sourceViewController: sourceViewController) } - } diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift index 80ed1e9a..2e3a62dd 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift @@ -10,7 +10,7 @@ import UIKit import Combine import Pachyderm -class ConversationMainStatusTableViewCell: UITableViewCell, StatusCell { +class ConversationMainStatusTableViewCell: BaseStatusTableViewCell { static let dateFormatter: DateFormatter = { let formatter = DateFormatter() @@ -19,285 +19,55 @@ class ConversationMainStatusTableViewCell: UITableViewCell, StatusCell { return formatter }() - var delegate: StatusTableViewCellDelegate? { - didSet { - contentLabel.navigationDelegate = delegate - } - } - + @IBOutlet weak var profileDetailContainerView: UIView! - @IBOutlet weak var avatarImageView: UIImageView! - @IBOutlet weak var displayNameLabel: UILabel! - @IBOutlet weak var usernameLabel: UILabel! - @IBOutlet weak var contentWarningLabel: UILabel! - @IBOutlet weak var collapseButton: UIButton! - @IBOutlet weak var contentLabel: StatusContentLabel! @IBOutlet weak var favoriteAndReblogCountStackView: UIStackView! @IBOutlet weak var totalFavoritesButton: UIButton! @IBOutlet weak var totalReblogsButton: UIButton! @IBOutlet weak var timestampAndClientLabel: UILabel! - @IBOutlet weak var attachmentsView: AttachmentsContainerView! - @IBOutlet weak var replyButton: UIButton! - @IBOutlet weak var favoriteButton: UIButton! - @IBOutlet weak var reblogButton: UIButton! - @IBOutlet weak var moreButton: UIButton! - + var profileAccessibilityElement: UIAccessibilityElement! - var statusID: String! - var accountID: String! - - var favorited: Bool = false { - didSet { - DispatchQueue.main.async { - self.favoriteButton.tintColor = self.favorited ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : self.tintColor - } - } - } - var reblogged: Bool = false { - didSet { - DispatchQueue.main.async { - self.reblogButton.tintColor = self.reblogged ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : self.tintColor - } - } - } - - var collapsible = false { - didSet { - collapseButton.isHidden = !collapsible - } - } - var collapsed = false - - var showStatusAutomatically = false - - var avatarURL: URL? - - 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))) - avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed))) - avatarImageView.layer.masksToBounds = true - attachmentsView.delegate = self - attachmentsView.layer.cornerRadius = 5 - attachmentsView.layer.masksToBounds = true - collapseButton.layer.masksToBounds = true - collapseButton.layer.cornerRadius = 5 - - NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil) - + super.awakeFromNib() + profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self) profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self) accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentLabel!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!] - - 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 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 - } else { - account = status.account - } - self.accountID = account.id - updateUI(account: account) - updateUIForPreferences() - + override func updateUI(statusID: String) { + super.updateUI(statusID: statusID) + guard let status = MastodonCache.status(for: statusID) else { fatalError() } + var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt) if let application = status.application { timestampAndClientText += " • \(application.name)" } timestampAndClientLabel.text = timestampAndClientText - - attachmentsView.updateUI(status: status) - - let realStatus = status.reblog ?? status - updateStatusState(status: realStatus) - - contentLabel.statusID = statusID - - collapsible = !status.spoilerText.isEmpty - var shouldCollapse = collapsible - contentWarningLabel.text = status.spoilerText - contentWarningLabel.isHidden = status.spoilerText.isEmpty - - if collapsible && showStatusAutomatically { - shouldCollapse = false - } - - setCollapsed(shouldCollapse, animated: false) } - private func updateStatusState(status: Status) { - favorited = status.favourited ?? false - reblogged = status.reblogged ?? false - - if favorited { - favoriteButton.accessibilityLabel = NSLocalizedString("Undo Favorite", comment: "undo favorite button accessibility label") - } else { - favoriteButton.accessibilityLabel = NSLocalizedString("Favorite", comment: "favorite button accessibility label") - } - if reblogged { - reblogButton.accessibilityLabel = NSLocalizedString("Undo Reblog", comment: "undo reblog button accessibility label") - } else { - reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label") - } - + override func updateStatusState(status: Status) { + super.updateStatusState(status: status) + // todo: localize me totalFavoritesButton.setTitle("\(status.favouritesCount) Favorite\(status.favouritesCount == 1 ? "" : "s")", for: .normal) totalReblogsButton.setTitle("\(status.reblogsCount) Reblog\(status.reblogsCount == 1 ? "" : "s")", for: .normal) } - private func updateUI(account: Account) { - usernameLabel.text = "@\(account.acct)" - avatarImageView.image = nil - avatarURL = account.avatar - ImageCache.avatars.get(account.avatar) { (data) in - guard let data = data else { return } - DispatchQueue.main.async { - self.avatarImageView.image = UIImage(data: data) - self.avatarURL = nil - } - } - + override func updateUI(account: Account) { + super.updateUI(account: account) + profileAccessibilityElement.accessibilityLabel = account.realDisplayName } - @objc func updateUIForPreferences() { - guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } + @objc override func updateUIForPreferences() { + super.updateUIForPreferences() - avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) - displayNameLabel.text = account.realDisplayName favoriteAndReblogCountStackView.isHidden = !Preferences.shared.showFavoriteAndReblogCounts } - override func prepareForReuse() { - if let url = avatarURL { - ImageCache.avatars.cancel(url) - } - attachmentsView.subviews.forEach { $0.removeFromSuperview() } - showStatusAutomatically = false - } - - @objc func accountPressed() { - delegate?.selected(account: accountID) - } - - @IBAction func collapsePressed(_ sender: Any) { - setCollapsed(!collapsed, animated: true) - delegate?.statusCollapsedStateChanged() - } - - func setCollapsed(_ collapsed: Bool, animated: Bool) { - self.collapsed = collapsed - - contentLabel.isHidden = collapsed - attachmentsView.isHidden = attachmentsView.attachments.count == 0 || collapsed - - let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up") - - if animated, let buttonImageView = collapseButton.imageView { - // see comment in StatusTableViewCell.setCollapsed - UIView.animateKeyframes(withDuration: 0.2, delay: 0, options: .calculationModeLinear, animations: { - UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { - buttonImageView.transform = CGAffineTransform(rotationAngle: collapsed ? .pi / 2 : -.pi / 2) - } - UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) { - buttonImageView.transform = CGAffineTransform(rotationAngle: .pi) - } - }, completion: { (finished) in - buttonImageView.transform = .identity - self.collapseButton.setImage(buttonImage, for: .normal) - }) - } else { - collapseButton.setImage(buttonImage, for: .normal) - } - - - if collapsed { - collapseButton.accessibilityLabel = NSLocalizedString("Expand Status", comment: "expand status button accessibility label") - } else { - collapseButton.accessibilityLabel = NSLocalizedString("Collapse Status", comment: "collapse status button accessibility label") - } - } - - @IBAction func replyPressed(_ sender: Any) { - delegate?.reply(to: statusID) - } - - @IBAction func favoritePressed(_ sender: Any) { - guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } - - favorited = !favorited - - let realStatus: Status = status.reblog ?? status - - let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus) - MastodonController.client.run(request) { response in - DispatchQueue.main.async { - if case let .success(newStatus, _) = response { - self.favorited = newStatus.favourited ?? false - MastodonCache.add(status: newStatus) - UIImpactFeedbackGenerator(style: .light).impactOccurred() - } else { - print("Couldn't favorite status \(realStatus.id)") - // todo: display error message - UINotificationFeedbackGenerator().notificationOccurred(.error) - return - } - } - } - } - - @IBAction func reblogPressed(_ sender: Any) { - guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } - - reblogged = !reblogged - - let realStatus: Status = status.reblog ?? status - - let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus) - MastodonController.client.run(request) { response in - DispatchQueue.main.async { - if case let .success(newStatus, _) = response { - self.reblogged = newStatus.reblogged ?? false - MastodonCache.add(status: newStatus) - UIImpactFeedbackGenerator(style: .light).impactOccurred() - } else { - print("Couldn't reblog status \(realStatus.id)") - // todo: display error message - UINotificationFeedbackGenerator().notificationOccurred(.error) - } - } - } - } - - @IBAction func morePressed(_ sender: Any) { - delegate?.showMoreOptions(forStatus: statusID) - } - - @IBAction func totalFavoritesPressed(_ sender: Any) { + @IBAction func totalFavoritesPressed() { if let delegate = delegate { // accounts aren't known, pass nil so the VC will load them let vc = delegate.statusActionAccountList(action: .favorite, statusID: statusID, accountIDs: nil) @@ -306,7 +76,7 @@ class ConversationMainStatusTableViewCell: UITableViewCell, StatusCell { } } - @IBAction func totalReblogsPressed(_ sender: Any) { + @IBAction func totalReblogsPressed() { if let delegate = delegate { // accounts aren't known, pass nil so the VC will load them let vc = delegate.statusActionAccountList(action: .reblog, statusID: statusID, accountIDs: nil) @@ -315,42 +85,3 @@ class ConversationMainStatusTableViewCell: UITableViewCell, StatusCell { } } } - -extension ConversationMainStatusTableViewCell: AttachmentViewDelegate { - func showAttachmentsGallery(startingAt index: Int) { - guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } - let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:)) - delegate?.showGallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index) - } -} - -extension ConversationMainStatusTableViewCell: MenuPreviewProvider { - func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { - if avatarImageView.frame.contains(location) { - return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) }) - } else if attachmentsView.frame.contains(location) { - let attachmentsViewLocation = attachmentsView.convert(location, from: self) - if let attachmentView = attachmentsView.subviews.first(where: { $0.frame.contains(attachmentsViewLocation) }) as? AttachmentView { - let image = attachmentView.image! - let description = attachmentView.attachment.description - return (content: { self.delegate?.largeImage(image, description: description, sourceView: attachmentView) }, actions: { [] }) - } - } else if contentLabel.frame.contains(location), - let link = contentLabel.getLink(atPoint: contentLabel.convert(location, from: self)) { - return ( - content: { self.contentLabel.getViewController(forLink: link.url, inRange: link.range) }, - actions: { - let text = (self.contentLabel.text! as NSString).substring(with: link.range) - if let mention = self.contentLabel.getMention(for: link.url, text: text) { - return self.actionsForProfile(accountID: mention.id) - } else if let hashtag = self.contentLabel.getHashtag(for: link.url, text: text) { - return self.actionsForHashtag(hashtag) - } else { - return self.actionsForURL(link.url) - } - } - ) - } - return nil - } -} diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib index 5530e6b1..93112ca2 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -72,7 +72,7 @@ - +