diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift
index 2b5ccfc9..9b7117fe 100644
--- a/Tusker/Screens/Conversation/ConversationTableViewController.swift
+++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift
@@ -119,7 +119,13 @@ class ConversationTableViewController: EnhancedTableViewController {
}
-extension ConversationTableViewController: StatusTableViewCellDelegate {}
+extension ConversationTableViewController: StatusTableViewCellDelegate {
+ func statusCollapsedStateChanged() {
+ // causes the table view to recalculate the cell heights
+ tableView.beginUpdates()
+ tableView.endUpdates()
+ }
+}
extension ConversationTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
diff --git a/Tusker/Screens/Notifications/NotificationsTableViewController.swift b/Tusker/Screens/Notifications/NotificationsTableViewController.swift
index 6b1399da..36cd5bb9 100644
--- a/Tusker/Screens/Notifications/NotificationsTableViewController.swift
+++ b/Tusker/Screens/Notifications/NotificationsTableViewController.swift
@@ -173,7 +173,13 @@ class NotificationsTableViewController: EnhancedTableViewController {
}
-extension NotificationsTableViewController: StatusTableViewCellDelegate {}
+extension NotificationsTableViewController: StatusTableViewCellDelegate {
+ func statusCollapsedStateChanged() {
+ // causes the table view to recalculate the cell heights
+ tableView.beginUpdates()
+ tableView.endUpdates()
+ }
+}
extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift
index 9f106612..c8264728 100644
--- a/Tusker/Screens/Profile/ProfileTableViewController.swift
+++ b/Tusker/Screens/Profile/ProfileTableViewController.swift
@@ -205,7 +205,14 @@ class ProfileTableViewController: EnhancedTableViewController {
}
-extension ProfileTableViewController: StatusTableViewCellDelegate {}
+extension ProfileTableViewController: StatusTableViewCellDelegate {
+ func statusCollapsedStateChanged() {
+ // causes the table view to recalculate the cell heights
+ tableView.beginUpdates()
+ tableView.endUpdates()
+ }
+}
+
extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
func showMoreOptions() {
let account = MastodonCache.account(for: accountID)!
diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
index 1a38cb7a..ade0d579 100644
--- a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
+++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift
@@ -134,4 +134,10 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
}
-extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {}
+extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
+ func statusCollapsedStateChanged() {
+ // causes the table view to recalculate the cell heights
+ tableView.beginUpdates()
+ tableView.endUpdates()
+ }
+}
diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift
index 04da25ae..1c482278 100644
--- a/Tusker/Screens/Timeline/TimelineTableViewController.swift
+++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift
@@ -151,7 +151,13 @@ class TimelineTableViewController: EnhancedTableViewController {
}
-extension TimelineTableViewController: StatusTableViewCellDelegate {}
+extension TimelineTableViewController: StatusTableViewCellDelegate {
+ func statusCollapsedStateChanged() {
+ // causes the table view to recalculate the cell heights
+ tableView.beginUpdates()
+ tableView.endUpdates()
+ }
+}
extension TimelineTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift
index 32abc286..243b7501 100644
--- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift
+++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift
@@ -27,6 +27,8 @@ class ConversationMainStatusTableViewCell: UITableViewCell {
@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 totalFavoritesButton: UIButton!
@@ -54,6 +56,13 @@ class ConversationMainStatusTableViewCell: UITableViewCell {
}
}
+ var collapsible = false {
+ didSet {
+ collapseButton.isHidden = !collapsible
+ }
+ }
+ var collapsed = false
+
var avatarURL: URL?
var statusUpdater: Cancellable?
@@ -72,6 +81,8 @@ class ConversationMainStatusTableViewCell: UITableViewCell {
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)
@@ -107,13 +118,17 @@ class ConversationMainStatusTableViewCell: UITableViewCell {
}
timestampAndClientLabel.text = timestampAndClientText
-
attachmentsView.updateUI(status: status)
let realStatus = status.reblog ?? status
updateStatusState(status: realStatus)
contentLabel.statusID = statusID
+
+ collapsible = !status.spoilerText.isEmpty
+ setCollapsed(collapsible, animated: false)
+ contentWarningLabel.text = status.spoilerText
+ contentWarningLabel.isHidden = status.spoilerText.isEmpty
}
private func updateStatusState(status: Status) {
@@ -156,6 +171,37 @@ class ConversationMainStatusTableViewCell: UITableViewCell {
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)
+ }
+ }
+
@IBAction func replyPressed(_ sender: Any) {
delegate?.reply(to: statusID)
}
diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib
index 466d65fa..32eb227a 100644
--- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib
+++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib
@@ -10,14 +10,14 @@
-
+
-
+
-
+
@@ -38,45 +38,62 @@
-
-
-
-
+
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
+
diff --git a/Tusker/Views/Status/StatusTableViewCell.swift b/Tusker/Views/Status/StatusTableViewCell.swift
index 1fc3d6da..59ed1b6d 100644
--- a/Tusker/Views/Status/StatusTableViewCell.swift
+++ b/Tusker/Views/Status/StatusTableViewCell.swift
@@ -10,7 +10,8 @@ import UIKit
import Combine
import Pachyderm
-protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
+protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
+ func statusCollapsedStateChanged()
}
class StatusTableViewCell: UITableViewCell {
@@ -23,6 +24,8 @@ class StatusTableViewCell: UITableViewCell {
@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!
@@ -47,6 +50,13 @@ class StatusTableViewCell: UITableViewCell {
}
}
+ var collapsible = false {
+ didSet {
+ collapseButton.isHidden = !collapsible
+ }
+ }
+ var collapsed = false
+
var avatarURL: URL?
var updateTimestampWorkItem: DispatchWorkItem?
var attachmentDataTasks: [URLSessionDataTask] = []
@@ -70,6 +80,8 @@ class StatusTableViewCell: UITableViewCell {
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)
@@ -122,6 +134,11 @@ class StatusTableViewCell: UITableViewCell {
updateStatusState(status: realStatus)
contentLabel.statusID = status.id
+
+ collapsible = !status.spoilerText.isEmpty
+ setCollapsed(collapsible, animated: false)
+ contentWarningLabel.text = status.spoilerText
+ contentWarningLabel.isHidden = status.spoilerText.isEmpty
}
private func updateStatusState(status: Status) {
@@ -192,6 +209,39 @@ class StatusTableViewCell: UITableViewCell {
}
}
+ @IBAction func collapseButtonPressed(_ 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 {
+ // we need to use a keyframe animation for this, because we want to control the direction the chevron rotates
+ // when rotating ±π, UIKit will always rotate in the same direction
+ // using a keyframe to set an intermediate point in the animation allows us to force a specific direction
+ 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)
+ }
+ }
+
@IBAction func replyPressed(_ sender: Any) {
delegate?.reply(to: statusID)
}
diff --git a/Tusker/Views/Status/StatusTableViewCell.xib b/Tusker/Views/Status/StatusTableViewCell.xib
index 04fea672..f612e2fc 100644
--- a/Tusker/Views/Status/StatusTableViewCell.xib
+++ b/Tusker/Views/Status/StatusTableViewCell.xib
@@ -10,20 +10,20 @@
-
+
-
+
-
-
+
@@ -138,7 +163,9 @@
+
+
@@ -146,11 +173,12 @@
-
+
+