forked from shadowfacts/Tusker
Unify most of TimelineStatus and ConverastionMainStatus cell code
Closes #54
This commit is contained in:
parent
8fb3b211b6
commit
24a1e7ceb9
|
@ -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 = "<group>"; };
|
||||
D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D60BAFB72383921D00EED893 /* StatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCell.swift; sourceTree = "<group>"; };
|
||||
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
|
||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -372,7 +372,7 @@
|
|||
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; sourceTree = "<group>"; };
|
||||
D667383B23299340000A2373 /* InstanceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceType.swift; sourceTree = "<group>"; };
|
||||
D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; };
|
||||
D667E5E02134937B0057A976 /* StatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineStatusTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D667E5E621349D4C0057A976 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = "<group>"; };
|
||||
D667E5E821349EE50057A976 /* ProfileHeaderTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileHeaderTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D667E5EA21349EF80057A976 /* ProfileHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -439,7 +439,7 @@
|
|||
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
||||
D6BC9DD9232D8BE5002CA326 /* SearchTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTableViewController.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentLabel.swift; sourceTree = "<group>"; };
|
||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -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 = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
//
|
||||
// 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 {
|
||||
|
||||
static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.dateTimeStyle = .numeric
|
||||
formatter.unitsStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
class BaseStatusTableViewCell: UITableViewCell {
|
||||
|
||||
var delegate: StatusTableViewCellDelegate? {
|
||||
didSet {
|
||||
|
@ -29,37 +22,31 @@ class StatusTableViewCell: UITableViewCell, StatusCell {
|
|||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
updateStatusState(status: status)
|
||||
|
||||
contentLabel.statusID = status.id
|
||||
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,12 +151,12 @@ 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
|
||||
|
@ -203,64 +165,22 @@ class StatusTableViewCell: UITableViewCell, StatusCell {
|
|||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
|
@ -271,7 +191,7 @@ class StatusTableViewCell: UITableViewCell, StatusCell {
|
|||
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)
|
||||
}
|
||||
|
||||
@objc func accountPressed() {
|
||||
delegate?.selected(account: accountID)
|
||||
}
|
||||
|
||||
func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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<Status>
|
||||
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<Status>
|
||||
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])
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -72,7 +72,7 @@
|
|||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="collapsePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="w9d-kB-EaQ"/>
|
||||
<action selector="collapseButtonPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="2Jy-L1-lN6"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="251" verticalHuggingPriority="249" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TgY-hs-Klo" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
|
@ -107,7 +107,7 @@
|
|||
<color key="titleColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="totalFavoritesPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="cj1-BB-TuR"/>
|
||||
<action selector="totalFavoritesPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="D3Y-YB-bqP"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dem-vG-cPB">
|
||||
|
@ -119,7 +119,7 @@
|
|||
<color key="titleColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="totalReblogsPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="duG-bV-hcI"/>
|
||||
<action selector="totalReblogsPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="WG3-nQ-jgr"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
|
@ -148,7 +148,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Reply"/>
|
||||
<state key="normal" image="arrowshape.turn.up.left.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="replyPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="hsh-gx-Swo"/>
|
||||
<action selector="replyPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="RxZ-zv-lkN"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DhN-rJ-jdA">
|
||||
|
@ -156,7 +156,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Favorite"/>
|
||||
<state key="normal" image="star.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="favoritePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="Hkh-Zo-9Qu"/>
|
||||
<action selector="favoritePressed" destination="iN0-l3-epB" eventType="touchUpInside" id="NCA-iR-VMt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GUG-f7-Hdy">
|
||||
|
@ -164,7 +164,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Reblog"/>
|
||||
<state key="normal" image="repeat" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="reblogPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="SAf-RN-q8N"/>
|
||||
<action selector="reblogPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="iIu-Vv-U0I"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ujo-Ap-dmK">
|
||||
|
@ -172,7 +172,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="More Actions"/>
|
||||
<state key="normal" image="ellipsis" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="morePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="dWb-67-CoL"/>
|
||||
<action selector="morePressed" destination="iN0-l3-epB" eventType="touchUpInside" id="1vn-0k-gYi"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// StatusCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 11/18/19.
|
||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol StatusCell {
|
||||
var collapsible: Bool { get }
|
||||
var showStatusAutomatically: Bool { get set }
|
||||
|
||||
func setCollapsed(_ collapsed: Bool, animated: Bool)
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
//
|
||||
// TimelineStatusTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/16/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import Pachyderm
|
||||
|
||||
class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||
|
||||
static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.dateTimeStyle = .numeric
|
||||
formatter.unitsStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
|
||||
@IBOutlet weak var reblogLabel: UILabel!
|
||||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var pinImageView: UIImageView!
|
||||
|
||||
var reblogStatusID: String?
|
||||
var rebloggerID: String?
|
||||
|
||||
var showPinned: Bool = false
|
||||
|
||||
var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
var rebloggerAccountUpdater: Cancellable?
|
||||
|
||||
deinit {
|
||||
rebloggerAccountUpdater?.cancel()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
|
||||
|
||||
accessibilityElements!.insert(reblogLabel!, at: 0)
|
||||
|
||||
rebloggerAccountUpdater = MastodonCache.accountSubject
|
||||
.filter { $0.id == self.rebloggerID }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: updateRebloggerLabel(reblogger:))
|
||||
}
|
||||
|
||||
override func updateUI(statusID: String) {
|
||||
guard var status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||
|
||||
let realStatusID: String
|
||||
if let rebloggedStatusID = status.reblog?.id,
|
||||
let rebloggedStatus = MastodonCache.status(for: rebloggedStatusID) {
|
||||
reblogStatusID = statusID
|
||||
rebloggerID = status.account.id
|
||||
status = rebloggedStatus
|
||||
realStatusID = rebloggedStatus.id
|
||||
reblogLabel.isHidden = false
|
||||
} else {
|
||||
reblogStatusID = nil
|
||||
rebloggerID = nil
|
||||
reblogLabel.isHidden = true
|
||||
realStatusID = statusID
|
||||
}
|
||||
|
||||
super.updateUI(statusID: realStatusID)
|
||||
|
||||
updateTimestamp()
|
||||
|
||||
let pinned = status.pinned ?? false
|
||||
pinImageView.isHidden = !(pinned && showPinned)
|
||||
timestampLabel.isHidden = !pinImageView.isHidden
|
||||
}
|
||||
|
||||
@objc override func updateUIForPreferences() {
|
||||
super.updateUIForPreferences()
|
||||
if let rebloggerID = rebloggerID,
|
||||
let reblogger = MastodonCache.account(for: rebloggerID) {
|
||||
updateRebloggerLabel(reblogger: reblogger)
|
||||
}
|
||||
}
|
||||
|
||||
private 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 = TimelineStatusTableViewCell.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() {
|
||||
super.prepareForReuse()
|
||||
updateTimestampWorkItem?.cancel()
|
||||
updateTimestampWorkItem = nil
|
||||
showPinned = false
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
if selected {
|
||||
delegate?.selected(status: statusID)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func reblogLabelPressed() {
|
||||
guard let rebloggerID = rebloggerID else { return }
|
||||
delegate?.selected(account: rebloggerID)
|
||||
}
|
||||
|
||||
override func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? {
|
||||
return (
|
||||
content: { ConversationTableViewController(for: self.statusID) },
|
||||
actions: { self.actionsForStatus(statusID: self.statusID) }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||
|
||||
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
|
||||
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||
|
||||
let favoriteTitle: String
|
||||
let favoriteRequest: Request<Status>
|
||||
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<Status>
|
||||
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])
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="StatusTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="TimelineStatusTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="240"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
|
@ -102,7 +102,7 @@
|
|||
<preferredSymbolConfiguration key="preferredSymbolConfiguration" scale="large"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="collapseButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="HNS-rX-gBM"/>
|
||||
<action selector="collapseButtonPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="JaH-xX-UOD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HrJ-t9-KcD" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||
|
@ -139,7 +139,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Reply"/>
|
||||
<state key="normal" image="arrowshape.turn.up.left.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="replyPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="Ohg-uU-d3Z"/>
|
||||
<action selector="replyPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="ybz-3W-jAa"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x0t-TR-jJ4">
|
||||
|
@ -147,7 +147,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Favorite"/>
|
||||
<state key="normal" image="star.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="favoritePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="gKJ-Hu-za3"/>
|
||||
<action selector="favoritePressed" destination="iN0-l3-epB" eventType="touchUpInside" id="8Q8-Rz-k02"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
|
||||
|
@ -155,7 +155,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="Reblog"/>
|
||||
<state key="normal" image="repeat" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="reblogPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="JQI-VT-wTt"/>
|
||||
<action selector="reblogPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="Wa2-ZA-TBo"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
|
||||
|
@ -163,7 +163,7 @@
|
|||
<accessibility key="accessibilityConfiguration" label="More Actions"/>
|
||||
<state key="normal" image="ellipsis" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="morePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="dcV-Ez-EIe"/>
|
||||
<action selector="morePressed" destination="iN0-l3-epB" eventType="touchUpInside" id="WT4-fi-usq"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
|
@ -186,20 +186,20 @@
|
|||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="attachmentsView" destination="nbq-yr-2mA" id="GkU-Xk-pc0"/>
|
||||
<outlet property="avatarImageView" destination="QMP-j2-HLn" id="CAl-hK-i3j"/>
|
||||
<outlet property="collapseButton" destination="O0E-Vf-XYR" id="fBb-0C-QA2"/>
|
||||
<outlet property="contentLabel" destination="HrJ-t9-KcD" id="tbD-3T-nNP"/>
|
||||
<outlet property="contentWarningLabel" destination="inI-Og-YiU" id="2jf-6J-JUU"/>
|
||||
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="63y-He-xy1"/>
|
||||
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="Ohz-bs-Ebr"/>
|
||||
<outlet property="moreButton" destination="982-J4-NGl" id="Xga-I4-CzK"/>
|
||||
<outlet property="attachmentsView" destination="nbq-yr-2mA" id="SVm-zl-mPb"/>
|
||||
<outlet property="avatarImageView" destination="QMP-j2-HLn" id="xfS-v8-Gzu"/>
|
||||
<outlet property="collapseButton" destination="O0E-Vf-XYR" id="nWd-gg-st8"/>
|
||||
<outlet property="contentLabel" destination="HrJ-t9-KcD" id="s6V-cx-bBt"/>
|
||||
<outlet property="contentWarningLabel" destination="inI-Og-YiU" id="C7a-eK-qcx"/>
|
||||
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="vVS-WM-Wqx"/>
|
||||
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="guV-yz-Lm6"/>
|
||||
<outlet property="moreButton" destination="982-J4-NGl" id="Pux-tL-aWe"/>
|
||||
<outlet property="pinImageView" destination="LRh-Cc-1br" id="9jn-0V-PdJ"/>
|
||||
<outlet property="reblogButton" destination="6tW-z8-Qh9" id="i9h-QA-ZPd"/>
|
||||
<outlet property="reblogButton" destination="6tW-z8-Qh9" id="u2t-8D-kOn"/>
|
||||
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
|
||||
<outlet property="replyButton" destination="rKF-yF-KIa" id="rul-lk-bIR"/>
|
||||
<outlet property="timestampLabel" destination="35d-EA-ReR" id="8EW-mb-LAb"/>
|
||||
<outlet property="usernameLabel" destination="j89-zc-SFa" id="see-Xd-3e9"/>
|
||||
<outlet property="replyButton" destination="rKF-yF-KIa" id="rka-q1-o4a"/>
|
||||
<outlet property="timestampLabel" destination="35d-EA-ReR" id="Ny2-nV-nqP"/>
|
||||
<outlet property="usernameLabel" destination="j89-zc-SFa" id="bXX-FZ-fCp"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="29.600000000000001" y="79.160419790104953"/>
|
||||
</view>
|
Loading…
Reference in New Issue