// Copyright © 2021 Metabolist. All rights reserved. import Combine import UIKit import ViewModels enum IdentitiesSection: Hashable { case add case identities(String) } enum IdentitiesItem: Hashable { case add case identitiy(Identity) } final class IdentitiesDataSource: UITableViewDiffableDataSource { private let updateQueue = DispatchQueue(label: "com.metabolist.metatext.identities-data-source.update-queue") private var cancellables = Set() init(tableView: UITableView, publisher: AnyPublisher<[Identity], Never>, viewModelProvider: @escaping (Identity) -> IdentityViewModel) { tableView.register(UITableViewCell.self, forCellReuseIdentifier: String(describing: UITableViewCell.self)) tableView.register(IdentityTableViewCell.self, forCellReuseIdentifier: String(describing: IdentityTableViewCell.self)) super.init(tableView: tableView) { tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: item.cellReuseIdentifier, for: indexPath) switch item { case .add: var configuration = cell.defaultContentConfiguration() configuration.text = NSLocalizedString("add", comment: "") configuration.image = UIImage(systemName: "plus.circle.fill") cell.contentConfiguration = configuration case let .identitiy(identity): let viewModel = viewModelProvider(identity) (cell as? IdentityTableViewCell)?.viewModel = viewModel cell.accessoryType = identity.id == viewModel.identityContext.identity.id ? .checkmark : .none } return cell } publisher .sink { [weak self] in self?.update(identities: $0) } .store(in: &cancellables) } override func apply(_ snapshot: NSDiffableDataSourceSnapshot, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) { updateQueue.async { super.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion) } } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { let currentSnapshot = snapshot() let section = currentSnapshot.sectionIdentifiers[section] if currentSnapshot.numberOfItems(inSection: section) > 0, case let .identities(title) = section { return title } return nil } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { itemIdentifier(for: indexPath) != .add } } private extension IdentitiesDataSource { private func update(identities: [Identity]) { var newSnapshot = NSDiffableDataSourceSnapshot() let sections = [ (section: IdentitiesSection.identities(NSLocalizedString("identities.accounts", comment: "")), identities: identities.filter { $0.authenticated && !$0.pending }.map(IdentitiesItem.identitiy)), (section: IdentitiesSection.identities(NSLocalizedString("identities.browsing", comment: "")), identities: identities.filter { !$0.authenticated && !$0.pending }.map(IdentitiesItem.identitiy)), (section: IdentitiesSection.identities(NSLocalizedString("identities.pending", comment: "")), identities: identities.filter { $0.pending }.map(IdentitiesItem.identitiy)) ] .filter { !$0.identities.isEmpty } newSnapshot.appendSections([.add] + sections.map(\.section)) newSnapshot.appendItems([.add], toSection: .add) for section in sections { newSnapshot.appendItems(section.identities, toSection: section.section) } apply(newSnapshot, animatingDifferences: !snapshot().sectionIdentifiers.filter { $0 != .add }.isEmpty) } } private extension IdentitiesItem { var cellReuseIdentifier: String { switch self { case .add: return String(describing: UITableViewCell.self) case .identitiy: return String(describing: IdentityTableViewCell.self) } } }