From 272f35417b89cf538dff59e10cfd26b614d38aca Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 22 Nov 2022 15:38:40 -0500 Subject: [PATCH] Rewrite account list VC using UICollectionView --- Tusker.xcodeproj/project.pbxproj | 8 +- .../AccountListTableViewController.swift | 75 ----------- .../AccountListViewController.swift | 120 ++++++++++++++++++ Tusker/TuskerNavigationDelegate.swift | 2 +- ...FollowNotificationGroupTableViewCell.swift | 2 +- 5 files changed, 126 insertions(+), 81 deletions(-) delete mode 100644 Tusker/Screens/Account List/AccountListTableViewController.swift create mode 100644 Tusker/Screens/Account List/AccountListViewController.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 3f887ca0..038ce999 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -200,7 +200,6 @@ D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; }; D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; }; D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */; }; - D6A3BC852321F6C100FD64D5 /* AccountListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC832321F6C100FD64D5 /* AccountListTableViewController.swift */; }; D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */; }; D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */; }; D6A4DCCC2553667800D9DE31 /* FastAccountSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */; }; @@ -272,6 +271,7 @@ D6D12B2F2925D66500D528E1 /* TimelineGapCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12B2E2925D66500D528E1 /* TimelineGapCollectionViewCell.swift */; }; D6D12B56292D57E800D528E1 /* AccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12B55292D57E800D528E1 /* AccountCollectionViewCell.swift */; }; D6D12B58292D5B2C00D528E1 /* StatusActionAccountListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12B57292D5B2C00D528E1 /* StatusActionAccountListViewController.swift */; }; + D6D12B5A292D684600D528E1 /* AccountListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12B59292D684600D528E1 /* AccountListViewController.swift */; }; D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */; }; D6D3FDE224F46A8D00FF50A5 /* ComposeUIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D3FDE124F46A8D00FF50A5 /* ComposeUIState.swift */; }; D6D4CC94250DB86A00FCCF8D /* ComposeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4CC93250DB86A00FCCF8D /* ComposeTests.swift */; }; @@ -564,7 +564,6 @@ D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationGroupTableViewCell.xib; sourceTree = ""; }; D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationGroupTableViewCell.swift; sourceTree = ""; }; D6A3BC7F2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FollowNotificationGroupTableViewCell.xib; sourceTree = ""; }; - D6A3BC832321F6C100FD64D5 /* AccountListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListTableViewController.swift; sourceTree = ""; }; D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = ""; }; D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = ""; }; D6A4DCCA2553667800D9DE31 /* FastAccountSwitcherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherViewController.swift; sourceTree = ""; }; @@ -636,6 +635,7 @@ D6D12B2E2925D66500D528E1 /* TimelineGapCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineGapCollectionViewCell.swift; sourceTree = ""; }; D6D12B55292D57E800D528E1 /* AccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCollectionViewCell.swift; sourceTree = ""; }; D6D12B57292D5B2C00D528E1 /* StatusActionAccountListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActionAccountListViewController.swift; sourceTree = ""; }; + D6D12B59292D684600D528E1 /* AccountListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListViewController.swift; sourceTree = ""; }; D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConditionalModifier.swift"; sourceTree = ""; }; D6D3FDE124F46A8D00FF50A5 /* ComposeUIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeUIState.swift; sourceTree = ""; }; D6D4CC93250DB86A00FCCF8D /* ComposeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTests.swift; sourceTree = ""; }; @@ -1183,7 +1183,7 @@ D6A3BC822321F69400FD64D5 /* Account List */ = { isa = PBXGroup; children = ( - D6A3BC832321F6C100FD64D5 /* AccountListTableViewController.swift */, + D6D12B59292D684600D528E1 /* AccountListViewController.swift */, ); path = "Account List"; sourceTree = ""; @@ -1858,7 +1858,6 @@ D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */, D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */, D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */, - D6A3BC852321F6C100FD64D5 /* AccountListTableViewController.swift in Sources */, D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */, D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */, D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */, @@ -2042,6 +2041,7 @@ D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */, D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */, D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */, + D6D12B5A292D684600D528E1 /* AccountListViewController.swift in Sources */, D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tusker/Screens/Account List/AccountListTableViewController.swift b/Tusker/Screens/Account List/AccountListTableViewController.swift deleted file mode 100644 index befe4df0..00000000 --- a/Tusker/Screens/Account List/AccountListTableViewController.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// AccountListTableViewController.swift -// Tusker -// -// Created by Shadowfacts on 9/5/19. -// Copyright © 2019 Shadowfacts. All rights reserved. -// - -import UIKit - -class AccountListTableViewController: EnhancedTableViewController { - - private let accountCell = "accountCell" - - let mastodonController: MastodonController - - let accountIDs: [String] - - init(accountIDs: [String], mastodonController: MastodonController) { - self.accountIDs = accountIDs - self.mastodonController = mastodonController - - super.init(style: .grouped) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - dragEnabled = true - - tableView.register(UINib(nibName: "AccountTableViewCell", bundle: .main), forCellReuseIdentifier: accountCell) - - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 66 - - tableView.alwaysBounceVertical = true - - tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude)) - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return accountIDs.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: accountCell, for: indexPath) as? AccountTableViewCell else { fatalError() } - - let id = accountIDs[indexPath.row] - cell.delegate = self - cell.updateUI(accountID: id) - - return cell - } - -} - -extension AccountListTableViewController: TuskerNavigationDelegate { - var apiController: MastodonController! { mastodonController } -} - -extension AccountListTableViewController: ToastableViewController { -} - -extension AccountListTableViewController: MenuActionProvider { -} diff --git a/Tusker/Screens/Account List/AccountListViewController.swift b/Tusker/Screens/Account List/AccountListViewController.swift new file mode 100644 index 00000000..8f89e064 --- /dev/null +++ b/Tusker/Screens/Account List/AccountListViewController.swift @@ -0,0 +1,120 @@ +// +// AccountListViewController.swift +// Tusker +// +// Created by Shadowfacts on 9/5/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +class AccountListViewController: UIViewController { + typealias Item = String + + private let mastodonController: MastodonController + private let accountIDs: [String] + + private var collectionView: UICollectionView { + view as! UICollectionView + } + private var dataSource: UICollectionViewDiffableDataSource! + + init(accountIDs: [String], mastodonController: MastodonController) { + self.mastodonController = mastodonController + self.accountIDs = accountIDs + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let config = UICollectionLayoutListConfiguration(appearance: .grouped) + let layout = UICollectionViewCompositionalLayout.list(using: config) + view = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.delegate = self + collectionView.dragDelegate = self + dataSource = createDataSource() + } + + private func createDataSource() -> UICollectionViewDiffableDataSource { + let accountCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in + cell.delegate = self + cell.updateUI(accountID: item) + } + return UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in + return collectionView.dequeueConfiguredReusableCell(using: accountCell, for: indexPath, item: itemIdentifier) + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.accounts]) + snapshot.appendItems(accountIDs) + dataSource.apply(snapshot, animatingDifferences: false) + } + +} + +extension AccountListViewController { + enum Section { + case accounts + } +} + +extension AccountListViewController: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let id = dataSource.itemIdentifier(for: indexPath) { + selected(account: id) + } + } + + func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + guard let id = dataSource.itemIdentifier(for: indexPath), + let cell = collectionView.cellForItem(at: indexPath) else { + return nil + } + return UIContextMenuConfiguration { + ProfileViewController(accountID: id, mastodonController: self.mastodonController) + } actionProvider: { _ in + UIMenu(children: self.actionsForProfile(accountID: id, sourceView: cell)) + } + } + + func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { + MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self) + } +} + +extension AccountListViewController: UICollectionViewDragDelegate { + func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { + guard let id = dataSource.itemIdentifier(for: indexPath), + let currentAccountID = mastodonController.accountInfo?.id, + let account = mastodonController.persistentContainer.account(for: id) else { + return [] + } + let provider = NSItemProvider(object: account.url as NSURL) + let activity = UserActivityManager.showProfileActivity(id: id, accountID: currentAccountID) + activity.displaysAuxiliaryScene = true + provider.registerObject(activity, visibility: .all) + return [UIDragItem(itemProvider: provider)] + } +} + +extension AccountListViewController: TuskerNavigationDelegate { + var apiController: MastodonController! { mastodonController } +} + +extension AccountListViewController: MenuActionProvider { +} + +extension AccountListViewController: StatusBarTappableViewController { + func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { + collectionView.scrollToTop() + return .stop + } +} diff --git a/Tusker/TuskerNavigationDelegate.swift b/Tusker/TuskerNavigationDelegate.swift index 7789a259..e17d6e83 100644 --- a/Tusker/TuskerNavigationDelegate.swift +++ b/Tusker/TuskerNavigationDelegate.swift @@ -178,7 +178,7 @@ extension TuskerNavigationDelegate { } func showFollowedByList(accountIDs: [String]) { - let vc = AccountListTableViewController(accountIDs: accountIDs, mastodonController: apiController) + let vc = AccountListViewController(accountIDs: accountIDs, mastodonController: apiController) vc.title = NSLocalizedString("Followed By", comment: "followed by accounts list title") show(vc, sender: self) } diff --git a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift index 50707f65..7afb7d7e 100644 --- a/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift +++ b/Tusker/Views/Notifications/FollowNotificationGroupTableViewCell.swift @@ -210,7 +210,7 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider { if accountIDs.count == 1 { return ProfileViewController(accountID: accountIDs.first!, mastodonController: mastodonController) } else { - return AccountListTableViewController(accountIDs: accountIDs, mastodonController: mastodonController) + return AccountListViewController(accountIDs: accountIDs, mastodonController: mastodonController) } }, actions: { if accountIDs.count == 1 {