fix: [WIP] add suggestion account scene back

This commit is contained in:
CMK 2022-02-10 19:30:41 +08:00
parent c1e1d527fe
commit 54e84ed814
23 changed files with 541 additions and 477 deletions

View File

@ -483,6 +483,8 @@
DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */; }; DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */; };
DBB45B5B27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */; }; DBB45B5B27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */; };
DBB45B5E27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5D27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift */; }; DBB45B5E27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5D27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift */; };
DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */; };
DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */; };
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; }; DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; }; DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; }; DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
@ -1227,6 +1229,8 @@
DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewVideoViewModel.swift; sourceTree = "<group>"; }; DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewVideoViewModel.swift; sourceTree = "<group>"; };
DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewTransitionViewController.swift; sourceTree = "<group>"; }; DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewTransitionViewController.swift; sourceTree = "<group>"; };
DBB45B5D27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveMarginStatusTableViewCell.swift; sourceTree = "<group>"; }; DBB45B5D27B4EB22002DC5A7 /* AdaptiveMarginStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveMarginStatusTableViewCell.swift; sourceTree = "<group>"; };
DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendAccountItem.swift; sourceTree = "<group>"; };
DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountViewModel+Diffable.swift"; sourceTree = "<group>"; };
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; }; DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; }; DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; }; DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
@ -1756,6 +1760,7 @@
children = ( children = (
2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */, 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */,
2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */, 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */,
DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */,
2D4AD89A2631659400613EFC /* CollectionViewCell */, 2D4AD89A2631659400613EFC /* CollectionViewCell */,
2DAC9E43262FC9DE0062E1A6 /* TableViewCell */, 2DAC9E43262FC9DE0062E1A6 /* TableViewCell */,
); );
@ -2012,6 +2017,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */, 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */,
DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */,
); );
path = RecommandAccount; path = RecommandAccount;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3845,6 +3851,7 @@
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */, DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */,
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */, DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */, DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */,
DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */,
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */, DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */, DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */,
DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */, DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */,
@ -4068,6 +4075,7 @@
DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */, DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */,
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */, DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */, DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */,
DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */,
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */, 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
DB6180E626391B550018D199 /* MediaPreviewTransitionController.swift in Sources */, DB6180E626391B550018D199 /* MediaPreviewTransitionController.swift in Sources */,
DB0FCB922796DE19006C02E2 /* TrendSectionHeaderCollectionReusableView.swift in Sources */, DB0FCB922796DE19006C02E2 /* TrendSectionHeaderCollectionReusableView.swift in Sources */,

View File

@ -7,32 +7,9 @@
import CoreData import CoreData
import Foundation import Foundation
import CoreDataStack
enum SelectedAccountItem { enum SelectedAccountItem: Hashable {
case accountObjectID(accountObjectID: NSManagedObjectID) case account(ManagedObjectRecord<MastodonUser>)
case placeHolder(uuid: UUID) case placeHolder(uuid: UUID)
} }
extension SelectedAccountItem: Equatable {
static func == (lhs: SelectedAccountItem, rhs: SelectedAccountItem) -> Bool {
switch (lhs, rhs) {
case (.accountObjectID(let idLeft), .accountObjectID(let idRight)):
return idLeft == idRight
case (.placeHolder(let uuidLeft), .placeHolder(let uuidRight)):
return uuidLeft == uuidRight
default:
return false
}
}
}
extension SelectedAccountItem: Hashable {
func hash(into hasher: inout Hasher) {
switch self {
case .accountObjectID(let id):
hasher.combine(id)
case .placeHolder(let id):
hasher.combine(id.uuidString)
}
}
}

View File

@ -17,15 +17,17 @@ enum SelectedAccountSection: Equatable, Hashable {
extension SelectedAccountSection { extension SelectedAccountSection {
static func collectionViewDiffableDataSource( static func collectionViewDiffableDataSource(
for collectionView: UICollectionView, collectionView: UICollectionView,
managedObjectContext: NSManagedObjectContext context: AppContext
) -> UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem> { ) -> UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self), for: indexPath) as! SuggestionAccountCollectionViewCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self), for: indexPath) as! SuggestionAccountCollectionViewCell
switch item { switch item {
case .accountObjectID(let objectID): case .account(let record):
let user = managedObjectContext.object(with: objectID) as! MastodonUser context.managedObjectContext.performAndWait {
cell.config(with: user) guard let user = record.object(in: context.managedObjectContext) else { return }
cell.config(with: user)
}
case .placeHolder: case .placeHolder:
cell.configAsPlaceHolder() cell.configAsPlaceHolder()
} }

View File

@ -20,15 +20,15 @@ final class UserFetchedResultsController: NSObject {
let fetchedResultsController: NSFetchedResultsController<MastodonUser> let fetchedResultsController: NSFetchedResultsController<MastodonUser>
// input // input
let domain = CurrentValueSubject<String?, Never>(nil) @Published var domain: String? = nil
let userIDs = CurrentValueSubject<[Mastodon.Entity.Account.ID], Never>([]) @Published var userIDs: [Mastodon.Entity.Account.ID] = []
// output // output
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([]) let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
@Published var records: [ManagedObjectRecord<MastodonUser>] = [] @Published var records: [ManagedObjectRecord<MastodonUser>] = []
init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate?) { init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate?) {
self.domain.value = domain ?? "" self.domain = domain ?? ""
self.fetchedResultsController = { self.fetchedResultsController = {
let fetchRequest = MastodonUser.sortedFetchRequest let fetchRequest = MastodonUser.sortedFetchRequest
fetchRequest.predicate = MastodonUser.predicate(domain: domain ?? "", ids: []) fetchRequest.predicate = MastodonUser.predicate(domain: domain ?? "", ids: [])
@ -54,8 +54,8 @@ final class UserFetchedResultsController: NSObject {
fetchedResultsController.delegate = self fetchedResultsController.delegate = self
Publishers.CombineLatest( Publishers.CombineLatest(
self.domain.removeDuplicates(), self.$domain.removeDuplicates(),
self.userIDs.removeDuplicates() self.$userIDs.removeDuplicates()
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] domain, ids in .sink { [weak self] domain, ids in
@ -79,11 +79,11 @@ final class UserFetchedResultsController: NSObject {
extension UserFetchedResultsController { extension UserFetchedResultsController {
public func append(userIDs: [Mastodon.Entity.Account.ID]) { public func append(userIDs: [Mastodon.Entity.Account.ID]) {
var result = self.userIDs.value var result = self.userIDs
for userID in userIDs where !result.contains(userID) { for userID in userIDs where !result.contains(userID) {
result.append(userID) result.append(userID)
} }
self.userIDs.value = result self.userIDs = result
} }
} }
@ -93,7 +93,7 @@ extension UserFetchedResultsController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let indexes = userIDs.value let indexes = userIDs
let objects = fetchedResultsController.fetchedObjects ?? [] let objects = fetchedResultsController.fetchedObjects ?? []
let items: [NSManagedObjectID] = objects let items: [NSManagedObjectID] = objects

View File

@ -0,0 +1,13 @@
//
// RecommendAccountItem.swift
// Mastodon
//
// Created by MainasuK on 2022-2-10.
//
import Foundation
import CoreDataStack
enum RecommendAccountItem: Hashable {
case account(ManagedObjectRecord<MastodonUser>)
}

View File

@ -129,22 +129,29 @@ enum RecommendAccountSection: Equatable, Hashable {
// //
//} //}
// //
//extension RecommendAccountSection { extension RecommendAccountSection {
//
// static func tableViewDiffableDataSource( struct Configuration {
// for tableView: UITableView, weak var suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate?
// managedObjectContext: NSManagedObjectContext, }
// viewModel: SuggestionAccountViewModel,
// delegate: SuggestionAccountTableViewCellDelegate static func tableViewDiffableDataSource(
// ) -> UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> { tableView: UITableView,
// UITableViewDiffableDataSource(tableView: tableView) { [weak viewModel, weak delegate] (tableView, indexPath, objectID) -> UITableViewCell? in context: AppContext,
// guard let viewModel = viewModel else { return nil } configuration: Configuration
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell ) -> UITableViewDiffableDataSource<RecommendAccountSection, RecommendAccountItem> {
// let user = managedObjectContext.object(with: objectID) as! MastodonUser UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
// let isSelected = viewModel.selectedAccounts.value.contains(objectID) let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell
// cell.delegate = delegate switch item {
// cell.config(with: user, isSelected: isSelected) case .account(let record):
// return cell context.managedObjectContext.performAndWait {
// } guard let user = record.object(in: context.managedObjectContext) else { return }
// } cell.config(with: user)
//} }
}
cell.delegate = configuration.suggestionAccountTableViewCellDelegate
return cell
}
}
}

View File

@ -74,6 +74,15 @@ extension HomeTimelineViewController {
guard let self = self else { return } guard let self = self else { return }
self.showThreadAction(action) self.showThreadAction(action)
}, },
UIAction(title: "Account Recommend", image: UIImage(systemName: "human"), attributes: []) { [weak self] action in
guard let self = self else { return }
let suggestionAccountViewModel = SuggestionAccountViewModel(context: self.context)
self.coordinator.present(
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
from: self,
transition: .modal(animated: true, completion: nil)
)
},
UIAction(title: "Store Rating", image: UIImage(systemName: "star.fill"), attributes: []) { [weak self] action in UIAction(title: "Store Rating", image: UIImage(systemName: "star.fill"), attributes: []) { [weak self] action in
guard let self = self else { return } guard let self = self else { return }
guard let windowScene = self.view.window?.windowScene else { return } guard let windowScene = self.view.window?.windowScene else { return }

View File

@ -383,10 +383,13 @@ extension HomeTimelineViewController {
extension HomeTimelineViewController { extension HomeTimelineViewController {
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) { @objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
// TODO: let suggestionAccountViewModel = SuggestionAccountViewModel(context: context)
// let viewModel = SuggestionAccountViewModel(context: context) suggestionAccountViewModel.delegate = viewModel
// viewModel.delegate = self.viewModel coordinator.present(
// coordinator.present(scene: .suggestionAccount(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil)) scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
from: self,
transition: .modal(animated: true, completion: nil)
)
} }
@objc private func manuallySearchButtonPressed(_ sender: UIButton) { @objc private func manuallySearchButtonPressed(_ sender: UIButton) {

View File

@ -119,8 +119,6 @@ final class HomeTimelineViewModel: NSObject {
} }
//extension HomeTimelineViewModel: SuggestionAccountViewModelDelegate { }
extension HomeTimelineViewModel { extension HomeTimelineViewModel {
struct ScrollPositionRecord { struct ScrollPositionRecord {
let item: StatusItem let item: StatusItem
@ -197,3 +195,9 @@ extension HomeTimelineViewModel {
} }
} }
// MARK: - SuggestionAccountViewModelDelegate
extension HomeTimelineViewModel: SuggestionAccountViewModelDelegate {
}

View File

@ -72,7 +72,7 @@ extension FollowerListViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return } guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
// reset // reset
viewModel.userFetchedResultsController.userIDs.value = [] viewModel.userFetchedResultsController.userIDs = []
stateMachine.enter(Loading.self) stateMachine.enter(Loading.self)
} }
@ -158,7 +158,7 @@ extension FollowerListViewModel.State {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count) followers") logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count) followers")
var hasNewAppend = false var hasNewAppend = false
var userIDs = viewModel.userFetchedResultsController.userIDs.value var userIDs = viewModel.userFetchedResultsController.userIDs
for user in response.value { for user in response.value {
guard !userIDs.contains(user.id) else { continue } guard !userIDs.contains(user.id) else { continue }
userIDs.append(user.id) userIDs.append(user.id)
@ -174,7 +174,7 @@ extension FollowerListViewModel.State {
} }
self.maxID = maxID self.maxID = maxID
viewModel.userFetchedResultsController.userIDs.value = userIDs viewModel.userFetchedResultsController.userIDs = userIDs
} catch { } catch {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch follower fail: \(error.localizedDescription)") logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch follower fail: \(error.localizedDescription)")

View File

@ -72,7 +72,7 @@ extension FollowingListViewModel.State {
guard let viewModel = viewModel, let stateMachine = stateMachine else { return } guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
// reset // reset
viewModel.userFetchedResultsController.userIDs.value = [] viewModel.userFetchedResultsController.userIDs = []
stateMachine.enter(Loading.self) stateMachine.enter(Loading.self)
} }
@ -159,7 +159,7 @@ extension FollowingListViewModel.State {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count)") logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count)")
var hasNewAppend = false var hasNewAppend = false
var userIDs = viewModel.userFetchedResultsController.userIDs.value var userIDs = viewModel.userFetchedResultsController.userIDs
for user in response.value { for user in response.value {
guard !userIDs.contains(user.id) else { continue } guard !userIDs.contains(user.id) else { continue }
userIDs.append(user.id) userIDs.append(user.id)
@ -174,7 +174,7 @@ extension FollowingListViewModel.State {
await enter(state: NoMore.self) await enter(state: NoMore.self)
} }
self.maxID = maxID self.maxID = maxID
viewModel.userFetchedResultsController.userIDs.value = userIDs viewModel.userFetchedResultsController.userIDs = userIDs
} catch { } catch {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch following fail: \(error.localizedDescription)") logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch following fail: \(error.localizedDescription)")

View File

@ -20,12 +20,12 @@ final class MeProfileViewModel: ProfileViewModel {
optionalMastodonUser: context.authenticationService.activeMastodonAuthentication.value?.user optionalMastodonUser: context.authenticationService.activeMastodonAuthentication.value?.user
) )
self.currentMastodonUser $me
.sink { [weak self] currentMastodonUser in .sink { [weak self] me in
os_log("%{public}s[%{public}ld], %{public}s: current active mastodon user: %s", ((#file as NSString).lastPathComponent), #line, #function, currentMastodonUser?.username ?? "<nil>") os_log("%{public}s[%{public}ld], %{public}s: current active mastodon user: %s", ((#file as NSString).lastPathComponent), #line, #function, me?.username ?? "<nil>")
guard let self = self else { return } guard let self = self else { return }
self.mastodonUser.value = currentMastodonUser self.user = me
} }
.store(in: &disposeBag) .store(in: &disposeBag)
} }

View File

@ -577,7 +577,7 @@ extension ProfileViewController {
private func bindProfileRelationship() { private func bindProfileRelationship() {
Publishers.CombineLatest( Publishers.CombineLatest(
viewModel.mastodonUser, viewModel.$user,
viewModel.relationshipActionOptionSet viewModel.relationshipActionOptionSet
) )
.asyncMap { [weak self] user, relationshipSet -> UIMenu? in .asyncMap { [weak self] user, relationshipSet -> UIMenu? in
@ -725,7 +725,7 @@ extension ProfileViewController {
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) { @objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID) let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
Task { Task {
let _activityViewController = try await DataSourceFacade.createActivityViewController( let _activityViewController = try await DataSourceFacade.createActivityViewController(
@ -754,7 +754,7 @@ extension ProfileViewController {
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) { @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let mastodonUser = viewModel.mastodonUser.value else { return } guard let mastodonUser = viewModel.user else { return }
let composeViewModel = ComposeViewModel( let composeViewModel = ComposeViewModel(
context: context, context: context,
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)), composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
@ -849,7 +849,7 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate {
// MARK: - ProfileHeaderViewDelegate // MARK: - ProfileHeaderViewDelegate
extension ProfileViewController: ProfileHeaderViewDelegate { extension ProfileViewController: ProfileHeaderViewDelegate {
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarButtonDidPressed button: AvatarButton) { func profileHeaderView(_ profileHeaderView: ProfileHeaderView, avatarButtonDidPressed button: AvatarButton) {
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID) let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
Task { Task {
@ -865,7 +865,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
} }
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView) { func profileHeaderView(_ profileHeaderView: ProfileHeaderView, bannerImageViewDidPressed imageView: UIImageView) {
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID) let record: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
Task { Task {
@ -956,7 +956,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
case .none: case .none:
break break
case .follow, .request, .pending, .following: case .follow, .request, .pending, .following:
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID) let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
Task { Task {
@ -968,7 +968,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
} }
case .muting: case .muting:
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let name = user.displayNameWithFallback let name = user.displayNameWithFallback
let alertController = UIAlertController( let alertController = UIAlertController(
@ -993,7 +993,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
present(alertController, animated: true, completion: nil) present(alertController, animated: true, completion: nil)
case .blocking: case .blocking:
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let name = user.displayNameWithFallback let name = user.displayNameWithFallback
let alertController = UIAlertController( let alertController = UIAlertController(
@ -1077,7 +1077,7 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate {
extension ProfileViewController: MastodonMenuDelegate { extension ProfileViewController: MastodonMenuDelegate {
func menuAction(_ action: MastodonMenu.Action) { func menuAction(_ action: MastodonMenu.Action) {
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let user = viewModel.mastodonUser.value else { return } guard let user = viewModel.user else { return }
let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID) let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)

View File

@ -28,8 +28,8 @@ class ProfileViewModel: NSObject {
// input // input
let context: AppContext let context: AppContext
let mastodonUser: CurrentValueSubject<MastodonUser?, Never> @Published var me: MastodonUser?
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil) @Published var user: MastodonUser?
let viewDidAppear = PassthroughSubject<Void, Never>() let viewDidAppear = PassthroughSubject<Void, Never>()
// output // output
@ -73,7 +73,7 @@ class ProfileViewModel: NSObject {
init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) { init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) {
self.context = context self.context = context
self.mastodonUser = CurrentValueSubject(mastodonUser) self.user = mastodonUser
self.domain = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value?.domain) self.domain = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value?.domain)
self.userID = CurrentValueSubject(mastodonUser?.id) self.userID = CurrentValueSubject(mastodonUser?.id)
self.bannerImageURL = CurrentValueSubject(mastodonUser?.headerImageURL()) self.bannerImageURL = CurrentValueSubject(mastodonUser?.headerImageURL())
@ -98,21 +98,21 @@ class ProfileViewModel: NSObject {
.store(in: &disposeBag) .store(in: &disposeBag)
// bind active authentication // bind active authentication
context.authenticationService.activeMastodonAuthentication context.authenticationService.activeMastodonAuthenticationBox
.sink { [weak self] activeMastodonAuthentication in .sink { [weak self] authenticationBox in
guard let self = self else { return } guard let self = self else { return }
guard let activeMastodonAuthentication = activeMastodonAuthentication else { guard let authenticationBox = authenticationBox else {
self.domain.value = nil self.domain.value = nil
self.currentMastodonUser.value = nil self.me = nil
return return
} }
self.domain.value = activeMastodonAuthentication.domain self.domain.value = authenticationBox.domain
self.currentMastodonUser.value = activeMastodonAuthentication.user self.me = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
} }
.store(in: &disposeBag) .store(in: &disposeBag)
// query relationship // query relationship
let userRecord = self.mastodonUser.map { user -> ManagedObjectRecord<MastodonUser>? in let userRecord = $user.map { user -> ManagedObjectRecord<MastodonUser>? in
user.flatMap { ManagedObjectRecord<MastodonUser>(objectID: $0.objectID) } user.flatMap { ManagedObjectRecord<MastodonUser>(objectID: $0.objectID) }
} }
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1) let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
@ -176,18 +176,18 @@ class ProfileViewModel: NSObject {
extension ProfileViewModel { extension ProfileViewModel {
private func setup() { private func setup() {
Publishers.CombineLatest( Publishers.CombineLatest(
mastodonUser.eraseToAnyPublisher(), $user,
currentMastodonUser.eraseToAnyPublisher() $me
) )
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] mastodonUser, currentMastodonUser in .sink { [weak self] user, me in
guard let self = self else { return } guard let self = self else { return }
// Update view model attribute // Update view model attribute
self.update(mastodonUser: mastodonUser) self.update(mastodonUser: user)
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser) self.update(mastodonUser: user, currentMastodonUser: me)
// Setup observer for user // Setup observer for user
if let mastodonUser = mastodonUser { if let mastodonUser = user {
// setup observer // setup observer
self.mastodonUserObserver = ManagedObjectObserver.observe(object: mastodonUser) self.mastodonUserObserver = ManagedObjectObserver.observe(object: mastodonUser)
.sink { completion in .sink { completion in
@ -203,7 +203,7 @@ extension ProfileViewModel {
switch changeType { switch changeType {
case .update: case .update:
self.update(mastodonUser: mastodonUser) self.update(mastodonUser: mastodonUser)
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser) self.update(mastodonUser: mastodonUser, currentMastodonUser: me)
case .delete: case .delete:
// TODO: // TODO:
break break
@ -215,7 +215,7 @@ extension ProfileViewModel {
} }
// Setup observer for user // Setup observer for user
if let currentMastodonUser = currentMastodonUser { if let currentMastodonUser = me {
// setup observer // setup observer
self.currentMastodonUserObserver = ManagedObjectObserver.observe(object: currentMastodonUser) self.currentMastodonUserObserver = ManagedObjectObserver.observe(object: currentMastodonUser)
.sink { completion in .sink { completion in
@ -230,7 +230,7 @@ extension ProfileViewModel {
guard let changeType = change.changeType else { return } guard let changeType = change.changeType else { return }
switch changeType { switch changeType {
case .update: case .update:
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser) self.update(mastodonUser: user, currentMastodonUser: currentMastodonUser)
case .delete: case .delete:
// TODO: // TODO:
break break
@ -347,13 +347,14 @@ extension ProfileViewModel {
// fetch profile info before edit // fetch profile info before edit
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> { func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
guard let currentMastodonUser = currentMastodonUser.value, guard let me = me,
let mastodonAuthentication = currentMastodonUser.mastodonAuthentication else { let mastodonAuthentication = me.mastodonAuthentication
else {
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
} }
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken) let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
return context.apiService.accountVerifyCredentials(domain: currentMastodonUser.domain, authorization: authorization) return context.apiService.accountVerifyCredentials(domain: me.domain, authorization: authorization)
} }
private func updateRelationship( private func updateRelationship(

View File

@ -48,7 +48,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
assertionFailure() assertionFailure()
return return
} }
self.mastodonUser.value = mastodonUser self.user = mastodonUser
} }
.store(in: &disposeBag) .store(in: &disposeBag)
} }

View File

@ -153,7 +153,7 @@ extension SearchDetailViewController {
assertionFailure() assertionFailure()
break break
case .people: case .people:
viewController.viewModel.userFetchedResultsController.userIDs.value = allSearchScopeViewController.viewModel.userFetchedResultsController.userIDs.value viewController.viewModel.userFetchedResultsController.userIDs = allSearchScopeViewController.viewModel.userFetchedResultsController.userIDs
case .hashtags: case .hashtags:
viewController.viewModel.hashtags = allSearchScopeViewController.viewModel.hashtags viewController.viewModel.hashtags = allSearchScopeViewController.viewModel.hashtags
case .posts: case .posts:

View File

@ -155,7 +155,7 @@ extension SearchResultViewModel.State {
// reset data source when the search is refresh // reset data source when the search is refresh
if offset == nil { if offset == nil {
viewModel.userFetchedResultsController.userIDs.value = [] viewModel.userFetchedResultsController.userIDs = []
viewModel.statusFetchedResultsController.statusIDs.value = [] viewModel.statusFetchedResultsController.statusIDs.value = []
viewModel.hashtags = [] viewModel.hashtags = []
} }

View File

@ -63,7 +63,7 @@ final class SearchResultViewModel {
context.authenticationService.activeMastodonAuthenticationBox context.authenticationService.activeMastodonAuthenticationBox
.map { $0?.domain } .map { $0?.domain }
.assign(to: \.value, on: userFetchedResultsController.domain) .assign(to: \.domain, on: userFetchedResultsController)
.store(in: &disposeBag) .store(in: &disposeBag)
context.authenticationService.activeMastodonAuthenticationBox context.authenticationService.activeMastodonAuthenticationBox

View File

@ -15,12 +15,43 @@ import MastodonAsset
import MastodonLocalization import MastodonLocalization
class SuggestionAccountViewController: UIViewController, NeedsDependency { class SuggestionAccountViewController: UIViewController, NeedsDependency {
static let collectionViewHeight: CGFloat = 24 + 64 + 24
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
var viewModel: SuggestionAccountViewModel! var viewModel: SuggestionAccountViewModel!
private static func createCollectionViewLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(64), heightDimension: .absolute(64))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 0, bottom: 24, trailing: 0)
section.orthogonalScrollingBehavior = .continuous
section.contentInsetsReference = .readableContent
section.interGroupSpacing = 16
return UICollectionViewCompositionalLayout(section: section)
}
let collectionView: UICollectionView = {
let collectionViewLayout = SuggestionAccountViewController.createCollectionViewLayout()
let view = ControlContainableCollectionView(
frame: .zero,
collectionViewLayout: collectionViewLayout
)
view.register(SuggestionAccountCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self))
view.backgroundColor = .clear
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.layer.masksToBounds = false
return view
}()
let tableView: UITableView = { let tableView: UITableView = {
let tableView = ControlContainableTableView() let tableView = ControlContainableTableView()
@ -32,34 +63,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency {
return tableView return tableView
}() }()
lazy var tableHeader: UIView = {
let view = UIView()
view.backgroundColor = ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor
view.frame = CGRect(origin: .zero, size: CGSize(width: tableView.frame.width, height: 156))
return view
}()
let followExplainLabel: UILabel = {
let label = UILabel()
label.text = L10n.Scene.SuggestionAccount.followExplain
label.textColor = Asset.Colors.Label.primary.color
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
label.numberOfLines = 0
return label
}()
let selectedCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
view.register(SuggestionAccountCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self))
view.backgroundColor = .clear
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.layer.masksToBounds = false
return view
}()
deinit { deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", (#file as NSString).lastPathComponent, #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", (#file as NSString).lastPathComponent, #line, #function)
} }
@ -68,164 +71,135 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency {
extension SuggestionAccountViewController { extension SuggestionAccountViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
fatalError()
// setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
// ThemeService.shared.currentTheme ThemeService.shared.currentTheme
// .receive(on: RunLoop.main) .receive(on: RunLoop.main)
// .sink { [weak self] theme in .sink { [weak self] theme in
// guard let self = self else { return } guard let self = self else { return }
// self.setupBackgroundColor(theme: theme) self.setupBackgroundColor(theme: theme)
// } }
// .store(in: &disposeBag) .store(in: &disposeBag)
//
// title = L10n.Scene.SuggestionAccount.title title = L10n.Scene.SuggestionAccount.title
// navigationItem.rightBarButtonItem navigationItem.rightBarButtonItem = UIBarButtonItem(
// = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, barButtonSystemItem: UIBarButtonItem.SystemItem.done,
// target: self, target: self,
// action: #selector(SuggestionAccountViewController.doneButtonDidClick(_:))) action: #selector(SuggestionAccountViewController.doneButtonDidClick(_:))
// )
// tableView.delegate = self
// tableView.translatesAutoresizingMaskIntoConstraints = false collectionView.translatesAutoresizingMaskIntoConstraints = false
// view.addSubview(tableView) view.addSubview(collectionView)
// NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
// tableView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
// tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), collectionView.heightAnchor.constraint(equalToConstant: SuggestionAccountViewController.collectionViewHeight),
// ]) ])
// viewModel.diffableDataSource = RecommendAccountSection.tableViewDiffableDataSource( defer { view.bringSubviewToFront(collectionView) }
// for: tableView,
// managedObjectContext: context.managedObjectContext, tableView.translatesAutoresizingMaskIntoConstraints = false
// viewModel: viewModel, view.addSubview(tableView)
// delegate: self NSLayoutConstraint.activate([
// ) tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor),
// tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
// viewModel.collectionDiffableDataSource = SelectedAccountSection.collectionViewDiffableDataSource(for: selectedCollectionView, managedObjectContext: context.managedObjectContext) tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// viewModel.accounts ])
// .receive(on: DispatchQueue.main)
// .sink { [weak self] accounts in collectionView.delegate = self
// guard let self = self else { return } viewModel.setupDiffableDataSource(
// self.setupHeader(accounts: accounts) collectionView: collectionView
// } )
// .store(in: &disposeBag)
tableView.delegate = self
viewModel.setupDiffableDataSource(
tableView: tableView,
suggestionAccountTableViewCellDelegate: self
)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
tableView.deselectRow(with: transitionCoordinator, animated: animated) tableView.deselectRow(with: transitionCoordinator, animated: animated)
viewModel.checkAccountsFollowState()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let avatarImageViewHeight: Double = 56
let avatarImageViewCount = Int(floor((Double(view.frame.width) - 20) / (avatarImageViewHeight + 15)))
viewModel.headerPlaceholderCount.value = avatarImageViewCount
}
func setupHeader(accounts: [NSManagedObjectID]) {
if accounts.isEmpty {
return
}
followExplainLabel.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(followExplainLabel)
NSLayoutConstraint.activate([
followExplainLabel.topAnchor.constraint(equalTo: tableHeader.topAnchor, constant: 20),
followExplainLabel.leadingAnchor.constraint(equalTo: tableHeader.leadingAnchor, constant: 20),
tableHeader.trailingAnchor.constraint(equalTo: followExplainLabel.trailingAnchor, constant: 20),
])
selectedCollectionView.translatesAutoresizingMaskIntoConstraints = false
tableHeader.addSubview(selectedCollectionView)
NSLayoutConstraint.activate([
selectedCollectionView.frameLayoutGuide.topAnchor.constraint(equalTo: followExplainLabel.topAnchor, constant: 20),
selectedCollectionView.frameLayoutGuide.leadingAnchor.constraint(equalTo: tableHeader.leadingAnchor, constant: 20),
selectedCollectionView.frameLayoutGuide.trailingAnchor.constraint(equalTo: tableHeader.trailingAnchor),
selectedCollectionView.frameLayoutGuide.bottomAnchor.constraint(equalTo: tableHeader.bottomAnchor),
])
selectedCollectionView.delegate = self
tableView.tableHeaderView = tableHeader
} }
private func setupBackgroundColor(theme: Theme) { private func setupBackgroundColor(theme: Theme) {
view.backgroundColor = theme.systemBackgroundColor view.backgroundColor = theme.systemBackgroundColor
tableHeader.backgroundColor = theme.systemGroupedBackgroundColor collectionView.backgroundColor = theme.systemGroupedBackgroundColor
} }
} }
extension SuggestionAccountViewController: UICollectionViewDelegateFlowLayout { // MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { extension SuggestionAccountViewController: UICollectionViewDelegate {
15
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: 56, height: 56)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let diffableDataSource = viewModel.collectionDiffableDataSource else { return } // guard let diffableDataSource = viewModel.collectionDiffableDataSource else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } // guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
switch item { // switch item {
case .accountObjectID(let accountObjectID): // case .accountObjectID(let accountObjectID):
let mastodonUser = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser // let mastodonUser = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser) // let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
DispatchQueue.main.async { // DispatchQueue.main.async {
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) // self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
} // }
default: // default:
break // break
} // }
} }
} }
// MARK: - UITableViewDelegate
extension SuggestionAccountViewController: UITableViewDelegate { extension SuggestionAccountViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let tableViewDiffableDataSource = viewModel.tableViewDiffableDataSource else { return }
guard let objectID = diffableDataSource.itemIdentifier(for: indexPath) else { return } guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }
let mastodonUser = context.managedObjectContext.object(with: objectID) as! MastodonUser switch item {
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser) case .account(let record):
DispatchQueue.main.async { guard let account = record.object(in: context.managedObjectContext) else { return }
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) let cachedProfileViewModel = CachedProfileViewModel(context: context, mastodonUser: account)
coordinator.present(
scene: .profile(viewModel: cachedProfileViewModel),
from: self,
transition: .show
)
} }
} }
} }
extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate { extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate {
func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell) { func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell) {
let selected = !viewModel.selectedAccounts.value.contains(objectID) // let selected = !viewModel.selectedAccounts.value.contains(objectID)
cell.startAnimating() // cell.startAnimating()
viewModel.followAction(objectID: objectID)? // viewModel.followAction(objectID: objectID)?
.sink(receiveCompletion: { [weak self] completion in // .sink(receiveCompletion: { [weak self] completion in
guard let self = self else { return } // guard let self = self else { return }
cell.stopAnimating() // cell.stopAnimating()
switch completion { // switch completion {
case .failure(let error): // case .failure(let error):
os_log("%{public}s[%{public}ld], %{public}s: follow failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription) // os_log("%{public}s[%{public}ld], %{public}s: follow failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
case .finished: // case .finished:
var selectedAccounts = self.viewModel.selectedAccounts.value // var selectedAccounts = self.viewModel.selectedAccounts.value
if selected { // if selected {
selectedAccounts.append(objectID) // selectedAccounts.append(objectID)
} else { // } else {
selectedAccounts.removeAll { $0 == objectID } // selectedAccounts.removeAll { $0 == objectID }
} // }
cell.button.isSelected = selected // cell.button.isSelected = selected
self.viewModel.selectedAccounts.value = selectedAccounts // self.viewModel.selectedAccounts.value = selectedAccounts
} // }
}, receiveValue: { _ in // }, receiveValue: { _ in
}) // })
.store(in: &disposeBag) // .store(in: &disposeBag)
} }
} }
extension SuggestionAccountViewController { extension SuggestionAccountViewController {
@objc func doneButtonDidClick(_ sender: UIButton) { @objc func doneButtonDidClick(_ sender: UIButton) {
dismiss(animated: true, completion: nil) dismiss(animated: true, completion: nil)
if viewModel.selectedAccounts.value.count > 0 { // if viewModel.selectedAccounts.value.count > 0 {
viewModel.delegate?.homeTimelineNeedRefresh.send() // viewModel.delegate?.homeTimelineNeedRefresh.send()
} // }
} }
} }

View File

@ -0,0 +1,74 @@
//
// SuggestionAccountViewModel+Diffable.swift
// Mastodon
//
// Created by MainasuK on 2022-2-10.
//
import UIKit
extension SuggestionAccountViewModel {
func setupDiffableDataSource(
tableView: UITableView,
suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate
) {
tableViewDiffableDataSource = RecommendAccountSection.tableViewDiffableDataSource(
tableView: tableView,
context: context,
configuration: RecommendAccountSection.Configuration(
suggestionAccountTableViewCellDelegate: suggestionAccountTableViewCellDelegate
)
)
userFetchedResultsController.$records
.receive(on: DispatchQueue.main)
.sink { [weak self] records in
guard let self = self else { return }
guard let tableViewDiffableDataSource = self.tableViewDiffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, RecommendAccountItem>()
snapshot.appendSections([.main])
let items: [RecommendAccountItem] = records.map { RecommendAccountItem.account($0) }
snapshot.appendItems(items, toSection: .main)
if #available(iOS 15.0, *) {
tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil)
} else {
// Fallback on earlier versions
tableViewDiffableDataSource.applySnapshot(snapshot, animated: false, completion: nil)
}
}
.store(in: &disposeBag)
}
func setupDiffableDataSource(
collectionView: UICollectionView
) {
collectionViewDiffableDataSource = SelectedAccountSection.collectionViewDiffableDataSource(
collectionView: collectionView,
context: context
)
userFetchedResultsController.$records
.receive(on: DispatchQueue.main)
.sink { [weak self] records in
guard let self = self else { return }
guard let collectionViewDiffableDataSource = self.collectionViewDiffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<SelectedAccountSection, SelectedAccountItem>()
snapshot.appendSections([.main])
let items: [SelectedAccountItem] = records.map { SelectedAccountItem.account($0) }
snapshot.appendItems(items, toSection: .main)
if #available(iOS 15.0, *) {
collectionViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil)
} else {
// Fallback on earlier versions
collectionViewDiffableDataSource.applySnapshot(snapshot, animated: false, completion: nil)
}
}
.store(in: &disposeBag)
}
}

View File

@ -20,177 +20,175 @@ protocol SuggestionAccountViewModelDelegate: AnyObject {
final class SuggestionAccountViewModel: NSObject { final class SuggestionAccountViewModel: NSObject {
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
weak var delegate: SuggestionAccountViewModelDelegate?
// input // input
let context: AppContext let context: AppContext
let userFetchedResultsController: UserFetchedResultsController
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil) var viewWillAppear = PassthroughSubject<Void, Never>()
weak var delegate: SuggestionAccountViewModelDelegate?
// output
let accounts = CurrentValueSubject<[NSManagedObjectID], Never>([])
var selectedAccounts = CurrentValueSubject<[NSManagedObjectID], Never>([])
// output
var collectionViewDiffableDataSource: UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem>?
var tableViewDiffableDataSource: UITableViewDiffableDataSource<RecommendAccountSection, RecommendAccountItem>?
@Published var selectedAccounts: [ManagedObjectRecord<MastodonUser>] = []
var headerPlaceholderCount = CurrentValueSubject<Int?, Never>(nil) var headerPlaceholderCount = CurrentValueSubject<Int?, Never>(nil)
var suggestionAccountsFallback = PassthroughSubject<Void, Never>() var suggestionAccountsFallback = PassthroughSubject<Void, Never>()
var viewWillAppear = PassthroughSubject<Void, Never>()
var diffableDataSource: UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID>? { init(
didSet(value) { context: AppContext
if !accounts.value.isEmpty { ) {
applyTableViewDataSource(accounts: accounts.value)
}
}
}
var collectionDiffableDataSource: UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem>?
init(context: AppContext, accounts: [NSManagedObjectID]? = nil) {
self.context = context self.context = context
self.userFetchedResultsController = UserFetchedResultsController(
managedObjectContext: context.managedObjectContext,
domain: nil,
additionalTweetPredicate: nil
)
super.init() super.init()
Publishers.CombineLatest( // Publishers.CombineLatest(
self.accounts, // $accounts,
self.selectedAccounts // $selectedAccounts
) // )
.receive(on: RunLoop.main) // .receive(on: RunLoop.main)
.sink { [weak self] accounts,selectedAccounts in // .sink { [weak self] accounts,selectedAccounts in
self?.applyTableViewDataSource(accounts: accounts) // self?.applyTableViewDataSource(accounts: accounts)
self?.applySelectedCollectionViewDataSource(accounts: selectedAccounts) // self?.applySelectedCollectionViewDataSource(accounts: selectedAccounts)
} // }
.store(in: &disposeBag) // .store(in: &disposeBag)
Publishers.CombineLatest( // Publishers.CombineLatest(
self.selectedAccounts, // self.selectedAccounts,
self.headerPlaceholderCount // self.headerPlaceholderCount
) // )
.receive(on: RunLoop.main) // .receive(on: RunLoop.main)
.sink { [weak self] selectedAccount,count in // .sink { [weak self] selectedAccount,count in
self?.applySelectedCollectionViewDataSource(accounts: selectedAccount) // self?.applySelectedCollectionViewDataSource(accounts: selectedAccount)
} // }
.store(in: &disposeBag) // .store(in: &disposeBag)
//
// viewWillAppear
// .sink { [weak self] _ in
// self?.checkAccountsFollowState()
// }
// .store(in: &disposeBag)
//
// 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)
viewWillAppear guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
.sink { [weak self] _ in
self?.checkAccountsFollowState()
}
.store(in: &disposeBag)
if let accounts = accounts {
self.accounts.value = accounts
}
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)
if accounts == nil || (accounts ?? []).isEmpty {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
context.apiService.suggestionAccountV2(domain: activeMastodonAuthenticationBox.domain, query: nil, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
if let apiError = error as? Mastodon.API.Error {
if apiError.httpResponseStatus == .notFound {
self?.suggestionAccountsFallback.send()
}
}
os_log("%{public}s[%{public}ld], %{public}s: fetch recommendAccountV2 failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
case .finished:
// handle isFetchingLatestTimeline in fetch controller delegate
break
}
} receiveValue: { [weak self] response in
let ids = response.value.map(\.account.id)
self?.receiveAccounts(ids: ids)
}
.store(in: &disposeBag)
suggestionAccountsFallback
.sink(receiveValue: { [weak self] _ in
self?.requestSuggestionAccount()
})
.store(in: &disposeBag)
}
}
func requestSuggestionAccount() {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
context.apiService.suggestionAccount(domain: activeMastodonAuthenticationBox.domain, query: nil, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
.sink { completion in
switch completion {
case .failure(let error):
os_log("%{public}s[%{public}ld], %{public}s: fetch recommendAccount failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
case .finished:
// handle isFetchingLatestTimeline in fetch controller delegate
break
}
} receiveValue: { [weak self] response in
let ids = response.value.map(\.id)
self?.receiveAccounts(ids: ids)
}
.store(in: &disposeBag)
}
func applyTableViewDataSource(accounts: [NSManagedObjectID]) {
assert(Thread.isMainThread)
guard let dataSource = diffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, NSManagedObjectID>()
snapshot.appendSections([.main])
snapshot.appendItems(accounts, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
}
func applySelectedCollectionViewDataSource(accounts: [NSManagedObjectID]) {
assert(Thread.isMainThread)
guard let count = headerPlaceholderCount.value else { return }
guard let dataSource = collectionDiffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<SelectedAccountSection, SelectedAccountItem>()
snapshot.appendSections([.main])
let placeholderCount = count - accounts.count
let accountItems = accounts.map { SelectedAccountItem.accountObjectID(accountObjectID: $0) }
snapshot.appendItems(accountItems, toSection: .main)
if placeholderCount > 0 {
for _ in 0 ..< placeholderCount {
snapshot.appendItems([SelectedAccountItem.placeHolder(uuid: UUID())], toSection: .main)
}
}
dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
}
func receiveAccounts(ids: [String]) {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return return
} }
let userFetchRequest = MastodonUser.sortedFetchRequest userFetchedResultsController.domain = authenticationBox.domain
userFetchRequest.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, ids: ids)
let mastodonUsers: [MastodonUser]? = { Task {
let userFetchRequest = MastodonUser.sortedFetchRequest var userIDs: [MastodonUser.ID] = []
userFetchRequest.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, ids: ids)
userFetchRequest.returnsObjectsAsFaults = false
do { do {
return try self.context.managedObjectContext.fetch(userFetchRequest) let response = try await context.apiService.suggestionAccountV2(
query: nil,
authenticationBox: authenticationBox
)
userIDs = response.value.map { $0.account.id }
} catch let error as Mastodon.API.Error where error.httpResponseStatus == .notFound {
let response = try await context.apiService.suggestionAccount(
query: nil,
authenticationBox: authenticationBox
)
userIDs = response.value.map { $0.id }
} catch { } catch {
assertionFailure(error.localizedDescription) os_log("%{public}s[%{public}ld], %{public}s: fetch recommendAccountV2 failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
return nil
} }
}()
if let users = mastodonUsers { guard !userIDs.isEmpty else { return }
let sortedUsers = users.sorted { (user1, user2) -> Bool in userFetchedResultsController.userIDs = userIDs
(ids.firstIndex(of: user1.id) ?? 0) < (ids.firstIndex(of: user2.id) ?? 0)
}
accounts.value = sortedUsers.map(\.objectID)
} }
// .sink { [weak self] completion in
// switch completion {
// case .failure(let error):
// if let apiError = error as? Mastodon.API.Error {
// if apiError.httpResponseStatus == .notFound {
// self?.suggestionAccountsFallback.send()
// }
// }
// case .finished:
// // handle isFetchingLatestTimeline in fetch controller delegate
// break
// }
// } receiveValue: { [weak self] response in
// let ids = response.value.map(\.account.id)
// self?.receiveAccounts(ids: ids)
// }
// .store(in: &disposeBag)
//
// suggestionAccountsFallback
// .sink(receiveValue: { [weak self] _ in
// self?.requestSuggestionAccount()
// })
// .store(in: &disposeBag)
} }
// func applyTableViewDataSource(accounts: [NSManagedObjectID]) {
// assert(Thread.isMainThread)
// guard let dataSource = diffableDataSource else { return }
// var snapshot = NSDiffableDataSourceSnapshot<RecommendAccountSection, NSManagedObjectID>()
// snapshot.appendSections([.main])
// snapshot.appendItems(accounts, toSection: .main)
// dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
// }
//
// func applySelectedCollectionViewDataSource(accounts: [NSManagedObjectID]) {
// assert(Thread.isMainThread)
// guard let count = headerPlaceholderCount.value else { return }
// guard let dataSource = collectionDiffableDataSource else { return }
// var snapshot = NSDiffableDataSourceSnapshot<SelectedAccountSection, SelectedAccountItem>()
// snapshot.appendSections([.main])
// let placeholderCount = count - accounts.count
// let accountItems = accounts.map { SelectedAccountItem.accountObjectID(accountObjectID: $0) }
// snapshot.appendItems(accountItems, toSection: .main)
//
// if placeholderCount > 0 {
// for _ in 0 ..< placeholderCount {
// snapshot.appendItems([SelectedAccountItem.placeHolder(uuid: UUID())], toSection: .main)
// }
// }
// dataSource.apply(snapshot, animatingDifferences: false, completion: nil)
// }
// func receiveAccounts(userIDs: [MastodonUser.ID]) {
// guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
// return
// }
// let request = MastodonUser.sortedFetchRequest
// request.predicate = MastodonUser.predicate(domain: activeMastodonAuthenticationBox.domain, ids: userIDs)
// 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 {
// let sortedUsers = users.sorted { (user1, user2) -> Bool in
// (ids.firstIndex(of: user1.id) ?? 0) < (ids.firstIndex(of: user2.id) ?? 0)
// }
// accounts.value = sortedUsers.map(\.objectID)
// }
// }
func followAction(objectID: NSManagedObjectID) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>? { func followAction(objectID: NSManagedObjectID) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error>? {
fatalError() fatalError()
@ -203,8 +201,8 @@ final class SuggestionAccountViewModel: NSObject {
// ) // )
} }
func checkAccountsFollowState() { // func checkAccountsFollowState() {
fatalError() // fatalError()
// guard let currentMastodonUser = currentMastodonUser.value else { // guard let currentMastodonUser = currentMastodonUser.value else {
// return // return
// } // }
@ -229,5 +227,5 @@ final class SuggestionAccountViewModel: NSObject {
// }.map(\.objectID) // }.map(\.objectID)
// //
// selectedAccounts.value = followingUsers // selectedAccounts.value = followingUsers
} // }
} }

View File

@ -141,7 +141,7 @@ extension SuggestionAccountTableViewCell {
]) ])
} }
func config(with account: MastodonUser, isSelected: Bool) { func config(with account: MastodonUser) {
if let url = account.avatarImageURL() { if let url = account.avatarImageURL() {
_imageView.af.setImage( _imageView.af.setImage(
withURL: url, withURL: url,

View File

@ -14,68 +14,62 @@ import OSLog
extension APIService { extension APIService {
func suggestionAccount( func suggestionAccount(
domain: String,
query: Mastodon.API.Suggestions.Query?, query: Mastodon.API.Suggestions.Query?,
mastodonAuthenticationBox: MastodonAuthenticationBox authenticationBox: MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> { ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
fatalError()
// let authorization = mastodonAuthenticationBox.userAuthorization let response = try await Mastodon.API.Suggestions.get(
// session: session,
// return Mastodon.API.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization) domain: authenticationBox.domain,
// .flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> in query: query,
// let log = OSLog.api authorization: authenticationBox.userAuthorization
// return self.backgroundManagedObjectContext.performChanges { ).singleOutput()
// 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 managedObjectContext = backgroundManagedObjectContext
// let flag = isCreated ? "+" : "-" try await managedObjectContext.performChanges {
// 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) for entity in response.value {
// } _ = Persistence.MastodonUser.createOrMerge(
// } in: managedObjectContext,
// .setFailureType(to: Error.self) context: Persistence.MastodonUser.PersistContext(
// .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Account]> in domain: authenticationBox.domain,
// switch result { entity: entity,
// case .success: cache: nil,
// return response networkDate: response.networkDate
// case .failure(let error): )
// throw error )
// } } // end for in
// } }
// .eraseToAnyPublisher()
// } return response
// .eraseToAnyPublisher()
} }
func suggestionAccountV2( func suggestionAccountV2(
domain: String,
query: Mastodon.API.Suggestions.Query?, query: Mastodon.API.Suggestions.Query?,
mastodonAuthenticationBox: MastodonAuthenticationBox authenticationBox: MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]>, Error> { ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]> {
fatalError() let response = try await Mastodon.API.V2.Suggestions.get(
// let authorization = mastodonAuthenticationBox.userAuthorization session: session,
// domain: authenticationBox.domain,
// return Mastodon.API.V2.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization) query: query,
// .flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]>, Error> in authorization: authenticationBox.userAuthorization
// let log = OSLog.api ).singleOutput()
// return self.backgroundManagedObjectContext.performChanges {
// response.value.forEach { suggestionAccount in let managedObjectContext = backgroundManagedObjectContext
// let user = suggestionAccount.account try await managedObjectContext.performChanges {
// let (mastodonUser,isCreated) = APIService.CoreData.createOrMergeMastodonUser(into: self.backgroundManagedObjectContext, for: nil, in: domain, entity: user, userCache: nil, networkDate: Date(), log: log) for entity in response.value {
// let flag = isCreated ? "+" : "-" _ = Persistence.MastodonUser.createOrMerge(
// 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) in: managedObjectContext,
// } context: Persistence.MastodonUser.PersistContext(
// } domain: authenticationBox.domain,
// .setFailureType(to: Error.self) entity: entity.account,
// .tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]> in cache: nil,
// switch result { networkDate: response.networkDate
// case .success: )
// return response )
// case .failure(let error): } // end for in
// throw error }
// }
// } return response
// .eraseToAnyPublisher()
// }
// .eraseToAnyPublisher()
} }
} }