Store status collapse state in containing view controller

Also, copy the state between screens, so e.g. expanding a status in the
timeline and then opening that conversation already has that status
expanded.

This intentionally doesn't store the sensitive attachment visibility
state, since showing text when not necessary is less dangerous than for
images. (Possibly a preference for this in the future?)

Closes #55
This commit is contained in:
Shadowfacts 2019-11-28 18:36:58 -05:00
parent 24a1e7ceb9
commit b47b08fa95
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
14 changed files with 164 additions and 82 deletions

View File

@ -12,12 +12,18 @@ public class NotificationGroup {
public let notificationIDs: [String]
public let id: String
public let kind: Notification.Kind
public let statusState: StatusState?
init?(notifications: [Notification]) {
guard !notifications.isEmpty else { return nil }
self.notificationIDs = notifications.map { $0.id }
self.id = notifications.first!.id
self.kind = notifications.first!.kind
if kind == .mention {
self.statusState = .unknown
} else {
self.statusState = nil
}
}
public static func createGroups(notifications: [Notification], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {

View File

@ -0,0 +1,40 @@
//
// StatusState.swift
// Pachyderm
//
// Created by Shadowfacts on 11/24/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import Foundation
public class StatusState: Equatable, Hashable {
public var collapsible: Bool?
public var collapsed: Bool?
public var unknown: Bool {
collapsible == nil || collapsed == nil
}
public init(collapsible: Bool?, collapsed: Bool?) {
self.collapsible = collapsible
self.collapsed = collapsed
}
public func copy() -> StatusState {
return StatusState(collapsible: self.collapsible, collapsed: self.collapsed)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(collapsible)
hasher.combine(collapsed)
}
public static var unknown: StatusState {
StatusState(collapsible: nil, collapsed: nil)
}
public static func == (lhs: StatusState, rhs: StatusState) -> Bool {
lhs.collapsible == rhs.collapsible && lhs.collapsed == rhs.collapsed
}
}

View File

@ -90,6 +90,7 @@
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */ = {isa = PBXBuildFile; fileRef = D640D76822BAF5E6004FBE69 /* DomainBlocks.plist */; };
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D641C772213CAA25004B4513 /* NotificationsTableViewController.swift */; };
@ -285,6 +286,7 @@
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
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>"; };
@ -1000,6 +1002,7 @@
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
isa = PBXGroup;
children = (
D60A4FFB238B726A008AC647 /* StatusState.swift */,
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */,
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */,
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */,
@ -1534,6 +1537,7 @@
D61099F5214568C300432DC2 /* Notification.swift in Sources */,
D61099EF214566C000432DC2 /* Instance.swift in Sources */,
D61099D22144B2E600432DC2 /* Body.swift in Sources */,
D63569E023908A8D003DD353 /* StatusState.swift in Sources */,
D6109A0121456B0800432DC2 /* Hashtag.swift in Sources */,
D61099FD21456A1D00432DC2 /* SearchResults.swift in Sources */,
D61099F12145686D00432DC2 /* List.swift in Sources */,

View File

@ -15,8 +15,9 @@ class ConversationTableViewController: EnhancedTableViewController {
static let showPostsImage = UIImage(systemName: "eye.fill")!
static let hidePostsImage = UIImage(systemName: "eye.slash.fill")!
var mainStatusID: String!
var statusIDs: [String] = [] {
let mainStatusID: String
let mainStatusState: StatusState
var statuses: [(id: String, state: StatusState)] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
@ -27,8 +28,9 @@ class ConversationTableViewController: EnhancedTableViewController {
var showStatusesAutomatically = false
var visibilityBarButtonItem: UIBarButtonItem!
init(for mainStatusID: String) {
init(for mainStatusID: String, state: StatusState = .unknown) {
self.mainStatusID = mainStatusID
self.mainStatusState = state
super.init(style: .plain)
}
@ -51,9 +53,9 @@ class ConversationTableViewController: EnhancedTableViewController {
visibilityBarButtonItem = UIBarButtonItem(image: ConversationTableViewController.showPostsImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
navigationItem.rightBarButtonItem = visibilityBarButtonItem
statusIDs = [mainStatusID]
statuses = [(mainStatusID, mainStatusState)]
guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID!)") }
guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID)") }
let request = Status.getContext(mainStatus)
MastodonController.client.run(request) { response in
@ -61,8 +63,8 @@ class ConversationTableViewController: EnhancedTableViewController {
let parents = self.getDirectParents(of: mainStatus, from: context.ancestors)
MastodonCache.addAll(statuses: parents)
MastodonCache.addAll(statuses: context.descendants)
self.statusIDs = parents.map { $0.id } + [self.mainStatusID] + context.descendants.map { $0.id }
let indexPath = IndexPath(row: self.statusIDs.firstIndex(of: self.mainStatusID)!, section: 0)
self.statuses = parents.map { ($0.id, .unknown) } + self.statuses + context.descendants.map { ($0.id, .unknown) }
let indexPath = IndexPath(row: parents.count, section: 0)
DispatchQueue.main.async {
self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
}
@ -89,30 +91,30 @@ class ConversationTableViewController: EnhancedTableViewController {
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return statusIDs.count
return statuses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let statusID = statusIDs[indexPath.row]
let (id, state) = statuses[indexPath.row]
if statusID == mainStatusID {
if id == mainStatusID {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "mainStatusCell", for: indexPath) as? ConversationMainStatusTableViewCell else { fatalError() }
cell.selectionStyle = .none
cell.showStatusAutomatically = showStatusesAutomatically
cell.updateUI(statusID: statusID)
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
cell.showStatusAutomatically = showStatusesAutomatically
cell.updateUI(statusID: statusID)
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
}
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
let statusID = statusIDs[indexPath.row]
let statusID = statuses[indexPath.row].id
return statusID == mainStatusID ? nil : indexPath
}
@ -136,8 +138,13 @@ class ConversationTableViewController: EnhancedTableViewController {
cell.collapsible else { continue }
cell.showStatusAutomatically = showStatusesAutomatically
cell.setCollapsed(!showStatusesAutomatically, animated: false)
let indexPath = tableView.indexPath(for: cell)!
statuses[indexPath.row].state.collapsed = !showStatusesAutomatically
}
statusCollapsedStateChanged()
// recalculate cell heights
tableView.beginUpdates()
tableView.endUpdates()
if showStatusesAutomatically {
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
@ -149,7 +156,7 @@ class ConversationTableViewController: EnhancedTableViewController {
}
extension ConversationTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()
@ -159,7 +166,7 @@ extension ConversationTableViewController: StatusTableViewCellDelegate {
extension ConversationTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard let status = MastodonCache.status(for: statusIDs[indexPath.row]) else { continue }
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
ImageCache.avatars.get(status.account.avatar, completion: nil)
for attachment in status.attachments {
ImageCache.attachments.get(attachment.url, completion: nil)
@ -169,7 +176,7 @@ extension ConversationTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard let status = MastodonCache.status(for: statusIDs[indexPath.row]) else { continue }
guard let status = MastodonCache.status(for: statuses[indexPath.row].id) else { continue }
ImageCache.avatars.cancel(status.account.avatar)
for attachment in status.attachments {
ImageCache.attachments.cancel(attachment.url)

View File

@ -91,7 +91,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else {
fatalError()
}
cell.updateUI(statusID: notification.status!.id)
cell.updateUI(statusID: notification.status!.id, state: group.statusState!)
cell.delegate = self
return cell
@ -212,7 +212,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
}
extension NotificationsTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()

View File

@ -22,14 +22,14 @@ class ProfileTableViewController: EnhancedTableViewController {
}
}
var pinnedStatusIDs: [String] = [] {
var pinnedStatuses: [(id: String, state: StatusState)] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var timelineSegments: [TimelineSegment<Status>] = [] {
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
@ -109,14 +109,14 @@ class ProfileTableViewController: EnhancedTableViewController {
guard case let .success(statuses, _) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses)
self.pinnedStatusIDs = statuses.map { $0.id }
self.pinnedStatuses = statuses.map { ($0.id, .unknown) }
}
getStatuses() { response in
guard case let .success(statuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses)
self.timelineSegments.append(TimelineSegment(objects: statuses))
self.timelineSegments.append(statuses.map { ($0.id, .unknown) })
self.older = pagination?.older
self.newer = pagination?.newer
@ -150,7 +150,7 @@ class ProfileTableViewController: EnhancedTableViewController {
if section == 0 {
return accountID == nil || MastodonCache.account(for: accountID) == nil ? 0 : 1
} else if section == 1 {
return pinnedStatusIDs.count
return pinnedStatuses.count
} else {
return timelineSegments[section - 2].count
}
@ -166,15 +166,15 @@ class ProfileTableViewController: EnhancedTableViewController {
return cell
case 1:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
let statusID = pinnedStatusIDs[indexPath.row]
let (id, state) = pinnedStatuses[indexPath.row]
cell.showPinned = true
cell.updateUI(statusID: statusID)
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
default:
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)
let (id, state) = timelineSegments[indexPath.section - 2][indexPath.row]
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
}
@ -188,7 +188,7 @@ class ProfileTableViewController: EnhancedTableViewController {
guard case let .success(newStatuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: newStatuses)
self.timelineSegments[indexPath.section - 2].append(objects: newStatuses)
self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
self.older = pagination?.older
}
@ -214,7 +214,7 @@ class ProfileTableViewController: EnhancedTableViewController {
guard case let .success(newStatuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: newStatuses)
self.timelineSegments[0].insertAtBeginning(objects: newStatuses)
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
self.newer = pagination?.newer
@ -231,7 +231,7 @@ class ProfileTableViewController: EnhancedTableViewController {
}
extension ProfileTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()
@ -270,7 +270,7 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
extension ProfileTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths where indexPath.section > 1 {
let statusID = timelineSegments[indexPath.section - 2][indexPath.row]
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
guard let status = MastodonCache.status(for: statusID) else { continue }
ImageCache.avatars.get(status.account.avatar, completion: nil)
for attachment in status.attachments {
@ -281,7 +281,7 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths where indexPath.section > 1 {
let statusID = timelineSegments[indexPath.section - 2][indexPath.row]
let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id
guard let status = MastodonCache.status(for: statusID) else { continue }
ImageCache.avatars.cancel(status.account.avatar)
for attachment in status.attachments {

View File

@ -54,9 +54,9 @@ class SearchTableViewController: EnhancedTableViewController {
cell.updateUI(hashtag: tag)
cell.delegate = self
return cell
case let .status(id):
case let .status(id, state):
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as! TimelineStatusTableViewCell
cell.updateUI(statusID: id)
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
}
@ -113,16 +113,16 @@ class SearchTableViewController: EnhancedTableViewController {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
if !results.accounts.isEmpty {
snapshot.appendSections([.accounts])
snapshot.appendItems(results.accounts.map { Item.account($0.id) }, toSection: .accounts)
snapshot.appendItems(results.accounts.map { .account($0.id) }, toSection: .accounts)
MastodonCache.addAll(accounts: results.accounts)
}
if !results.hashtags.isEmpty {
snapshot.appendSections([.hashtags])
snapshot.appendItems(results.hashtags.map { Item.hashtag($0) }, toSection: .hashtags)
snapshot.appendItems(results.hashtags.map { .hashtag($0) }, toSection: .hashtags)
}
if !results.statuses.isEmpty {
snapshot.appendSections([.statuses])
snapshot.appendItems(results.statuses.map { Item.status($0.id) }, toSection: .statuses)
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
MastodonCache.addAll(statuses: results.statuses)
MastodonCache.addAll(accounts: results.statuses.map { $0.account })
}
@ -152,7 +152,7 @@ extension SearchTableViewController {
enum Item: Hashable {
case account(String)
case hashtag(Hashtag)
case status(String)
case status(String, StatusState)
}
class DataSource: UITableViewDiffableDataSource<Section, Item> {
@ -176,7 +176,7 @@ extension SearchTableViewController: UISearchBarDelegate {
}
extension SearchTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates()
tableView.endUpdates()
}

View File

@ -16,6 +16,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
let actionType: ActionType
let statusID: String
var statusState: StatusState
var accountIDs: [String]? {
didSet {
tableView.reloadData()
@ -32,9 +33,10 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
- Parameter statusID The ID of the status to show.
- Parameter accountIDs The accounts that will be shown. If `nil` is passed, a request will be performed to load the accounts.
*/
init(actionType: ActionType, statusID: String, accountIDs: [String]?) {
init(actionType: ActionType, statusID: String, statusState: StatusState, accountIDs: [String]?) {
self.actionType = actionType
self.statusID = statusID
self.statusState = statusState
self.accountIDs = accountIDs
super.init(style: .grouped)
@ -109,7 +111,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
switch indexPath.section {
case 0:
guard let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
cell.updateUI(statusID: statusID)
cell.updateUI(statusID: statusID, state: statusState)
cell.delegate = self
return cell
case 1:
@ -135,7 +137,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
}
extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()

View File

@ -26,7 +26,7 @@ class TimelineTableViewController: EnhancedTableViewController {
var timeline: Timeline!
var timelineSegments: [TimelineSegment<Status>] = [] {
var timelineSegments: [[(id: String, state: StatusState)]] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
@ -56,7 +56,7 @@ class TimelineTableViewController: EnhancedTableViewController {
}
func statusID(for indexPath: IndexPath) -> String {
return timelineSegments[indexPath.section][indexPath.row]
return timelineSegments[indexPath.section][indexPath.row].id
}
override func viewDidLoad() {
@ -74,7 +74,7 @@ class TimelineTableViewController: EnhancedTableViewController {
MastodonController.client.run(request) { response in
guard case let .success(statuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses)
self.timelineSegments.insert(TimelineSegment(objects: statuses), at: 0)
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
self.newer = pagination?.newer
self.older = pagination?.older
}
@ -94,7 +94,8 @@ class TimelineTableViewController: EnhancedTableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? TimelineStatusTableViewCell else { fatalError() }
cell.updateUI(statusID: statusID(for: indexPath))
let (id, state) = timelineSegments[indexPath.section][indexPath.row]
cell.updateUI(statusID: id, state: state)
cell.delegate = self
return cell
@ -112,7 +113,7 @@ class TimelineTableViewController: EnhancedTableViewController {
guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.older = pagination?.older
MastodonCache.addAll(statuses: newStatuses)
self.timelineSegments[self.timelineSegments.count - 1].append(objects: newStatuses)
self.timelineSegments[self.timelineSegments.count - 1].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
}
}
}
@ -137,7 +138,7 @@ class TimelineTableViewController: EnhancedTableViewController {
guard case let .success(newStatuses, pagination) = response else { fatalError() }
self.newer = pagination?.newer
MastodonCache.addAll(statuses: newStatuses)
self.timelineSegments[0].insertAtBeginning(objects: newStatuses)
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
@ -154,7 +155,7 @@ class TimelineTableViewController: EnhancedTableViewController {
}
extension TimelineTableViewController: StatusTableViewCellDelegate {
func statusCollapsedStateChanged() {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()

View File

@ -24,6 +24,8 @@ protocol TuskerNavigationDelegate {
func selected(status statusID: String)
func selected(status statusID: String, state: StatusState)
func compose()
func reply(to statusID: String)
@ -46,7 +48,7 @@ protocol TuskerNavigationDelegate {
func showFollowedByList(accountIDs: [String])
func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, accountIDs: [String]?) -> StatusActionAccountListTableViewController
func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController
}
extension TuskerNavigationDelegate where Self: UIViewController {
@ -96,13 +98,18 @@ extension TuskerNavigationDelegate where Self: UIViewController {
}
func selected(status statusID: String) {
self.selected(status: statusID, state: .unknown)
}
func selected(status statusID: String, state: StatusState) {
// todo: is this necessary? should the conversation main status cell prevent this
// don't open if the conversation is the same as the current one
if let conversationController = self as? ConversationTableViewController,
conversationController.mainStatusID == statusID {
return
}
show(ConversationTableViewController(for: statusID), sender: self)
show(ConversationTableViewController(for: statusID, state: state), sender: self)
}
func compose() {
@ -195,8 +202,8 @@ extension TuskerNavigationDelegate where Self: UIViewController {
show(vc, sender: self)
}
func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, accountIDs: [String]?) -> StatusActionAccountListTableViewController {
return StatusActionAccountListTableViewController(actionType: action, statusID: statusID, accountIDs: accountIDs)
func statusActionAccountList(action: StatusActionAccountListTableViewController.ActionType, statusID: String, statusState state: StatusState, accountIDs: [String]?) -> StatusActionAccountListTableViewController {
return StatusActionAccountListTableViewController(actionType: action, statusID: statusID, statusState: state, accountIDs: accountIDs)
}
}

View File

@ -165,7 +165,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
default:
fatalError()
}
let vc = delegate.statusActionAccountList(action: action, statusID: statusID, accountIDs: accountIDs)
let vc = delegate.statusActionAccountList(action: action, statusID: statusID, statusState: .unknown, accountIDs: accountIDs)
delegate.show(vc)
}
}
@ -187,7 +187,7 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
default:
fatalError()
}
return self.delegate?.statusActionAccountList(action: action, statusID: self.statusID, accountIDs: accountIDs)
return self.delegate?.statusActionAccountList(action: action, statusID: self.statusID, statusState: .unknown, accountIDs: accountIDs)
}, actions: {
return []
})

View File

@ -11,7 +11,7 @@ import Pachyderm
import Combine
protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
func statusCollapsedStateChanged()
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell)
}
class BaseStatusTableViewCell: UITableViewCell {
@ -48,12 +48,18 @@ class BaseStatusTableViewCell: UITableViewCell {
}
}
var statusState: StatusState!
var collapsible = false {
didSet {
collapseButton.isHidden = !collapsible
statusState?.collapsible = collapsible
}
}
var collapsed = false {
didSet {
statusState?.collapsed = collapsed
}
}
var collapsed = false
var showStatusAutomatically = false
var avatarURL: URL?
@ -99,11 +105,12 @@ class BaseStatusTableViewCell: UITableViewCell {
.sink(receiveValue: updateUI(account:))
}
func updateUI(statusID: String) {
func updateUI(statusID: String, state: StatusState) {
guard let status = MastodonCache.status(for: statusID) else {
fatalError("Missing cached status")
}
self.statusID = statusID
self.statusState = state
let account = status.account
self.accountID = account.id
@ -119,20 +126,28 @@ class BaseStatusTableViewCell: UITableViewCell {
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 state.unknown {
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)
state.collapsible = collapsible
state.collapsed = shouldCollapse
} else {
collapsible = state.collapsible!
setCollapsed(state.collapsed!, animated: false)
}
if collapsible && showStatusAutomatically {
shouldCollapse = false
}
setCollapsed(shouldCollapse, animated: false)
}
func updateStatusState(status: Status) {
@ -182,7 +197,7 @@ class BaseStatusTableViewCell: UITableViewCell {
@IBAction func collapseButtonPressed() {
setCollapsed(!collapsed, animated: true)
delegate?.statusCollapsedStateChanged()
delegate?.statusCellCollapsedStateChanged(self)
}
func setCollapsed(_ collapsed: Bool, animated: Bool) {

View File

@ -36,8 +36,8 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentLabel!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
}
override func updateUI(statusID: String) {
super.updateUI(statusID: statusID)
override func updateUI(statusID: String, state: StatusState) {
super.updateUI(statusID: statusID, state: state)
guard let status = MastodonCache.status(for: statusID) else { fatalError() }
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
@ -70,7 +70,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
@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)
let vc = delegate.statusActionAccountList(action: .favorite, statusID: statusID, statusState: statusState.copy(), accountIDs: nil)
vc.showInacurateCountWarning = true
delegate.show(vc)
}
@ -79,7 +79,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
@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)
let vc = delegate.statusActionAccountList(action: .reblog, statusID: statusID, statusState: statusState.copy(), accountIDs: nil)
vc.showInacurateCountWarning = true
delegate.show(vc)
}

View File

@ -48,7 +48,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
.sink(receiveValue: updateRebloggerLabel(reblogger:))
}
override func updateUI(statusID: String) {
override func updateUI(statusID: String, state: StatusState) {
guard var status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
let realStatusID: String
@ -66,7 +66,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
realStatusID = statusID
}
super.updateUI(statusID: realStatusID)
super.updateUI(statusID: realStatusID, state: state)
updateTimestamp()
@ -123,7 +123,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
super.setSelected(selected, animated: animated)
if selected {
delegate?.selected(status: statusID)
delegate?.selected(status: statusID, state: statusState.copy())
}
}
@ -134,7 +134,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
override func getStatusCellPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? {
return (
content: { ConversationTableViewController(for: self.statusID) },
content: { ConversationTableViewController(for: self.statusID, state: self.statusState.copy()) },
actions: { self.actionsForStatus(statusID: self.statusID) }
)
}