// // ProfileDirectoryViewController.swift // Tusker // // Created by Shadowfacts on 2/6/21. // Copyright © 2021 Shadowfacts. All rights reserved. // import UIKit import Pachyderm class ProfileDirectoryViewController: UIViewController { weak var mastodonController: MastodonController! private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! init(mastodonController: MastodonController) { self.mastodonController = mastodonController super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() title = NSLocalizedString("Profile Directory", comment: "profile directory title") let configuration = UICollectionViewCompositionalLayoutConfiguration() configuration.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem( layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100)), elementKind: "filter", alignment: .top ) ] let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) in let itemHeight = NSCollectionLayoutDimension.absolute(200) let itemWidth: NSCollectionLayoutDimension if case .compact = layoutEnvironment.traitCollection.horizontalSizeClass { itemWidth = .fractionalWidth(1) } else { itemWidth = .absolute((layoutEnvironment.container.contentSize.width - 12) / 2) } let itemSize = NSCollectionLayoutSize(widthDimension: itemWidth, heightDimension: itemHeight) let item = NSCollectionLayoutItem(layoutSize: itemSize) let itemB = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: itemHeight) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, itemB]) group.interItemSpacing = .flexible(4) let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = 4 if case .compact = layoutEnvironment.traitCollection.horizontalSizeClass { section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) } else { section.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4) } return section }, configuration: configuration) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.backgroundColor = .secondarySystemBackground collectionView.register(UINib(nibName: "FeaturedProfileCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "featuredProfileCell") collectionView.register(ProfileDirectoryFilterView.self, forSupplementaryViewOfKind: "filter", withReuseIdentifier: "filter") collectionView.delegate = self collectionView.dragDelegate = self view.addSubview(collectionView) dataSource = createDataSource() updateProfiles(local: true, order: .active) } private func createDataSource() -> UICollectionViewDiffableDataSource { let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) in guard case let .account(account) = item else { fatalError() } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "featuredProfileCell", for: indexPath) as! FeaturedProfileCollectionViewCell cell.updateUI(account: account) return cell } dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in guard elementKind == "filter" else { return nil } let filterView = collectionView.dequeueReusableSupplementaryView(ofKind: "filter", withReuseIdentifier: "filter", for: indexPath) as! ProfileDirectoryFilterView filterView.updateUI(mastodonController: self.mastodonController) filterView.onFilterChanged = { [weak self] (scope, order) in guard let self = self else { return } self.dataSource.apply(.init()) self.updateProfiles(local: scope == .instance, order: order) } return filterView } return dataSource } private func updateProfiles(local: Bool, order: DirectoryOrder) { let request = Client.getFeaturedProfiles(local: local, order: order) mastodonController.run(request) { (response) in guard case let .success(accounts, _) = response else { return } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.featuredProfiles]) snapshot.appendItems(accounts.map { .account($0) }) DispatchQueue.main.async { self.dataSource.apply(snapshot) } } } } extension ProfileDirectoryViewController { enum Section { case featuredProfiles } enum Item: Hashable { case account(Account) func hash(into hasher: inout Hasher) { guard case let .account(account) = self else { return } hasher.combine(account.id) } } } extension ProfileDirectoryViewController: TuskerNavigationDelegate { var apiController: MastodonController { mastodonController } } extension ProfileDirectoryViewController: MenuPreviewProvider { var navigationDelegate: TuskerNavigationDelegate? { self } } extension ProfileDirectoryViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let item = dataSource.itemIdentifier(for: indexPath), case let .account(account) = item else { return } show(ProfileViewController(accountID: account.id, mastodonController: mastodonController), sender: nil) } func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { guard let item = dataSource.itemIdentifier(for: indexPath), case let .account(account) = item else { return nil } return UIContextMenuConfiguration(identifier: nil) { return ProfileViewController(accountID: account.id, mastodonController: self.mastodonController) } actionProvider: { (_) in let actions = self.actionsForProfile(accountID: account.id, sourceView: self.collectionView.cellForItem(at: indexPath)) return UIMenu(children: actions) } } func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { if let viewController = animator.previewViewController { animator.preferredCommitStyle = .pop animator.addCompletion { self.show(viewController, sender: nil) } } } } extension ProfileDirectoryViewController: UICollectionViewDragDelegate { func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { guard let item = dataSource.itemIdentifier(for: indexPath), case let .account(account) = item, let currentAccountID = mastodonController.accountInfo?.id else { return [] } let provider = NSItemProvider(object: account.url as NSURL) let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID) provider.registerObject(activity, visibility: .all) return [UIDragItem(itemProvider: provider)] } }