Merge pull request #98 from tootsuite/feature/searchFollow

Feature/search follow
This commit is contained in:
sxiaojian88 2021-04-13 09:22:39 +08:00 committed by GitHub
commit c3070d9594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 56 deletions

View File

@ -135,6 +135,7 @@
5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1057825F88A1D00D6C0D4 /* PlayerContainerView.swift */; };
5DF1057F25F88A4100D6C0D4 /* TouchBlockingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1057E25F88A4100D6C0D4 /* TouchBlockingView.swift */; };
5DF1058525F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */; };
5DFC35DF262068D20045711D /* SearchViewController+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DFC35DE262068D20045711D /* SearchViewController+Follow.swift */; };
5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; };
5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; };
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
@ -506,6 +507,7 @@
5DF1057825F88A1D00D6C0D4 /* PlayerContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerContainerView.swift; sourceTree = "<group>"; };
5DF1057E25F88A4100D6C0D4 /* TouchBlockingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBlockingView.swift; sourceTree = "<group>"; };
5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeedsDependency+AVPlayerViewControllerDelegate.swift"; sourceTree = "<group>"; };
5DFC35DE262068D20045711D /* SearchViewController+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+Follow.swift"; sourceTree = "<group>"; };
75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.release.xcconfig"; sourceTree = "<group>"; };
9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = "<group>"; };
A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1613,6 +1615,7 @@
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */,
2D34D9CA261489930081BFC0 /* SearchViewController+Recommend.swift */,
2DFAD5262616F9D300F9EE7C /* SearchViewController+Searching.swift */,
5DFC35DE262068D20045711D /* SearchViewController+Follow.swift */,
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */,
2D198654261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift */,
2D34D9E026149C550081BFC0 /* CollectionViewCell */,
@ -2355,6 +2358,7 @@
2D42FF6B25C817D2004A627A /* MastodonStatusContent.swift in Sources */,
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */,
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */,
5DFC35DF262068D20045711D /* SearchViewController+Follow.swift in Sources */,
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */,

View File

@ -5,6 +5,8 @@
// Created by sxiaojian on 2021/4/1.
//
import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
import UIKit
@ -15,11 +17,15 @@ enum RecommendAccountSection: Equatable, Hashable {
extension RecommendAccountSection {
static func collectionViewDiffableDataSource(
for collectionView: UICollectionView
) -> UICollectionViewDiffableDataSource<RecommendAccountSection, Mastodon.Entity.Account> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, account -> UICollectionViewCell? in
for collectionView: UICollectionView,
delegate: SearchRecommendAccountsCollectionViewCellDelegate,
managedObjectContext: NSManagedObjectContext
) -> UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak delegate] collectionView, indexPath, objectID -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell
cell.config(with: account)
let user = managedObjectContext.object(with: objectID) as! MastodonUser
cell.delegate = delegate
cell.config(with: user)
return cell
}
}

View File

@ -5,11 +5,23 @@
// Created by sxiaojian on 2021/4/1.
//
import Combine
import CoreDataStack
import Foundation
import MastodonSDK
import UIKit
protocol SearchRecommendAccountsCollectionViewCellDelegate: NSObject {
func followButtonDidPressed(clickedUser: MastodonUser)
func configFollowButton(with mastodonUser: MastodonUser, followButton: HighlightDimmableButton)
}
class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
var disposeBag = Set<AnyCancellable>()
weak var delegate: SearchRecommendAccountsCollectionViewCellDelegate?
let avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 8.4
@ -47,8 +59,8 @@ class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
return label
}()
let followButton: UIButton = {
let button = UIButton(type: .custom)
let followButton: HighlightDimmableButton = {
let button = HighlightDimmableButton(type: .custom)
button.setTitleColor(.white, for: .normal)
button.setTitle(L10n.Scene.Search.Recommend.Accounts.follow, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .semibold)
@ -63,6 +75,7 @@ class SearchRecommendAccountsCollectionViewCell: UICollectionViewCell {
headerImageView.af.cancelImageRequest()
avatarImageView.af.cancelImageRequest()
visualEffectView.removeFromSuperview()
disposeBag.removeAll()
}
override init(frame: CGRect) {
@ -122,22 +135,33 @@ extension SearchRecommendAccountsCollectionViewCell {
])
}
func config(with account: Mastodon.Entity.Account) {
displayNameLabel.text = account.displayName.isEmpty ? account.username : account.displayName
acctLabel.text = account.acct
func config(with mastodonUser: MastodonUser) {
displayNameLabel.text = mastodonUser.displayName.isEmpty ? mastodonUser.username : mastodonUser.displayName
acctLabel.text = mastodonUser.acct
avatarImageView.af.setImage(
withURL: URL(string: account.avatar)!,
withURL: URL(string: mastodonUser.avatar)!,
placeholderImage: UIImage.placeholder(color: .systemFill),
imageTransition: .crossDissolve(0.2)
)
headerImageView.af.setImage(
withURL: URL(string: account.header)!,
withURL: URL(string: mastodonUser.header)!,
placeholderImage: UIImage.placeholder(color: .systemFill),
imageTransition: .crossDissolve(0.2)) { [weak self] _ in
imageTransition: .crossDissolve(0.2)
) { [weak self] _ in
guard let self = self else { return }
self.headerImageView.addSubview(self.visualEffectView)
self.visualEffectView.pin(top: 0, left: 0, bottom: 0, right: 0)
}
delegate?.configFollowButton(with: mastodonUser, followButton: followButton)
followButton.publisher(for: .touchUpInside)
.sink { [weak self] _ in
self?.followButtonDidPressed(mastodonUser: mastodonUser)
}
.store(in: &disposeBag)
}
func followButtonDidPressed(mastodonUser: MastodonUser) {
delegate?.followButtonDidPressed(clickedUser: mastodonUser)
}
}

View File

@ -0,0 +1,137 @@
//
// SearchViewController+Follow.swift
// Mastodon
//
// Created by xiaojian sun on 2021/4/9.
//
import Combine
import CoreDataStack
import Foundation
import UIKit
extension SearchViewController: UserProvider {
func mastodonUser() -> Future<MastodonUser?, Never> {
Future { promise in
promise(.success(self.viewModel.mastodonUser.value))
}
}
}
extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegate {
func followButtonDidPressed(clickedUser: MastodonUser) {
viewModel.mastodonUser.value = clickedUser
guard let currentMastodonUser = viewModel.currentMastodonUser.value else {
return
}
guard let relationshipAction = relationShipActionSet(mastodonUser: clickedUser, currentMastodonUser: currentMastodonUser).highPriorityAction(except: .editOptions) else { return }
switch relationshipAction {
case .none:
break
case .follow, .following:
UserProviderFacade.toggleUserFollowRelationship(provider: self)
.sink { _ in
} receiveValue: { _ in
}
.store(in: &disposeBag)
case .pending:
break
case .muting:
guard let mastodonUser = viewModel.mastodonUser.value else { return }
let name = mastodonUser.displayNameWithFallback
let alertController = UIAlertController(
title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title,
message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name),
preferredStyle: .alert
)
let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in
guard let self = self else { return }
UserProviderFacade.toggleUserMuteRelationship(provider: self)
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &self.context.disposeBag)
}
alertController.addAction(unmuteAction)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
case .blocking:
guard let mastodonUser = viewModel.mastodonUser.value else { return }
let name = mastodonUser.displayNameWithFallback
let alertController = UIAlertController(
title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUsre.title,
message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUsre.message(name),
preferredStyle: .alert
)
let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in
guard let self = self else { return }
UserProviderFacade.toggleUserBlockRelationship(provider: self)
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &self.context.disposeBag)
}
alertController.addAction(unblockAction)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
case .blocked:
break
default:
assertionFailure()
}
}
func configFollowButton(with mastodonUser: MastodonUser, followButton: HighlightDimmableButton) {
guard let currentMastodonUser = viewModel.currentMastodonUser.value else {
return
}
_configFollowButton(with: mastodonUser, currentMastodonUser: currentMastodonUser, followButton: followButton)
ManagedObjectObserver.observe(object: currentMastodonUser)
.sink { _ in
} receiveValue: { change in
guard case .update(let object) = change.changeType,
let newUser = object as? MastodonUser else { return }
self._configFollowButton(with: mastodonUser, currentMastodonUser: newUser, followButton: followButton)
}
.store(in: &disposeBag)
}
}
extension SearchViewController {
func _configFollowButton(with mastodonUser: MastodonUser, currentMastodonUser: MastodonUser, followButton: HighlightDimmableButton) {
let relationshipActionSet = relationShipActionSet(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
followButton.setTitle(relationshipActionSet.title, for: .normal)
}
func relationShipActionSet(mastodonUser: MastodonUser, currentMastodonUser: MastodonUser) -> ProfileViewModel.RelationshipActionOptionSet {
var relationshipActionSet = ProfileViewModel.RelationshipActionOptionSet([.follow])
let isFollowing = mastodonUser.followingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
if isFollowing {
relationshipActionSet.insert(.following)
}
let isPending = mastodonUser.followRequestedBy.flatMap { $0.contains(currentMastodonUser) } ?? false
if isPending {
relationshipActionSet.insert(.pending)
}
let isBlocking = mastodonUser.blockingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
if isBlocking {
relationshipActionSet.insert(.blocking)
}
let isBlockedBy = currentMastodonUser.blockingBy.flatMap { $0.contains(mastodonUser) } ?? false
if isBlockedBy {
relationshipActionSet.insert(.blocked)
}
return relationshipActionSet
}
}

View File

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

View File

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

View File

@ -21,6 +21,9 @@ final class SearchViewModel: NSObject {
let context: AppContext
weak var coordinator: SceneCoordinator!
let mastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
// output
let searchText = CurrentValueSubject<String, Never>("")
let searchScope = CurrentValueSubject<Mastodon.API.Search.SearchType, Never>(Mastodon.API.Search.SearchType.default)
@ -30,10 +33,10 @@ final class SearchViewModel: NSObject {
let searchResult = CurrentValueSubject<Mastodon.Entity.SearchResult?, Never>(nil)
var recommendHashTags = [Mastodon.Entity.Tag]()
var recommendAccounts = [Mastodon.Entity.Account]()
var recommendAccounts = [NSManagedObjectID]()
var hashtagDiffableDataSource: UICollectionViewDiffableDataSource<RecommendHashTagSection, Mastodon.Entity.Tag>?
var accountDiffableDataSource: UICollectionViewDiffableDataSource<RecommendAccountSection, Mastodon.Entity.Account>?
var accountDiffableDataSource: UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID>?
var searchResultDiffableDataSource: UITableViewDiffableDataSource<SearchResultSection, SearchResultItem>?
// bottom loader
@ -52,7 +55,7 @@ final class SearchViewModel: NSObject {
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<LoadOldestState?, Never>(nil)
init(context: AppContext,coordinator: SceneCoordinator) {
init(context: AppContext, coordinator: SceneCoordinator) {
self.coordinator = coordinator
self.context = context
super.init()
@ -60,6 +63,19 @@ final class SearchViewModel: NSObject {
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
// bind active authentication
context.authenticationService.activeMastodonAuthentication
.sink { [weak self] activeMastodonAuthentication in
guard let self = self else { return }
guard let activeMastodonAuthentication = activeMastodonAuthentication else {
self.currentMastodonUser.value = nil
return
}
self.currentMastodonUser.value = activeMastodonAuthentication.user
}
.store(in: &disposeBag)
Publishers.CombineLatest(
searchText
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main).removeDuplicates(),
@ -102,7 +118,7 @@ final class SearchViewModel: NSObject {
searchText,
searchScope
)
.filter { isSearching, text, _ in
.filter { isSearching, _, _ in
isSearching
}
.sink { [weak self] _, text, scope in
@ -151,7 +167,7 @@ final class SearchViewModel: NSObject {
guard let self = self else { return }
if !self.recommendAccounts.isEmpty {
guard let dataSource = self.accountDiffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, Mastodon.Entity.Account>()
var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, NSManagedObjectID>()
snapshot.appendSections([.main])
snapshot.appendItems(self.recommendAccounts, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
@ -170,7 +186,7 @@ final class SearchViewModel: NSObject {
snapshot.appendSections([.account])
let items = accounts.compactMap { SearchResultItem.account(account: $0) }
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)
}
}
@ -178,7 +194,7 @@ final class SearchViewModel: NSObject {
snapshot.appendSections([.hashtag])
let items = tags.compactMap { SearchResultItem.hashtag(tag: $0) }
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)
}
}
@ -229,49 +245,45 @@ final class SearchViewModel: NSObject {
}
} receiveValue: { [weak self] accounts in
guard let self = self else { return }
self.recommendAccounts = accounts.value
let ids = accounts.value.compactMap({$0.id})
let userFetchRequest = MastodonUser.sortedFetchRequest
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)
userFetchRequest.returnsObjectsAsFaults = false
do {
return try self.context.managedObjectContext.fetch(userFetchRequest)
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
if let users = mastodonUsers {
self.recommendAccounts = users.map(\.objectID)
}
}
.store(in: &self.disposeBag)
}
}
func accountCollectionViewItemDidSelected(account: Mastodon.Entity.Account, from: UIViewController) {
_ = 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 {
return try self.context.managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
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)
let viewModel = ProfileViewModel(context: self.context, optionalMastodonUser: mastodonUser)
DispatchQueue.main.async {
self.coordinator.present(scene: .profile(viewModel: viewModel), from: from, transition: .show)
}
func accountCollectionViewItemDidSelected(mastodonUser: MastodonUser, from: UIViewController) {
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
DispatchQueue.main.async {
self.coordinator.present(scene: .profile(viewModel: viewModel), from: from, transition: .show)
}
}
func hashtagCollectionViewItemDidSelected(hashtag: Mastodon.Entity.Tag, from: UIViewController) {
let (tagInCoreData,_) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: hashtag)
let viewModel = HashtagTimelineViewModel(context: self.context, hashtag: tagInCoreData.name)
let (tagInCoreData, _) = APIService.CoreData.createOrMergeTag(into: context.managedObjectContext, entity: hashtag)
let viewModel = HashtagTimelineViewModel(context: context, hashtag: tagInCoreData.name)
DispatchQueue.main.async {
self.coordinator.present(scene: .hashtagTimeline(viewModel: viewModel), from: from, transition: .show)
}
}
func searchResultItemDidSelected(item: SearchResultItem,from: UIViewController) {
let searchHistories = self.fetchSearchHistory()
func searchResultItemDidSelected(item: SearchResultItem, from: UIViewController) {
let searchHistories = fetchSearchHistory()
_ = context.managedObjectContext.performChanges { [weak self] in
guard let self = self else { return }
switch item {
@ -312,7 +324,7 @@ final class SearchViewModel: NSObject {
}
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 {
let history = searchHistories.first { history -> Bool in
guard let hashtag = history.hashtag else { return false }

View File

@ -5,12 +5,14 @@
// Created by sxiaojian on 2021/3/31.
//
import Combine
import Foundation
import MastodonSDK
import Combine
import CoreData
import CoreDataStack
import OSLog
extension APIService {
func recommendAccount(
domain: String,
query: Mastodon.API.Suggestions.Query?,
@ -19,12 +21,33 @@ extension APIService {
let authorization = mastodonAuthenticationBox.userAuthorization
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(
domain: String,
query: Mastodon.API.Trends.Query?
) -> 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)
}
}