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:
parent
24a1e7ceb9
commit
b47b08fa95
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 */,
|
||||
|
|
|
@ -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]
|
||||
|
||||
guard let mainStatus = MastodonCache.status(for: mainStatusID) else { fatalError("Missing cached status \(mainStatusID!)") }
|
||||
statuses = [(mainStatusID, mainStatusState)]
|
||||
|
||||
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,9 +138,14 @@ 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
|
||||
} else {
|
||||
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 []
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue