From 10f2358247e35e4e2eb8c4e6a2d07f4132dc36ba Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 25 Oct 2023 17:53:13 +0200 Subject: [PATCH] Migrate Followers-list to work with Mastodon.Entity.Account instead of MastodonUser --- Mastodon.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 250 ------------------ ...istViewController+DataSourceProvider.swift | 34 --- .../Follower/FollowerListViewController.swift | 28 ++ .../FollowerListViewModel+Diffable.swift | 48 ++-- .../FollowerListViewModel+State.swift | 54 ++-- .../Follower/FollowerListViewModel.swift | 11 +- 7 files changed, 96 insertions(+), 335 deletions(-) delete mode 100644 Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 Mastodon/Scene/Profile/Follower/FollowerListViewController+DataSourceProvider.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 067137721..2ab5600b9 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -317,7 +317,7 @@ DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; }; DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F7442799056400455B82 /* HashtagTableViewCell.swift */; }; DB63F74727990B0600455B82 /* DataSourceFacade+Hashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F74627990B0600455B82 /* DataSourceFacade+Hashtag.swift */; }; - DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F7482799126300455B82 /* FollowerListViewController+DataSourceProvider.swift */; }; + DB63F74B279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F74A279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift */; }; DB63F74D27993F5B00455B82 /* SearchHistoryUserCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F74C27993F5B00455B82 /* SearchHistoryUserCollectionViewCell.swift */; }; DB63F74F2799405600455B82 /* SearchHistoryViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F74E2799405600455B82 /* SearchHistoryViewModel+Diffable.swift */; }; DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F751279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift */; }; @@ -1021,7 +1021,7 @@ DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = ""; }; DB63F7442799056400455B82 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = ""; }; DB63F74627990B0600455B82 /* DataSourceFacade+Hashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Hashtag.swift"; sourceTree = ""; }; - DB63F7482799126300455B82 /* FollowerListViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowerListViewController+DataSourceProvider.swift"; sourceTree = ""; }; + DB63F74A279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewController+DataSourceProvider.swift"; sourceTree = ""; }; DB63F74C27993F5B00455B82 /* SearchHistoryUserCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryUserCollectionViewCell.swift; sourceTree = ""; }; DB63F74E2799405600455B82 /* SearchHistoryViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchHistoryViewModel+Diffable.swift"; sourceTree = ""; }; DB63F751279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistorySectionHeaderCollectionReusableView.swift; sourceTree = ""; }; @@ -2494,7 +2494,6 @@ isa = PBXGroup; children = ( DB6B74EE272FB55000C70B6E /* FollowerListViewController.swift */, - DB63F7482799126300455B82 /* FollowerListViewController+DataSourceProvider.swift */, DB6B74F1272FB67600C70B6E /* FollowerListViewModel.swift */, DB6B74F3272FBAE700C70B6E /* FollowerListViewModel+Diffable.swift */, DB6B74F5272FBCDB00C70B6E /* FollowerListViewModel+State.swift */, @@ -3738,7 +3737,6 @@ DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */, DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */, 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */, - DB63F7492799126300455B82 /* FollowerListViewController+DataSourceProvider.swift in Sources */, 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */, 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */, DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */, diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index ec05f44da..000000000 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,250 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire.git", - "state": { - "branch": null, - "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", - "version": "5.6.2" - } - }, - { - "package": "AlamofireImage", - "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", - "state": { - "branch": null, - "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", - "version": "4.2.0" - } - }, - { - "package": "FaviconFinder", - "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", - "state": { - "branch": null, - "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", - "version": "3.3.0" - } - }, - { - "package": "FLAnimatedImage", - "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", - "state": { - "branch": null, - "revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f", - "version": "1.0.17" - } - }, - { - "package": "Fuzi", - "repositoryURL": "https://github.com/cezheng/Fuzi.git", - "state": { - "branch": null, - "revision": "f08c8323da21e985f3772610753bcfc652c2103f", - "version": "3.1.3" - } - }, - { - "package": "FXPageControl", - "repositoryURL": "https://github.com/nicklockwood/FXPageControl.git", - "state": { - "branch": null, - "revision": "a94633402ba98c52f86c2a70e61ff086dec9de78", - "version": "1.6.0" - } - }, - { - "package": "Kanna", - "repositoryURL": "https://github.com/tid-kijyun/Kanna.git", - "state": { - "branch": null, - "revision": "f9e4922223dd0d3dfbf02ca70812cf5531fc0593", - "version": "5.2.7" - } - }, - { - "package": "KeychainAccess", - "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", - "state": { - "branch": null, - "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", - "version": "4.2.2" - } - }, - { - "package": "LightChart", - "repositoryURL": "https://github.com/Bearologics/LightChart.git", - "state": { - "branch": "master", - "revision": "a7e724e9ec3cdcaa2d0840b95780e66b870dbf1e", - "version": null - } - }, - { - "package": "MBProgressHUD", - "repositoryURL": "https://github.com/jdg/MBProgressHUD.git", - "state": { - "branch": null, - "revision": "bca42b801100b2b3a4eda0ba8dd33d858c780b0d", - "version": "1.2.0" - } - }, - { - "package": "MetaTextKit", - "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", - "state": { - "branch": null, - "revision": "dcd5255d6930c2fab408dc8562c577547e477624", - "version": "2.2.5" - } - }, - { - "package": "NextLevelSessionExporter", - "repositoryURL": "https://github.com/NextLevel/NextLevelSessionExporter.git", - "state": { - "branch": null, - "revision": "b6c0cce1aa37fe1547d694f958fac3c3524b74da", - "version": "0.4.6" - } - }, - { - "package": "Nuke", - "repositoryURL": "https://github.com/kean/Nuke.git", - "state": { - "branch": null, - "revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97", - "version": "10.11.2" - } - }, - { - "package": "Pageboy", - "repositoryURL": "https://github.com/uias/Pageboy", - "state": { - "branch": null, - "revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", - "version": "3.7.0" - } - }, - { - "package": "PanModal", - "repositoryURL": "https://github.com/slackhq/PanModal.git", - "state": { - "branch": null, - "revision": "b012aecb6b67a8e46369227f893c12544846613f", - "version": "1.2.7" - } - }, - { - "package": "SDWebImage", - "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", - "state": { - "branch": null, - "revision": "3312bf5e67b52fbce7c3caf431b0cda721a9f7bb", - "version": "5.14.2" - } - }, - { - "package": "Stripes", - "repositoryURL": "https://github.com/eneko/Stripes.git", - "state": { - "branch": null, - "revision": "d533fd44b8043a3abbf523e733599173d6f98c11", - "version": "0.2.0" - } - }, - { - "package": "swift-atomics", - "repositoryURL": "https://github.com/apple/swift-atomics.git", - "state": { - "branch": null, - "revision": "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version": "1.1.0" - } - }, - { - "package": "swift-collections", - "repositoryURL": "https://github.com/apple/swift-collections.git", - "state": { - "branch": null, - "revision": "f504716c27d2e5d4144fa4794b12129301d17729", - "version": "1.0.3" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "3db5c4aeee8100d2db6f1eaf3864afdad5dc68fd", - "version": "2.59.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", - "state": { - "branch": null, - "revision": "6778575285177365cbad3e5b8a72f2a20583cfec", - "version": "2.4.3" - } - }, - { - "package": "TabBarPager", - "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git", - "state": { - "branch": null, - "revision": "488aa66d157a648901b61721212c0dec23d27ee5", - "version": "0.1.0" - } - }, - { - "package": "Tabman", - "repositoryURL": "https://github.com/uias/Tabman", - "state": { - "branch": null, - "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465", - "version": "2.13.0" - } - }, - { - "package": "TOCropViewController", - "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", - "state": { - "branch": null, - "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e", - "version": "2.6.1" - } - }, - { - "package": "UIHostingConfigurationBackport", - "repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git", - "state": { - "branch": null, - "revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3", - "version": "0.1.0" - } - }, - { - "package": "UITextView+Placeholder", - "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git", - "state": { - "branch": null, - "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", - "version": "1.4.1" - } - }, - { - "package": "XLPagerTabStrip", - "repositoryURL": "https://github.com/xmartlabs/XLPagerTabStrip.git", - "state": { - "branch": null, - "revision": "211ed62aa376722cf93c429802a8b6ff66a8bd52", - "version": "9.1.0" - } - } - ] - }, - "version": 1 -} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController+DataSourceProvider.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController+DataSourceProvider.swift deleted file mode 100644 index 956cb0704..000000000 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController+DataSourceProvider.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// FollowerListViewController+DataSourceProvider.swift -// Mastodon -// -// Created by MainasuK on 2022-1-20. -// - -import UIKit - -extension FollowerListViewController: DataSourceProvider { - func item(from source: DataSourceItem.Source) async -> DataSourceItem? { - var _indexPath = source.indexPath - if _indexPath == nil, let cell = source.tableViewCell { - _indexPath = await self.indexPath(for: cell) - } - guard let indexPath = _indexPath else { return nil } - - guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { - return nil - } - - switch item { - case .user(let record): - return .user(record: record) - default: - return nil - } - } - - @MainActor - private func indexPath(for cell: UITableViewCell) async -> IndexPath? { - return tableView.indexPath(for: cell) - } -} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift index 733bd1699..d438a0ec0 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -107,3 +107,31 @@ extension FollowerListViewController: UITableViewDelegate, AutoGenerateTableView // MARK: - UserTableViewCellDelegate extension FollowerListViewController: UserTableViewCellDelegate {} + + +// MARK: - DataSourceProvider +extension FollowerListViewController: DataSourceProvider { + func item(from source: DataSourceItem.Source) async -> DataSourceItem? { + var _indexPath = source.indexPath + if _indexPath == nil, let cell = source.tableViewCell { + _indexPath = await self.indexPath(for: cell) + } + guard let indexPath = _indexPath else { return nil } + + guard let item = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { + return nil + } + + switch item { + case .account(let account, let relationship): + return .account(account: account, relationship: relationship) + default: + return nil + } + } + + @MainActor + private func indexPath(for cell: UITableViewCell) async -> IndexPath? { + return tableView.indexPath(for: cell) + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift index d0676dc59..d3384e538 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -8,6 +8,7 @@ import UIKit import MastodonAsset import MastodonLocalization +import MastodonSDK extension FollowerListViewModel { func setupDiffableDataSource( @@ -27,34 +28,43 @@ extension FollowerListViewModel { snapshot.appendSections([.main]) snapshot.appendItems([.bottomLoader], toSection: .main) diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil) - - userFetchedResultsController.$records + + $accounts .receive(on: DispatchQueue.main) - .sink { [weak self] records in - guard let self = self else { return } + .sink { [weak self] accounts in + guard let self else { return } guard let diffableDataSource = self.diffableDataSource else { return } - + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - let items = records.map { UserItem.user(record: $0) } + + let accountsWithRelationship: [(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)] = accounts.compactMap { account in + guard let relationship = self.relationships.first(where: {$0.id == account.id }) else { return (account: account, relationship: nil)} + + return (account: account, relationship: relationship) + } + + let items = accountsWithRelationship.map { UserItem.account(account: $0.account, relationship: $0.relationship) } snapshot.appendItems(items, toSection: .main) - + if let currentState = self.stateMachine.currentState { switch currentState { - case is State.Idle, is State.Loading, is State.Fail: - snapshot.appendItems([.bottomLoader], toSection: .main) - case is State.NoMore: - guard let userID = self.userID, - userID != self.authContext.mastodonAuthenticationBox.userID - else { break } - // display hint footer exclude self - let text = L10n.Scene.Follower.footer - snapshot.appendItems([.bottomHeader(text: text)], toSection: .main) - default: - break + case is State.Loading: + snapshot.appendItems([.bottomLoader], toSection: .main) + case is State.NoMore: + guard let userID = self.userID, + userID != self.authContext.mastodonAuthenticationBox.userID + else { break } + // display footer exclude self + let text = L10n.Scene.Following.footer + snapshot.appendItems([.bottomHeader(text: text)], toSection: .main) + case is State.Idle, is State.Fail: + break + default: + break } } - + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift index fb7b20166..51c189dd8 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -61,8 +61,9 @@ extension FollowerListViewModel.State { guard let viewModel = viewModel, let stateMachine = stateMachine else { return } // reset - viewModel.userFetchedResultsController.userIDs = [] - + viewModel.accounts = [] + viewModel.relationships = [] + stateMachine.enter(Loading.self) } } @@ -123,47 +124,58 @@ extension FollowerListViewModel.State { maxID = nil } - guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let viewModel, let stateMachine else { return } - guard let userID = viewModel.userID, !userID.isEmpty else { + guard let userID = viewModel.userID, userID.isEmpty == false else { stateMachine.enter(Fail.self) return } - + Task { do { - let response = try await viewModel.context.apiService.followers( + let accountResponse = try await viewModel.context.apiService.followers( userID: userID, maxID: maxID, authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) - + var hasNewAppend = false - var userIDs = viewModel.userFetchedResultsController.userIDs - for user in response.value { - guard !userIDs.contains(user.id) else { continue } - userIDs.append(user.id) + + let newRelationships = try await viewModel.context.apiService.relationship(forAccounts: accountResponse.value, authenticationBox: viewModel.authContext.mastodonAuthenticationBox) + + var accounts = viewModel.accounts + + for user in accountResponse.value { + guard accounts.contains(user) == false else { continue } + accounts.append(user) hasNewAppend = true } - - let maxID = response.link?.maxID - - if hasNewAppend && maxID != nil { + + var relationships = viewModel.relationships + + for relationship in newRelationships.value { + guard relationships.contains(relationship) == false else { continue } + relationships.append(relationship) + } + + let maxID = accountResponse.link?.maxID + + if hasNewAppend, maxID != nil { await enter(state: Idle.self) } else { await enter(state: NoMore.self) } - + + viewModel.accounts = accounts + viewModel.relationships = relationships self.maxID = maxID - viewModel.userFetchedResultsController.userIDs = userIDs - } catch { await enter(state: Fail.self) } - } // end Task - } // end func didEnter + } + } } - + class NoMore: FollowerListViewModel.State { override func isValidNextState(_ stateClass: AnyClass) -> Bool { switch stateClass { diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift index ab1144e54..81441da79 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift @@ -19,7 +19,8 @@ final class FollowerListViewModel { // input let context: AppContext let authContext: AuthContext - let userFetchedResultsController: UserFetchedResultsController + @Published var accounts: [Mastodon.Entity.Account] + @Published var relationships: [Mastodon.Entity.Relationship] let listBatchFetchViewModel = ListBatchFetchViewModel() @Published var domain: String? @@ -48,13 +49,9 @@ final class FollowerListViewModel { ) { self.context = context self.authContext = authContext - self.userFetchedResultsController = UserFetchedResultsController( - managedObjectContext: context.managedObjectContext, - domain: domain, - additionalPredicate: nil - ) self.domain = domain self.userID = userID - // end init + self.accounts = [] + self.relationships = [] } }