From a304fb21086eb8eb3ab4f2bfbb2ef5554c54108e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Sun, 17 Sep 2023 12:48:58 +0200 Subject: [PATCH] Show profile-page for suggested accounts in search (IOS-141) --- Mastodon/Diffable/User/UserSection.swift | 49 +++++----- .../Provider/DataSourceFacade+Profile.swift | 2 +- ...chResultsOverviewTableViewController.swift | 92 +++++++++++++------ .../SearchResult/SearchResultSection.swift | 2 +- .../UserTableViewCell+ViewModel.swift | 83 ++++++++--------- 5 files changed, 130 insertions(+), 98 deletions(-) diff --git a/Mastodon/Diffable/User/UserSection.swift b/Mastodon/Diffable/User/UserSection.swift index cbad1ff72..24f13ddc6 100644 --- a/Mastodon/Diffable/User/UserSection.swift +++ b/Mastodon/Diffable/User/UserSection.swift @@ -39,30 +39,31 @@ extension UserSection { return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in switch item { - case .user(let record): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - configure( - context: context, - authContext: authContext, - tableView: tableView, - cell: cell, - viewModel: UserTableViewCell.ViewModel(value: .user(user), - followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), - blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), - followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher() - ), - configuration: configuration - ) - } - - return cell - case .bottomLoader: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell - cell.startAnimating() - return cell - case .bottomHeader(let text): + case .user(let record): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell + context.managedObjectContext.performAndWait { + guard let user = record.object(in: context.managedObjectContext) else { return } + configure( + context: context, + authContext: authContext, + tableView: tableView, + cell: cell, + viewModel: UserTableViewCell.ViewModel( + user: user, + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher() + ), + configuration: configuration + ) + } + + return cell + case .bottomLoader: + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell + cell.startAnimating() + return cell + case .bottomHeader(let text): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineFooterTableViewCell.self), for: indexPath) as! TimelineFooterTableViewCell cell.messageLabel.text = text return cell diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index f434bc6ac..161e52e87 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -33,7 +33,7 @@ extension DataSourceFacade { @MainActor static func coordinateToProfileScene( - provider: DataSourceProvider & AuthContextProvider, + provider: NeedsDependency & UIViewController & AuthContextProvider, user: ManagedObjectRecord ) async { guard let user = user.object(in: provider.context.managedObjectContext) else { diff --git a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift index a4b8b123b..b3026e279 100644 --- a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultsOverviewTableViewController.swift @@ -51,36 +51,52 @@ class SearchResultsOverviewTableViewController: UIViewController, NeedsDependenc case .suggestion(let suggestion): - guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultDefaultSectionTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultDefaultSectionTableViewCell else { fatalError() } + switch suggestion { - cell.configure(item: suggestion) - return cell + case .hashtag(let hashtag): -// switch suggestion { -// -// case .hashtag(let hashtag): -// -// case .profile(let profile): -// //TODO: Use `UserFetchedResultsController` or `Persistence.MastodonUser.fetch` ??? -// -// guard let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as? UserTableViewCell else { fatalError() } + + guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultDefaultSectionTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultDefaultSectionTableViewCell else { fatalError() } + + cell.configure(item: .hashtag(tag: hashtag)) + return cell + + // + case .profile(let profile): + guard let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as? UserTableViewCell else { fatalError() } // how the fuck do I get a MastodonUser??? -// try await managedObjectContext.perform { -// Persistence.MastodonUser.fetch(in: managedObjectContext, -// context: Persistence.MastodonUser.PersistContext( -// domain: domain, -// entity: profile.value, -// cache: nil, -// networkDate: profile.netwo -// )) -// } -// + let managedObjectContext = appContext.managedObjectContext + Task { + do { - // cell.configure(me: <#T##MastodonUser?#>, tableView: <#T##UITableView#>, viewModel: <#T##UserTableViewCell.ViewModel#>, delegate: <#T##UserTableViewCellDelegate?#>) + try await managedObjectContext.perform { + guard let user = Persistence.MastodonUser.fetch(in: managedObjectContext, + context: Persistence.MastodonUser.PersistContext( + domain: authContext.mastodonAuthenticationBox.domain, + entity: profile, + cache: nil, + networkDate: Date() + )) else { return } -// return cell -// } + cell.configure( + me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user, + tableView: tableView, + viewModel: UserTableViewCell.ViewModel( + user: user, + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()), + delegate: nil) + } + } + catch { + // do nothing + } + } + + return cell + } } } @@ -183,6 +199,28 @@ class SearchResultsOverviewTableViewController: UIViewController, NeedsDependenc ) } } + + func showProfile(for account: Mastodon.Entity.Account) { + let managedObjectContext = context.managedObjectContext + let domain = authContext.mastodonAuthenticationBox.domain + + Task { + let user = try await managedObjectContext.perform { + return Persistence.MastodonUser.fetch(in: managedObjectContext, + context: Persistence.MastodonUser.PersistContext( + domain: domain, + entity: account, + cache: nil, + networkDate: Date() + )) + } + + if let user { + await DataSourceFacade.coordinateToProfileScene(provider:self, + user: user.asRecord) + } + } + } } //MARK: UITableViewDelegate @@ -198,8 +236,10 @@ extension SearchResultsOverviewTableViewController: UITableViewDelegate { case .default(let defaultSectionEntry): switch defaultSectionEntry { case .posts(let hashtag): + //FIXME: Show statuses instead of tag-content. Reuse SearchResultsViewController with statuses here? showPosts(tag: Mastodon.Entity.Tag(name: hashtag, url: authContext.mastodonAuthenticationBox.domain)) case .people(let string): + //FIXME: Invoke SearchResultsViewController with people-scope here delegate?.showPeople(self) case .profile(let profile, let instanceName): delegate?.showProfile(self) @@ -211,8 +251,8 @@ extension SearchResultsOverviewTableViewController: UITableViewDelegate { case .hashtag(let tag): showPosts(tag: tag) - case .profile(_): - delegate?.showProfile(self) + case .profile(let account): + showProfile(for: account) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift index 90560150e..49eeddb32 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift @@ -53,7 +53,7 @@ extension SearchResultSection { authContext: authContext, tableView: tableView, cell: cell, - viewModel: UserTableViewCell.ViewModel(value: .user(user), + viewModel: UserTableViewCell.ViewModel(user: user, followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()), diff --git a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift index b83b8b47d..7f2727a91 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift @@ -12,72 +12,63 @@ import Combine extension UserTableViewCell { final class ViewModel { - let value: Value + let user: MastodonUser let followedUsers: AnyPublisher<[String], Never> let blockedUsers: AnyPublisher<[String], Never> let followRequestedUsers: AnyPublisher<[String], Never> - init(value: Value, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { - self.value = value + init(user: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { + self.user = user self.followedUsers = followedUsers self.followRequestedUsers = followRequestedUsers self.blockedUsers = blockedUsers } - - enum Value { - case user(MastodonUser) - // case status(Status) - } } } extension UserTableViewCell { func configure( - me: MastodonUser?, + me: MastodonUser? = nil, tableView: UITableView, viewModel: ViewModel, delegate: UserTableViewCellDelegate? ) { - switch viewModel.value { - case .user(let user): - userView.configure(user: user, delegate: delegate) - - guard let me = me else { - return userView.setButtonState(.none) - } - - if user == me { - userView.setButtonState(.none) - } else { - userView.setButtonState(.loading) - } - - Publishers.CombineLatest3( - viewModel.followedUsers, - viewModel.followRequestedUsers, - viewModel.blockedUsers - ) - .receive(on: DispatchQueue.main) - .sink { [weak self] followed, requested, blocked in - if blocked.contains(user.id) { - self?.userView.setButtonState(.blocked) - } else if followed.contains(user.id) { - self?.userView.setButtonState(.unfollow) - } else if requested.contains(user.id) { - self?.userView.setButtonState(.pending) - } else if user.locked { - self?.userView.setButtonState(.request) - } else if user != me { - self?.userView.setButtonState(.follow) - } - } - .store(in: &disposeBag) - + userView.configure(user: viewModel.user, delegate: delegate) + + guard let me = me else { + return userView.setButtonState(.none) } - - self.delegate = delegate + + if viewModel.user == me { + userView.setButtonState(.none) + } else { + userView.setButtonState(.loading) + } + + Publishers.CombineLatest3( + viewModel.followedUsers, + viewModel.followRequestedUsers, + viewModel.blockedUsers + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] followed, requested, blocked in + if blocked.contains(viewModel.user.id) { + self?.userView.setButtonState(.blocked) + } else if followed.contains(viewModel.user.id) { + self?.userView.setButtonState(.unfollow) + } else if requested.contains(viewModel.user.id) { + self?.userView.setButtonState(.pending) + } else if viewModel.user.locked { + self?.userView.setButtonState(.request) + } else if viewModel.user != me { + self?.userView.setButtonState(.follow) + } + } + .store(in: &disposeBag) + + self.delegate = delegate } }