metatext-app-ios-iphone-ipad/Data Sources/ExploreDataSource.swift

120 lines
4.5 KiB
Swift

// Copyright © 2021 Metabolist. All rights reserved.
import Combine
import Mastodon
import UIKit
import ViewModels
final class ExploreDataSource: UICollectionViewDiffableDataSource<ExploreViewModel.Section, ExploreViewModel.Item> {
private let updateQueue =
DispatchQueue(label: "com.metabolist.metatext.explore-data-source.update-queue")
private weak var collectionView: UICollectionView?
private var cancellables = Set<AnyCancellable>()
init(collectionView: UICollectionView, viewModel: ExploreViewModel) {
self.collectionView = collectionView
let tagRegistration = UICollectionView.CellRegistration<TagCollectionViewCell, TagViewModel> {
$0.viewModel = $2
}
let instanceRegistration = UICollectionView.CellRegistration<InstanceCollectionViewCell, InstanceViewModel> {
$0.viewModel = $2
}
let itemRegistration = UICollectionView.CellRegistration
<SeparatorConfiguredCollectionViewListCell, ExploreViewModel.Item> {
var configuration = $0.defaultContentConfiguration()
switch $2 {
case .profileDirectory:
configuration.text = NSLocalizedString("explore.profile-directory", comment: "")
configuration.image = UIImage(systemName: "person.crop.square.fill.and.at.rectangle")
default:
break
}
$0.contentConfiguration = configuration
$0.accessories = [.disclosureIndicator()]
}
super.init(collectionView: collectionView) {
switch $2 {
case let .tag(tag):
return $0.dequeueConfiguredReusableCell(
using: tagRegistration,
for: $1,
item: viewModel.viewModel(tag: tag))
case .instance:
return $0.dequeueConfiguredReusableCell(
using: instanceRegistration,
for: $1,
item: viewModel.instanceViewModel)
default:
return $0.dequeueConfiguredReusableCell(using: itemRegistration, for: $1, item: $2)
}
}
let headerRegistration = UICollectionView.SupplementaryRegistration
<ExploreSectionHeaderView>(elementKind: UICollectionView.elementKindSectionHeader) { [weak self] in
$0.label.text = self?.snapshot().sectionIdentifiers[$2.section].displayName
}
supplementaryViewProvider = {
$0.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: $2)
}
viewModel.$trends.combineLatest(viewModel.$instanceViewModel)
.sink { [weak self] in self?.update(tags: $0, instanceViewModel: $1) }
.store(in: &cancellables)
}
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<ExploreViewModel.Section, ExploreViewModel.Item>,
animatingDifferences: Bool = true,
completion: (() -> Void)? = nil) {
updateQueue.async {
super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
}
}
}
private extension ExploreDataSource {
func update(tags: [Tag], instanceViewModel: InstanceViewModel?) {
var newsnapshot = NSDiffableDataSourceSnapshot<ExploreViewModel.Section, ExploreViewModel.Item>()
if !tags.isEmpty {
newsnapshot.appendSections([.trending])
newsnapshot.appendItems(tags.map(ExploreViewModel.Item.tag), toSection: .trending)
}
if let instanceViewModel = instanceViewModel {
newsnapshot.appendSections([.instance])
newsnapshot.appendItems([.instance], toSection: .instance)
if instanceViewModel.instance.canShowProfileDirectory {
newsnapshot.appendItems([.profileDirectory], toSection: .instance)
}
}
let wasEmpty = self.snapshot().itemIdentifiers.isEmpty
let contentOffset = collectionView?.contentOffset
apply(newsnapshot, animatingDifferences: false) {
if let contentOffset = contentOffset, !wasEmpty {
self.collectionView?.contentOffset = contentOffset
}
}
}
}
private extension ExploreViewModel.Section {
var displayName: String {
switch self {
case .trending:
return NSLocalizedString("explore.trending", comment: "")
case .instance:
return NSLocalizedString("explore.instance", comment: "")
}
}
}