chore: recommend account use CoreData dateSource

This commit is contained in:
sunxiaojian 2021-04-09 13:09:30 +08:00
parent ae20a29013
commit 0418ec1470
6 changed files with 73 additions and 48 deletions

View File

@ -8,6 +8,8 @@
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import UIKit import UIKit
import CoreData
import CoreDataStack
enum RecommendAccountSection: Equatable, Hashable { enum RecommendAccountSection: Equatable, Hashable {
case main case main
@ -15,10 +17,12 @@ enum RecommendAccountSection: Equatable, Hashable {
extension RecommendAccountSection { extension RecommendAccountSection {
static func collectionViewDiffableDataSource( static func collectionViewDiffableDataSource(
for collectionView: UICollectionView for collectionView: UICollectionView,
) -> UICollectionViewDiffableDataSource<RecommendAccountSection, Mastodon.Entity.Account> { managedObjectContext: NSManagedObjectContext
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, account -> UICollectionViewCell? in ) -> UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, objectID -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell
let account = managedObjectContext.object(with: objectID) as! MastodonUser
cell.config(with: account) cell.config(with: account)
return cell return cell
} }

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import UIKit import UIKit
import CoreDataStack
class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell { class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
let avatarImageView: UIImageView = { let avatarImageView: UIImageView = {
@ -122,7 +123,7 @@ extension SearchRecommendAccountsCollectionViewCell {
]) ])
} }
func config(with account: Mastodon.Entity.Account) { func config(with account: MastodonUser) {
displayNameLabel.text = account.displayName.isEmpty ? account.username : account.displayName displayNameLabel.text = account.displayName.isEmpty ? account.username : account.displayName
acctLabel.text = account.acct acctLabel.text = account.acct
avatarImageView.af.setImage( avatarImageView.af.setImage(

View File

@ -59,8 +59,9 @@ extension SearchViewController: UICollectionViewDelegate {
switch collectionView { switch collectionView {
case self.accountsCollectionView: case self.accountsCollectionView:
guard let diffableDataSource = viewModel.accountDiffableDataSource else { return } guard let diffableDataSource = viewModel.accountDiffableDataSource else { return }
guard let account = diffableDataSource.itemIdentifier(for: indexPath) else { return } guard let accountObjectID = diffableDataSource.itemIdentifier(for: indexPath) else { return }
viewModel.accountCollectionViewItemDidSelected(account: account, from: self) let user = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser
viewModel.accountCollectionViewItemDidSelected(mastodonUser: user, from: self)
case self.hashtagCollectionView: case self.hashtagCollectionView:
guard let diffableDataSource = viewModel.hashtagDiffableDataSource else { return } guard let diffableDataSource = viewModel.hashtagDiffableDataSource else { return }
guard let hashtag = diffableDataSource.itemIdentifier(for: indexPath) else { return } guard let hashtag = diffableDataSource.itemIdentifier(for: indexPath) else { return }

View File

@ -150,7 +150,7 @@ extension SearchViewController {
func setupDataSource() { func setupDataSource() {
viewModel.hashtagDiffableDataSource = RecommendHashTagSection.collectionViewDiffableDataSource(for: hashtagCollectionView) viewModel.hashtagDiffableDataSource = RecommendHashTagSection.collectionViewDiffableDataSource(for: hashtagCollectionView)
viewModel.accountDiffableDataSource = RecommendAccountSection.collectionViewDiffableDataSource(for: accountsCollectionView) viewModel.accountDiffableDataSource = RecommendAccountSection.collectionViewDiffableDataSource(for: accountsCollectionView, managedObjectContext: context.managedObjectContext)
viewModel.searchResultDiffableDataSource = SearchResultSection.tableViewDiffableDataSource(for: searchingTableView, dependency: self) viewModel.searchResultDiffableDataSource = SearchResultSection.tableViewDiffableDataSource(for: searchingTableView, dependency: self)
} }
} }

View File

@ -30,10 +30,10 @@ final class SearchViewModel: NSObject {
let searchResult = CurrentValueSubject<Mastodon.Entity.SearchResult?, Never>(nil) let searchResult = CurrentValueSubject<Mastodon.Entity.SearchResult?, Never>(nil)
var recommendHashTags = [Mastodon.Entity.Tag]() var recommendHashTags = [Mastodon.Entity.Tag]()
var recommendAccounts = [Mastodon.Entity.Account]() var recommendAccounts = [NSManagedObjectID]()
var hashtagDiffableDataSource: UICollectionViewDiffableDataSource<RecommendHashTagSection, Mastodon.Entity.Tag>? var hashtagDiffableDataSource: UICollectionViewDiffableDataSource<RecommendHashTagSection, Mastodon.Entity.Tag>?
var accountDiffableDataSource: UICollectionViewDiffableDataSource<RecommendAccountSection, Mastodon.Entity.Account>? var accountDiffableDataSource: UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID>?
var searchResultDiffableDataSource: UITableViewDiffableDataSource<SearchResultSection, SearchResultItem>? var searchResultDiffableDataSource: UITableViewDiffableDataSource<SearchResultSection, SearchResultItem>?
// bottom loader // bottom loader
@ -52,7 +52,7 @@ final class SearchViewModel: NSObject {
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<LoadOldestState?, Never>(nil) lazy var loadOldestStateMachinePublisher = CurrentValueSubject<LoadOldestState?, Never>(nil)
init(context: AppContext,coordinator: SceneCoordinator) { init(context: AppContext, coordinator: SceneCoordinator) {
self.coordinator = coordinator self.coordinator = coordinator
self.context = context self.context = context
super.init() super.init()
@ -102,7 +102,7 @@ final class SearchViewModel: NSObject {
searchText, searchText,
searchScope searchScope
) )
.filter { isSearching, text, _ in .filter { isSearching, _, _ in
isSearching isSearching
} }
.sink { [weak self] _, text, scope in .sink { [weak self] _, text, scope in
@ -151,7 +151,7 @@ final class SearchViewModel: NSObject {
guard let self = self else { return } guard let self = self else { return }
if !self.recommendAccounts.isEmpty { if !self.recommendAccounts.isEmpty {
guard let dataSource = self.accountDiffableDataSource else { return } guard let dataSource = self.accountDiffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, Mastodon.Entity.Account>() var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, NSManagedObjectID>()
snapshot.appendSections([.main]) snapshot.appendSections([.main])
snapshot.appendItems(self.recommendAccounts, toSection: .main) snapshot.appendItems(self.recommendAccounts, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: false, completion: nil) dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
@ -170,7 +170,7 @@ final class SearchViewModel: NSObject {
snapshot.appendSections([.account]) snapshot.appendSections([.account])
let items = accounts.compactMap { SearchResultItem.account(account: $0) } let items = accounts.compactMap { SearchResultItem.account(account: $0) }
snapshot.appendItems(items, toSection: .account) snapshot.appendItems(items, toSection: .account)
if self.searchScope.value == Mastodon.API.Search.SearchType.accounts && !items.isEmpty { if self.searchScope.value == Mastodon.API.Search.SearchType.accounts, !items.isEmpty {
snapshot.appendItems([.bottomLoader], toSection: .account) snapshot.appendItems([.bottomLoader], toSection: .account)
} }
} }
@ -178,7 +178,7 @@ final class SearchViewModel: NSObject {
snapshot.appendSections([.hashtag]) snapshot.appendSections([.hashtag])
let items = tags.compactMap { SearchResultItem.hashtag(tag: $0) } let items = tags.compactMap { SearchResultItem.hashtag(tag: $0) }
snapshot.appendItems(items, toSection: .hashtag) snapshot.appendItems(items, toSection: .hashtag)
if self.searchScope.value == Mastodon.API.Search.SearchType.hashtags && !items.isEmpty { if self.searchScope.value == Mastodon.API.Search.SearchType.hashtags, !items.isEmpty {
snapshot.appendItems([.bottomLoader], toSection: .hashtag) snapshot.appendItems([.bottomLoader], toSection: .hashtag)
} }
} }
@ -229,49 +229,45 @@ final class SearchViewModel: NSObject {
} }
} receiveValue: { [weak self] accounts in } receiveValue: { [weak self] accounts in
guard let self = self else { return } guard let self = self else { return }
self.recommendAccounts = accounts.value let ids = accounts.value.compactMap({$0.id})
} let userFetchRequest = MastodonUser.sortedFetchRequest
.store(in: &self.disposeBag) userFetchRequest.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, ids: ids)
} let mastodonUsers: [MastodonUser]? = {
} let userFetchRequest = MastodonUser.sortedFetchRequest
userFetchRequest.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, ids: ids)
func accountCollectionViewItemDidSelected(account: Mastodon.Entity.Account, from: UIViewController) { userFetchRequest.returnsObjectsAsFaults = false
_ = context.managedObjectContext.performChanges { [weak self] in
guard let self = self else { return }
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
// load request mastodon user
let requestMastodonUser: MastodonUser? = {
let request = MastodonUser.sortedFetchRequest
request.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, id: activeMastodonAuthenticationBox.userID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do { do {
return try self.context.managedObjectContext.fetch(request).first return try self.context.managedObjectContext.fetch(userFetchRequest)
} catch { } catch {
assertionFailure(error.localizedDescription) assertionFailure(error.localizedDescription)
return nil return nil
} }
}() }()
let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.context.managedObjectContext, for: requestMastodonUser, in: activeMastodonAuthenticationBox.domain, entity: account, userCache: nil, networkDate: Date(), log: OSLog.api) if let users = mastodonUsers {
let viewModel = ProfileViewModel(context: self.context, optionalMastodonUser: mastodonUser) self.recommendAccounts = users.map(\.objectID)
}
}
.store(in: &self.disposeBag)
}
}
func accountCollectionViewItemDidSelected(mastodonUser: MastodonUser, from: UIViewController) {
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
DispatchQueue.main.async { DispatchQueue.main.async {
self.coordinator.present(scene: .profile(viewModel: viewModel), from: from, transition: .show) self.coordinator.present(scene: .profile(viewModel: viewModel), from: from, transition: .show)
} }
} }
}
func hashtagCollectionViewItemDidSelected(hashtag: Mastodon.Entity.Tag, from: UIViewController) { func hashtagCollectionViewItemDidSelected(hashtag: Mastodon.Entity.Tag, from: UIViewController) {
let (tagInCoreData,_) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: hashtag) let (tagInCoreData, _) = APIService.CoreData.createOrMergeTag(into: context.managedObjectContext, entity: hashtag)
let viewModel = HashtagTimelineViewModel(context: self.context, hashtag: tagInCoreData.name) let viewModel = HashtagTimelineViewModel(context: context, hashtag: tagInCoreData.name)
DispatchQueue.main.async { DispatchQueue.main.async {
self.coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: from, transition: .show) self.coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: from, transition: .show)
} }
} }
func searchResultItemDidSelected(item: SearchResultItem,from: UIViewController) { func searchResultItemDidSelected(item: SearchResultItem, from: UIViewController) {
let searchHistories = self.fetchSearchHistory() let searchHistories = fetchSearchHistory()
_ = context.managedObjectContext.performChanges { [weak self] in _ = context.managedObjectContext.performChanges { [weak self] in
guard let self = self else { return } guard let self = self else { return }
switch item { switch item {
@ -312,7 +308,7 @@ final class SearchViewModel: NSObject {
} }
case .hashtag(let tag): case .hashtag(let tag):
let (tagInCoreData,_) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: tag) let (tagInCoreData, _) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: tag)
if let searchHistories = searchHistories { if let searchHistories = searchHistories {
let history = searchHistories.first { history -> Bool in let history = searchHistories.first { history -> Bool in
guard let hashtag = history.hashtag else { return false } guard let hashtag = history.hashtag else { return false }

View File

@ -5,12 +5,14 @@
// Created by sxiaojian on 2021/3/31. // Created by sxiaojian on 2021/3/31.
// //
import Combine
import Foundation import Foundation
import MastodonSDK import MastodonSDK
import Combine import CoreData
import CoreDataStack
import OSLog
extension APIService { extension APIService {
func recommendAccount( func recommendAccount(
domain: String, domain: String,
query: Mastodon.API.Suggestions.Query?, query: Mastodon.API.Suggestions.Query?,
@ -19,12 +21,33 @@ extension APIService {
let authorization = mastodonAuthenticationBox.userAuthorization let authorization = mastodonAuthenticationBox.userAuthorization
return Mastodon.API.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization) return Mastodon.API.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> in
let log = OSLog.api
return self.backgroundManagedObjectContext.performChanges {
response.value.forEach { user in
let (mastodonUser,isCreated) = APIService.CoreData.createOrMergeMastodonUser(into: self.backgroundManagedObjectContext, for: nil, in: domain, entity: user, userCache: nil, networkDate: Date(), log: log)
let flag = isCreated ? "+" : "-"
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: fetch mastodon user [%s](%s)%s", (#file as NSString).lastPathComponent, #line, #function, flag, mastodonUser.id, mastodonUser.username)
}
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Account]> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
} }
func recommendTrends( func recommendTrends(
domain: String, domain: String,
query: Mastodon.API.Trends.Query? query: Mastodon.API.Trends.Query?
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> {
return Mastodon.API.Trends.get(session: session, domain: domain, query: query) Mastodon.API.Trends.get(session: session, domain: domain, query: query)
} }
} }