2021-02-07 07:42:50 +01:00
|
|
|
//
|
|
|
|
// HomeTimelineViewModel+Diffable.swift
|
|
|
|
// Mastodon
|
|
|
|
//
|
|
|
|
// Created by sxiaojian on 2021/2/7.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
2022-10-10 13:14:52 +02:00
|
|
|
import MastodonUI
|
2023-11-27 13:44:26 +01:00
|
|
|
import MastodonSDK
|
2021-02-07 07:42:50 +01:00
|
|
|
|
|
|
|
extension HomeTimelineViewModel {
|
|
|
|
|
|
|
|
func setupDiffableDataSource(
|
2022-01-27 14:23:39 +01:00
|
|
|
tableView: UITableView,
|
2021-03-03 09:12:48 +01:00
|
|
|
statusTableViewCellDelegate: StatusTableViewCellDelegate,
|
2021-02-07 07:42:50 +01:00
|
|
|
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate
|
|
|
|
) {
|
2022-01-27 14:23:39 +01:00
|
|
|
diffableDataSource = StatusSection.diffableDataSource(
|
|
|
|
tableView: tableView,
|
|
|
|
context: context,
|
|
|
|
configuration: StatusSection.Configuration(
|
2022-12-12 16:41:13 +01:00
|
|
|
context: context,
|
2022-10-09 14:07:57 +02:00
|
|
|
authContext: authContext,
|
2022-01-27 14:23:39 +01:00
|
|
|
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
2022-02-15 12:44:45 +01:00
|
|
|
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
|
|
|
|
filterContext: .home,
|
|
|
|
activeFilters: context.statusFilterService.$activeFilters
|
2022-01-27 14:23:39 +01:00
|
|
|
)
|
2021-02-07 07:42:50 +01:00
|
|
|
)
|
2021-07-09 07:17:25 +02:00
|
|
|
|
|
|
|
// make initial snapshot animation smooth
|
2022-01-27 14:23:39 +01:00
|
|
|
var snapshot = NSDiffableDataSourceSnapshot<StatusSection, StatusItem>()
|
2021-06-22 11:49:07 +02:00
|
|
|
snapshot.appendSections([.main])
|
|
|
|
diffableDataSource?.apply(snapshot)
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
fetchedResultsController.$records
|
|
|
|
.receive(on: DispatchQueue.main)
|
|
|
|
.sink { [weak self] records in
|
|
|
|
guard let self = self else { return }
|
|
|
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
2023-09-15 17:45:22 +02:00
|
|
|
|
2022-12-01 08:48:01 +01:00
|
|
|
Task { @MainActor in
|
2022-01-27 14:23:39 +01:00
|
|
|
let oldSnapshot = diffableDataSource.snapshot()
|
|
|
|
var newSnapshot: NSDiffableDataSourceSnapshot<StatusSection, StatusItem> = {
|
|
|
|
let newItems = records.map { record in
|
|
|
|
StatusItem.feed(record: record)
|
2023-12-12 08:56:28 +01:00
|
|
|
}.removingDuplicates()
|
2022-01-27 14:23:39 +01:00
|
|
|
var snapshot = NSDiffableDataSourceSnapshot<StatusSection, StatusItem>()
|
|
|
|
snapshot.appendSections([.main])
|
|
|
|
snapshot.appendItems(newItems, toSection: .main)
|
|
|
|
return snapshot
|
|
|
|
}()
|
2023-11-27 13:44:26 +01:00
|
|
|
|
|
|
|
let anchors: [MastodonFeed] = records.filter { $0.hasMore == true }
|
|
|
|
let itemIdentifiers = newSnapshot.itemIdentifiers
|
|
|
|
for (index, item) in itemIdentifiers.enumerated() {
|
|
|
|
guard case let .feed(record) = item else { continue }
|
|
|
|
guard anchors.contains(where: { feed in feed.id == record.id }) else { continue }
|
|
|
|
let isLast = index + 1 == itemIdentifiers.count
|
|
|
|
if isLast {
|
|
|
|
newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
|
|
|
} else {
|
|
|
|
newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
2023-03-02 11:06:13 +01:00
|
|
|
if !hasChanges && !self.hasPendingStatusEditReload {
|
2022-01-27 14:23:39 +01:00
|
|
|
self.didLoadLatest.send()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-01 08:48:01 +01:00
|
|
|
guard let difference = self.calculateReloadSnapshotDifference(
|
2022-01-27 14:23:39 +01:00
|
|
|
tableView: tableView,
|
|
|
|
oldSnapshot: oldSnapshot,
|
|
|
|
newSnapshot: newSnapshot
|
|
|
|
) else {
|
2023-12-01 09:52:08 +01:00
|
|
|
await self.updateDataSource(snapshot: newSnapshot, animatingDifferences: false)
|
2022-01-27 14:23:39 +01:00
|
|
|
self.didLoadLatest.send()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-01 09:52:08 +01:00
|
|
|
await self.updateDataSource(snapshot: newSnapshot, animatingDifferences: false)
|
2022-12-01 08:48:01 +01:00
|
|
|
tableView.scrollToRow(at: difference.targetIndexPath, at: .top, animated: false)
|
|
|
|
var contentOffset = tableView.contentOffset
|
|
|
|
contentOffset.y = tableView.contentOffset.y - difference.sourceDistanceToTableViewTopEdge
|
|
|
|
tableView.setContentOffset(contentOffset, animated: false)
|
2022-01-27 14:23:39 +01:00
|
|
|
self.didLoadLatest.send()
|
2023-03-02 11:06:13 +01:00
|
|
|
self.hasPendingStatusEditReload = false
|
2022-01-27 14:23:39 +01:00
|
|
|
} // end Task
|
|
|
|
}
|
|
|
|
.store(in: &disposeBag)
|
2021-02-07 07:42:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
extension HomeTimelineViewModel {
|
2021-02-07 07:42:50 +01:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
@MainActor func updateDataSource(
|
|
|
|
snapshot: NSDiffableDataSourceSnapshot<StatusSection, StatusItem>,
|
|
|
|
animatingDifferences: Bool
|
|
|
|
) async {
|
2022-12-17 20:26:20 +01:00
|
|
|
await diffableDataSource?.apply(snapshot, animatingDifferences: animatingDifferences)
|
2021-02-07 07:42:50 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
@MainActor func updateSnapshotUsingReloadData(
|
|
|
|
snapshot: NSDiffableDataSourceSnapshot<StatusSection, StatusItem>
|
2022-12-01 08:48:01 +01:00
|
|
|
) {
|
2022-12-17 20:26:20 +01:00
|
|
|
self.diffableDataSource?.applySnapshotUsingReloadData(snapshot)
|
2021-02-07 07:42:50 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
struct Difference<T> {
|
2021-02-07 07:42:50 +01:00
|
|
|
let item: T
|
|
|
|
let sourceIndexPath: IndexPath
|
2022-01-27 14:23:39 +01:00
|
|
|
let sourceDistanceToTableViewTopEdge: CGFloat
|
2021-02-07 07:42:50 +01:00
|
|
|
let targetIndexPath: IndexPath
|
|
|
|
}
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
@MainActor private func calculateReloadSnapshotDifference<S: Hashable, T: Hashable>(
|
2021-02-07 07:42:50 +01:00
|
|
|
tableView: UITableView,
|
2022-01-27 14:23:39 +01:00
|
|
|
oldSnapshot: NSDiffableDataSourceSnapshot<S, T>,
|
|
|
|
newSnapshot: NSDiffableDataSourceSnapshot<S, T>
|
2021-02-07 07:42:50 +01:00
|
|
|
) -> Difference<T>? {
|
2022-01-27 14:23:39 +01:00
|
|
|
guard let sourceIndexPath = (tableView.indexPathsForVisibleRows ?? []).sorted().first else { return nil }
|
|
|
|
let rectForSourceItemCell = tableView.rectForRow(at: sourceIndexPath)
|
2022-07-14 20:46:48 +02:00
|
|
|
let sourceDistanceToTableViewTopEdge: CGFloat = {
|
|
|
|
if tableView.window != nil {
|
|
|
|
return tableView.convert(rectForSourceItemCell, to: nil).origin.y - tableView.safeAreaInsets.top
|
|
|
|
} else {
|
|
|
|
return rectForSourceItemCell.origin.y - tableView.contentOffset.y - tableView.safeAreaInsets.top
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
guard sourceIndexPath.section < oldSnapshot.numberOfSections,
|
|
|
|
sourceIndexPath.row < oldSnapshot.numberOfItems(inSection: oldSnapshot.sectionIdentifiers[sourceIndexPath.section])
|
|
|
|
else { return nil }
|
2021-02-07 07:42:50 +01:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
let sectionIdentifier = oldSnapshot.sectionIdentifiers[sourceIndexPath.section]
|
|
|
|
let item = oldSnapshot.itemIdentifiers(inSection: sectionIdentifier)[sourceIndexPath.row]
|
2021-02-07 07:42:50 +01:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
guard let targetIndexPathRow = newSnapshot.indexOfItem(item),
|
|
|
|
let newSectionIdentifier = newSnapshot.sectionIdentifier(containingItem: item),
|
|
|
|
let targetIndexPathSection = newSnapshot.indexOfSection(newSectionIdentifier)
|
|
|
|
else { return nil }
|
2021-02-07 07:42:50 +01:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
let targetIndexPath = IndexPath(row: targetIndexPathRow, section: targetIndexPathSection)
|
2021-02-07 07:42:50 +01:00
|
|
|
|
|
|
|
return Difference(
|
2022-01-27 14:23:39 +01:00
|
|
|
item: item,
|
2021-02-07 07:42:50 +01:00
|
|
|
sourceIndexPath: sourceIndexPath,
|
2022-01-27 14:23:39 +01:00
|
|
|
sourceDistanceToTableViewTopEdge: sourceDistanceToTableViewTopEdge,
|
|
|
|
targetIndexPath: targetIndexPath
|
2021-02-07 07:42:50 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|