Compare commits

..

No commits in common. "06442b5629a5d673f57c8f729effc8666c958962" and "24a1e7ceb99d4e9c0b47fe15f806d63d38bbdbb4" have entirely different histories.

15 changed files with 92 additions and 167 deletions

View File

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

View File

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

View File

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

View File

@ -71,6 +71,16 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
modalPresentationStyle = .fullScreen modalPresentationStyle = .fullScreen
} }
// init(gifData: Data?, description: String?, sourceFrame: CGRect, sourceCornerRadius: CGFloat, router: AppRouter) {
// self.router = router
// self.gifData = gifData
// self.imageDescription = description
// self.originFrame = sourceFrame
// self.originCornerRadius = sourceCornerRadius
//
// super.init(nibName: "LargeImageViewController", bundle: nil)
// }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@ -162,9 +172,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale
if scrollView.zoomScale <= scrollView.minimumZoomScale { if scrollView.zoomScale <= scrollView.minimumZoomScale {
setControlsVisible(true, animated: true) controlsVisible = true
} else if scrollView.zoomScale > prevZoomScale { } else if scrollView.zoomScale > prevZoomScale {
setControlsVisible(false, animated: true) controlsVisible = false
} }
self.prevZoomScale = scrollView.zoomScale self.prevZoomScale = scrollView.zoomScale
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import Pachyderm
import Combine import Combine
protocol StatusTableViewCellDelegate: TuskerNavigationDelegate { protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) func statusCollapsedStateChanged()
} }
class BaseStatusTableViewCell: UITableViewCell { class BaseStatusTableViewCell: UITableViewCell {
@ -48,18 +48,12 @@ class BaseStatusTableViewCell: UITableViewCell {
} }
} }
var statusState: StatusState!
var collapsible = false { var collapsible = false {
didSet { didSet {
collapseButton.isHidden = !collapsible collapseButton.isHidden = !collapsible
statusState?.collapsible = collapsible
}
}
var collapsed = false {
didSet {
statusState?.collapsed = collapsed
} }
} }
var collapsed = false
var showStatusAutomatically = false var showStatusAutomatically = false
var avatarURL: URL? var avatarURL: URL?
@ -105,12 +99,11 @@ class BaseStatusTableViewCell: UITableViewCell {
.sink(receiveValue: updateUI(account:)) .sink(receiveValue: updateUI(account:))
} }
func updateUI(statusID: String, state: StatusState) { func updateUI(statusID: String) {
guard let status = MastodonCache.status(for: statusID) else { guard let status = MastodonCache.status(for: statusID) else {
fatalError("Missing cached status") fatalError("Missing cached status")
} }
self.statusID = statusID self.statusID = statusID
self.statusState = state
let account = status.account let account = status.account
self.accountID = account.id self.accountID = account.id
@ -126,29 +119,20 @@ class BaseStatusTableViewCell: UITableViewCell {
contentLabel.statusID = statusID contentLabel.statusID = statusID
collapsible = !status.spoilerText.isEmpty
var shouldCollapse = collapsible
contentWarningLabel.text = status.spoilerText contentWarningLabel.text = status.spoilerText
contentWarningLabel.isHidden = status.spoilerText.isEmpty contentWarningLabel.isHidden = status.spoilerText.isEmpty
if !shouldCollapse,
if state.unknown { let text = contentLabel.text,
collapsible = !status.spoilerText.isEmpty text.count > 500 {
var shouldCollapse = collapsible collapsible = true
if !shouldCollapse, shouldCollapse = true
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) { func updateStatusState(status: Status) {
@ -198,7 +182,7 @@ class BaseStatusTableViewCell: UITableViewCell {
@IBAction func collapseButtonPressed() { @IBAction func collapseButtonPressed() {
setCollapsed(!collapsed, animated: true) setCollapsed(!collapsed, animated: true)
delegate?.statusCellCollapsedStateChanged(self) delegate?.statusCollapsedStateChanged()
} }
func setCollapsed(_ collapsed: Bool, animated: Bool) { 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!] accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentLabel!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
} }
override func updateUI(statusID: String, state: StatusState) { override func updateUI(statusID: String) {
super.updateUI(statusID: statusID, state: state) super.updateUI(statusID: statusID)
guard let status = MastodonCache.status(for: statusID) else { fatalError() } guard let status = MastodonCache.status(for: statusID) else { fatalError() }
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt) var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
@ -70,7 +70,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
@IBAction func totalFavoritesPressed() { @IBAction func totalFavoritesPressed() {
if let delegate = delegate { if let delegate = delegate {
// accounts aren't known, pass nil so the VC will load them // accounts aren't known, pass nil so the VC will load them
let vc = delegate.statusActionAccountList(action: .favorite, statusID: statusID, statusState: statusState.copy(), accountIDs: nil) let vc = delegate.statusActionAccountList(action: .favorite, statusID: statusID, accountIDs: nil)
vc.showInacurateCountWarning = true vc.showInacurateCountWarning = true
delegate.show(vc) delegate.show(vc)
} }
@ -79,7 +79,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
@IBAction func totalReblogsPressed() { @IBAction func totalReblogsPressed() {
if let delegate = delegate { if let delegate = delegate {
// accounts aren't known, pass nil so the VC will load them // accounts aren't known, pass nil so the VC will load them
let vc = delegate.statusActionAccountList(action: .reblog, statusID: statusID, statusState: statusState.copy(), accountIDs: nil) let vc = delegate.statusActionAccountList(action: .reblog, statusID: statusID, accountIDs: nil)
vc.showInacurateCountWarning = true vc.showInacurateCountWarning = true
delegate.show(vc) delegate.show(vc)
} }

View File

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