Remove TimelineLikeTableViewController

Everything now uses DiffableTimelineLike
This commit is contained in:
Shadowfacts 2021-11-25 12:29:35 -05:00
parent 654b5d9c59
commit 2b22180191
2 changed files with 0 additions and 258 deletions

View File

@ -154,7 +154,6 @@
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */; };
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
D65234C9256189D0001AF9CF /* TimelineLikeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */; };
D65234D325618EFA001AF9CF /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65234D225618EFA001AF9CF /* TimelineTableViewController.swift */; };
D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65234E02561AA68001AF9CF /* NotificationsTableViewController.swift */; };
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
@ -559,7 +558,6 @@
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = "<group>"; };
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLikeTableViewController.swift; sourceTree = "<group>"; };
D65234D225618EFA001AF9CF /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = "<group>"; };
D65234E02561AA68001AF9CF /* NotificationsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewController.swift; sourceTree = "<group>"; };
D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentView.swift; sourceTree = "<group>"; };
@ -1523,7 +1521,6 @@
D6412B0224AFF6A600F5412E /* TabBarScrollableViewController.swift */,
D6B22A0E2560D52D004D82EF /* TabbedPageViewController.swift */,
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */,
D65234C8256189D0001AF9CF /* TimelineLikeTableViewController.swift */,
D6DFC69D242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift */,
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */,
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */,
@ -2080,7 +2077,6 @@
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */,
D65234C9256189D0001AF9CF /* TimelineLikeTableViewController.swift in Sources */,
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,

View File

@ -1,254 +0,0 @@
//
// TimelineLikeTableViewController.swift
// Tusker
//
// Created by Shadowfacts on 11/15/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
/// A table view controller that manages common functionality between timeline-like UIs.
/// For example, this class handles loading new items when the user scrolls to the end,
/// refreshing, and pruning offscreen rows automatically.
class TimelineLikeTableViewController<Item>: EnhancedTableViewController, RefreshableViewController {
private(set) var loaded = false
var sections: [[Item]] = []
private let pageSize = 20
private var lastLastVisibleRow: IndexPath?
init() {
super.init(style: .plain)
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: Self.refreshCommandTitle()))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func item(for indexPath: IndexPath) -> Item {
return sections[indexPath.section][indexPath.row]
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 140
#if !targetEnvironment(macCatalyst)
self.refreshControl = UIRefreshControl()
self.refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged)
#endif
if let prefetchSource = self as? UITableViewDataSourcePrefetching {
tableView.prefetchDataSource = prefetchSource
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadInitial()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
pruneOffscreenRows()
}
func loadInitial() {
guard !loaded else { return }
// set loaded immediately so we don't trigger another request while the current one is running
loaded = true
loadInitialItems() { (items) in
DispatchQueue.main.async {
guard items.count > 0 else {
// set loaded back to false so the next time the VC appears, we try to load again
// todo: this should probably retry automatically
self.loaded = false
return
}
if self.sections.count < self.headerSectionsCount() {
self.sections.insert(contentsOf: Array(repeating: [], count: self.headerSectionsCount() - self.sections.count), at: 0)
}
self.sections.append(items)
self.tableView.reloadData()
}
}
}
func reloadInitialItems() {
loaded = false
sections = []
loadInitial()
}
func cellHeightChanged() {
// causes the table view to recalculate the cell heights
tableView.beginUpdates()
tableView.endUpdates()
}
class func refreshCommandTitle() -> String {
return "Refresh"
}
// todo: these three should use Result<[Item], Client.Error> so we can differentiate between failed requests and there actually being no results
func loadInitialItems(completion: @escaping ([Item]) -> Void) {
fatalError("loadInitialItems(completion:) must be implemented by subclasses")
}
func loadOlder(completion: @escaping ([Item]) -> Void) {
fatalError("loadOlder(completion:) must be implemented by subclasses")
}
func loadNewer(completion: @escaping ([Item]) -> Void) {
fatalError("loadNewer(completion:) must be implemented by subclasses")
}
func willRemoveRows(at indexPaths: [IndexPath]) {
}
func headerSectionsCount() -> Int {
return 0
}
private func pruneOffscreenRows() {
guard let lastVisibleRow = lastLastVisibleRow,
// never remove the last section
sections.count - headerSectionsCount() > 1 else {
return
}
let lastSectionIndex = sections.count - 1
if lastVisibleRow.section < lastSectionIndex {
// if there is a section below the last visible one
let sectionsToRemove = (lastVisibleRow.section + 1)...lastSectionIndex
let indexPathsToRemove = sectionsToRemove.flatMap { (section) in
sections[section].indices.map { (row) in
IndexPath(row: row, section: section)
}
}
willRemoveRows(at: indexPathsToRemove)
UIView.performWithoutAnimation {
tableView.deleteSections(IndexSet(sectionsToRemove), with: .none)
}
sections.removeSubrange(sectionsToRemove)
} else if lastVisibleRow.section == lastSectionIndex {
let lastSection = sections.last!
let lastRowIndex = lastSection.count - 1
if lastVisibleRow.row < lastRowIndex - pageSize {
// if there are more than pageSize rows in the current section below the last visible one
let rowIndicesInLastSectionToRemove = (lastVisibleRow.row + pageSize)..<lastSection.count
let indexPathsToRemove = rowIndicesInLastSectionToRemove.map {
IndexPath(row: $0, section: lastSectionIndex)
}
willRemoveRows(at: indexPathsToRemove)
sections[lastSectionIndex].removeSubrange(rowIndicesInLastSectionToRemove)
UIView.performWithoutAnimation {
tableView.deleteRows(at: indexPathsToRemove, with: .none)
}
}
}
}
// MARK: - UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
fatalError("tableView(_:cellForRowAt:) must be implemented by subclasses")
}
// MARK: - UITableViewDelegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// this assumes that indexPathsForVisibleRows is always in order
lastLastVisibleRow = tableView.indexPathsForVisibleRows?.last
if indexPath.section == sections.count - 1,
indexPath.row == sections[indexPath.section].count - 1 {
loadOlder() { (newItems) in
guard newItems.count > 0 else { return }
DispatchQueue.main.async {
let newRows = self.sections.last!.count..<(self.sections.last!.count + newItems.count)
let newIndexPaths = newRows.map { IndexPath(row: $0, section: self.sections.count - 1) }
self.sections[self.sections.count - 1].append(contentsOf: newItems)
UIView.performWithoutAnimation {
self.tableView.insertRows(at: newIndexPaths, with: .none)
}
}
}
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.leadingSwipeActionsConfiguration()
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
}
// MARK: - RefreshableViewController
func refresh() {
loadNewer() { (newItems) in
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
guard newItems.count > 0 else { return }
let firstNonHeaderSection = self.headerSectionsCount()
self.sections[firstNonHeaderSection].insert(contentsOf: newItems, at: 0)
let newIndexPaths = (0..<newItems.count).map { IndexPath(row: $0, section: firstNonHeaderSection) }
UIView.performWithoutAnimation {
self.tableView.insertRows(at: newIndexPaths, with: .none)
}
// maintain the current position in the list (don't scroll to top)
self.tableView.scrollToRow(at: IndexPath(row: newItems.count, section: firstNonHeaderSection), at: .top, animated: false)
}
}
}
}
extension TimelineLikeTableViewController: BackgroundableViewController {
func sceneDidEnterBackground() {
pruneOffscreenRows()
}
}