chore: recommend account use CoreData dateSource
This commit is contained in:
parent
ae20a29013
commit
0418ec1470
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue