Refactoring
This commit is contained in:
parent
9e40c45b0f
commit
e74daa4df0
|
@ -4,10 +4,11 @@ import Foundation
|
|||
import Combine
|
||||
|
||||
class StatusesViewModel: ObservableObject {
|
||||
@Published private(set) var statusSections = [[Status]]()
|
||||
@Published private(set) var statusIDs = [[String]]()
|
||||
@Published var alertItem: AlertItem?
|
||||
@Published private(set) var loading = false
|
||||
private(set) var maintainScrollPositionOfStatusID: String?
|
||||
private var statuses = [String: Status]()
|
||||
private let statusListService: StatusListService
|
||||
private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
@ -19,9 +20,11 @@ class StatusesViewModel: ObservableObject {
|
|||
.handleEvents(receiveOutput: { [weak self] in
|
||||
self?.determineIfScrollPositionShouldBeMaintained(newStatusSections: $0)
|
||||
self?.cleanViewModelCache(newStatusSections: $0)
|
||||
self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) })
|
||||
})
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.assign(to: &$statusSections)
|
||||
.map { $0.map { section in section.map(\.id) } }
|
||||
.assign(to: &$statusIDs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +41,9 @@ extension StatusesViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func statusViewModel(status: Status) -> StatusViewModel {
|
||||
func statusViewModel(id: String) -> StatusViewModel? {
|
||||
guard let status = statuses[id] else { return nil }
|
||||
|
||||
var statusViewModel: StatusViewModel
|
||||
|
||||
if let cachedViewModel = statusViewModelCache[status]?.0 {
|
||||
|
@ -59,8 +64,10 @@ extension StatusesViewModel {
|
|||
return statusViewModel
|
||||
}
|
||||
|
||||
func contextViewModel(status: Status) -> StatusesViewModel {
|
||||
StatusesViewModel(statusListService: statusListService.contextService(status: status))
|
||||
func contextViewModel(id: String) -> StatusesViewModel? {
|
||||
guard let status = statuses[id] else { return nil }
|
||||
|
||||
return StatusesViewModel(statusListService: statusListService.contextService(status: status))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +76,7 @@ private extension StatusesViewModel {
|
|||
maintainScrollPositionOfStatusID = nil // clear old value
|
||||
|
||||
// Maintain scroll position of parent after initial load of context
|
||||
if let contextParentID = contextParentID, statusSections.reduce([], +).map(\.id) == [contextParentID] {
|
||||
if let contextParentID = contextParentID, statusIDs.reduce([], +) == [contextParentID] {
|
||||
maintainScrollPositionOfStatusID = contextParentID
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ import Combine
|
|||
class StatusListViewController: UITableViewController {
|
||||
private let viewModel: StatusesViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var cellHeightCaches = [CGFloat: [Status: CGFloat]]()
|
||||
private var cellHeightCaches = [CGFloat: [String: CGFloat]]()
|
||||
|
||||
private lazy var dataSource: UITableViewDiffableDataSource<Int, Status> = {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, status in
|
||||
private lazy var dataSource: UITableViewDiffableDataSource<Int, String> = {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, statusID in
|
||||
guard
|
||||
let self = self,
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
|
@ -17,9 +17,7 @@ class StatusListViewController: UITableViewController {
|
|||
for: indexPath) as? StatusTableViewCell
|
||||
else { return nil }
|
||||
|
||||
let statusViewModel = self.viewModel.statusViewModel(status: status)
|
||||
|
||||
cell.viewModel = statusViewModel
|
||||
cell.viewModel = self.viewModel.statusViewModel(id: statusID)
|
||||
cell.delegate = self
|
||||
|
||||
return cell
|
||||
|
@ -50,7 +48,7 @@ class StatusListViewController: UITableViewController {
|
|||
tableView.cellLayoutMarginsFollowReadableWidth = true
|
||||
tableView.separatorInset = .zero
|
||||
|
||||
viewModel.$statusSections.map { $0.snapshot() }
|
||||
viewModel.$statusIDs
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
@ -58,16 +56,16 @@ class StatusListViewController: UITableViewController {
|
|||
|
||||
if
|
||||
let id = self.viewModel.maintainScrollPositionOfStatusID,
|
||||
let indexPath = self.indexPath(statusID: id),
|
||||
let indexPath = self.dataSource.indexPath(for: id),
|
||||
let navigationBar = self.navigationController?.navigationBar {
|
||||
let navigationBarMaxY = self.tableView.convert(navigationBar.bounds, from: navigationBar).maxY
|
||||
offsetFromNavigationBar = self.tableView.rectForRow(at: indexPath).origin.y - navigationBarMaxY
|
||||
}
|
||||
|
||||
self.dataSource.apply($0, animatingDifferences: false) {
|
||||
self.dataSource.apply($0.snapshot(), animatingDifferences: false) {
|
||||
if
|
||||
let id = self.viewModel.maintainScrollPositionOfStatusID,
|
||||
let indexPath = self.indexPath(statusID: id),
|
||||
let indexPath = self.dataSource.indexPath(for: id),
|
||||
let offsetFromNavigationBar = offsetFromNavigationBar {
|
||||
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
|
||||
self.tableView.contentOffset.y -= offsetFromNavigationBar
|
||||
|
@ -88,7 +86,7 @@ class StatusListViewController: UITableViewController {
|
|||
forRowAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
var heightCache = cellHeightCaches[tableView.frame.width] ?? [Status: CGFloat]()
|
||||
var heightCache = cellHeightCaches[tableView.frame.width] ?? [String: CGFloat]()
|
||||
|
||||
heightCache[item] = cell.frame.height
|
||||
cellHeightCaches[tableView.frame.width] = heightCache
|
||||
|
@ -101,41 +99,32 @@ class StatusListViewController: UITableViewController {
|
|||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
viewModel.statusSections[indexPath.section][indexPath.row].id != viewModel.contextParentID
|
||||
guard let id = dataSource.itemIdentifier(for: indexPath) else { return true }
|
||||
|
||||
return id != viewModel.contextParentID
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let status = viewModel.statusSections[indexPath.section][indexPath.row]
|
||||
guard
|
||||
let id = dataSource.itemIdentifier(for: indexPath),
|
||||
let contextViewModel = viewModel.contextViewModel(id: id)
|
||||
else { return }
|
||||
|
||||
navigationController?.pushViewController(
|
||||
StatusListViewController(viewModel: viewModel.contextViewModel(status: status)),
|
||||
StatusListViewController(viewModel: contextViewModel),
|
||||
animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusListViewController: StatusTableViewCellDelegate {
|
||||
func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell) {
|
||||
guard let url = cell.viewModel.sharingURL else { return }
|
||||
guard let url = cell.viewModel?.sharingURL else { return }
|
||||
|
||||
share(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
private extension StatusListViewController {
|
||||
func indexPath(statusID: String) -> IndexPath? {
|
||||
for section in 0..<dataSource.numberOfSections(in: tableView) {
|
||||
for row in 0..<dataSource.tableView(tableView, numberOfRowsInSection: section) {
|
||||
let indexPath = IndexPath(row: row, section: section)
|
||||
|
||||
if dataSource.itemIdentifier(for: indexPath)?.id == statusID {
|
||||
return indexPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func share(url: URL) {
|
||||
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
|
||||
|
@ -143,16 +132,16 @@ private extension StatusListViewController {
|
|||
}
|
||||
}
|
||||
|
||||
private extension Array where Element == [Status] {
|
||||
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Status> {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Status>()
|
||||
private extension Array where Element: Sequence, Element.Element: Hashable {
|
||||
func snapshot() -> NSDiffableDataSourceSnapshot<Int, Element.Element> {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Element.Element>()
|
||||
|
||||
let sections = [Int](0..<count)
|
||||
|
||||
snapshot.appendSections(sections)
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendItems(self[section], toSection: section)
|
||||
snapshot.appendItems(self[section].map { $0 }, toSection: section)
|
||||
}
|
||||
|
||||
return snapshot
|
||||
|
|
|
@ -58,8 +58,10 @@ class StatusTableViewCell: UITableViewCell {
|
|||
|
||||
@IBOutlet private var separatorConstraints: [NSLayoutConstraint]!
|
||||
|
||||
var viewModel: StatusViewModel! {
|
||||
var viewModel: StatusViewModel? {
|
||||
didSet {
|
||||
guard let viewModel = viewModel else { return }
|
||||
|
||||
let mutableContent = NSMutableAttributedString(attributedString: viewModel.content)
|
||||
let mutableDisplayName = NSMutableAttributedString(string: viewModel.displayName)
|
||||
let mutableSpoilerText = NSMutableAttributedString(string: viewModel.spoilerText)
|
||||
|
@ -274,7 +276,7 @@ extension StatusTableViewCell {
|
|||
}
|
||||
|
||||
@IBAction func favoriteButtonTapped(_ sender: UIButton) {
|
||||
viewModel.toggleFavorited()
|
||||
viewModel?.toggleFavorited()
|
||||
}
|
||||
|
||||
@IBAction func actionsButtonTapped(_ sender: Any) {
|
||||
|
@ -325,7 +327,7 @@ private extension StatusTableViewCell {
|
|||
let reblogColor: UIColor = reblogged ? .systemGreen : .secondaryLabel
|
||||
let reblogButton: UIButton
|
||||
|
||||
if viewModel.isContextParent {
|
||||
if viewModel?.isContextParent ?? false {
|
||||
reblogButton = contextParentReblogButton
|
||||
} else {
|
||||
reblogButton = self.reblogButton
|
||||
|
@ -340,7 +342,7 @@ private extension StatusTableViewCell {
|
|||
let favoriteButton: UIButton
|
||||
let scale: UIImage.SymbolScale
|
||||
|
||||
if viewModel.isContextParent {
|
||||
if viewModel?.isContextParent ?? false {
|
||||
favoriteButton = contextParentFavoriteButton
|
||||
scale = .medium
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue