Compare commits

...

4 Commits

Author SHA1 Message Date
Shadowfacts 06442b5629
Fix controls in large image/gallery not hiding/showing on zoom
Closes #58
2019-11-28 21:51:24 -05:00
Shadowfacts d5232c0b03
Fix content warning label always showing in conversation main status
When the conversation was opened, the status state of the main status
would already be known, so the CW label wasn't getting updated or
hidden/shown.
2019-11-28 21:22:13 -05:00
Shadowfacts 7140590ccf
Fix covnerstaion expand/collapse button not working on all statuses 2019-11-28 18:58:47 -05:00
Shadowfacts b47b08fa95
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
2019-11-28 18:36:58 -05:00
15 changed files with 167 additions and 92 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]
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
}
@ -131,14 +133,21 @@ class ConversationTableViewController: EnhancedTableViewController {
@objc func toggleVisibilityButtonPressed() {
showStatusesAutomatically = !showStatusesAutomatically
for (_, state) in statuses where state.collapsible == true {
state.collapsed = !showStatusesAutomatically
}
for cell in tableView.visibleCells {
guard let cell = cell as? BaseStatusTableViewCell,
cell.collapsible else { continue }
cell.showStatusAutomatically = showStatusesAutomatically
cell.setCollapsed(!showStatusesAutomatically, animated: false)
}
statusCollapsedStateChanged()
// recalculate cell heights
tableView.beginUpdates()
tableView.endUpdates()
if showStatusesAutomatically {
visibilityBarButtonItem.image = ConversationTableViewController.hidePostsImage
} else {
@ -149,7 +158,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 +168,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 +178,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

@ -71,16 +71,6 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
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) {
fatalError("init(coder:) has not been implemented")
}
@ -172,9 +162,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate {
let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale
if scrollView.zoomScale <= scrollView.minimumZoomScale {
controlsVisible = true
setControlsVisible(true, animated: true)
} else if scrollView.zoomScale > prevZoomScale {
controlsVisible = false
setControlsVisible(false, animated: true)
}
self.prevZoomScale = scrollView.zoomScale
}

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,29 @@ 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
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 +198,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) }
)
}