fix: [WIP] add suggestion account scene back
This commit is contained in:
parent
c1e1d527fe
commit
54e84ed814
|
@ -483,6 +483,8 @@
|
|||
DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */; };
|
||||
DBB45B5B27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.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 */; };
|
||||
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1756,6 +1760,7 @@
|
|||
children = (
|
||||
2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */,
|
||||
2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */,
|
||||
DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */,
|
||||
2D4AD89A2631659400613EFC /* CollectionViewCell */,
|
||||
2DAC9E43262FC9DE0062E1A6 /* TableViewCell */,
|
||||
);
|
||||
|
@ -2012,6 +2017,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */,
|
||||
DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */,
|
||||
);
|
||||
path = RecommandAccount;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3845,6 +3851,7 @@
|
|||
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */,
|
||||
DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */,
|
||||
DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */,
|
||||
DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */,
|
||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||
DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */,
|
||||
DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */,
|
||||
|
@ -4068,6 +4075,7 @@
|
|||
DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */,
|
||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */,
|
||||
DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */,
|
||||
DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */,
|
||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
||||
DB6180E626391B550018D199 /* MediaPreviewTransitionController.swift in Sources */,
|
||||
DB0FCB922796DE19006C02E2 /* TrendSectionHeaderCollectionReusableView.swift in Sources */,
|
||||
|
|
|
@ -7,32 +7,9 @@
|
|||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
|
||||
enum SelectedAccountItem {
|
||||
case accountObjectID(accountObjectID: NSManagedObjectID)
|
||||
enum SelectedAccountItem: Hashable {
|
||||
case account(ManagedObjectRecord<MastodonUser>)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,15 +17,17 @@ enum SelectedAccountSection: Equatable, Hashable {
|
|||
|
||||
extension SelectedAccountSection {
|
||||
static func collectionViewDiffableDataSource(
|
||||
for collectionView: UICollectionView,
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
collectionView: UICollectionView,
|
||||
context: AppContext
|
||||
) -> UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem> {
|
||||
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self), for: indexPath) as! SuggestionAccountCollectionViewCell
|
||||
switch item {
|
||||
case .accountObjectID(let objectID):
|
||||
let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
cell.config(with: user)
|
||||
case .account(let record):
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
cell.config(with: user)
|
||||
}
|
||||
case .placeHolder:
|
||||
cell.configAsPlaceHolder()
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ final class UserFetchedResultsController: NSObject {
|
|||
let fetchedResultsController: NSFetchedResultsController<MastodonUser>
|
||||
|
||||
// input
|
||||
let domain = CurrentValueSubject<String?, Never>(nil)
|
||||
let userIDs = CurrentValueSubject<[Mastodon.Entity.Account.ID], Never>([])
|
||||
@Published var domain: String? = nil
|
||||
@Published var userIDs: [Mastodon.Entity.Account.ID] = []
|
||||
|
||||
// output
|
||||
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
@Published var records: [ManagedObjectRecord<MastodonUser>] = []
|
||||
|
||||
init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate?) {
|
||||
self.domain.value = domain ?? ""
|
||||
self.domain = domain ?? ""
|
||||
self.fetchedResultsController = {
|
||||
let fetchRequest = MastodonUser.sortedFetchRequest
|
||||
fetchRequest.predicate = MastodonUser.predicate(domain: domain ?? "", ids: [])
|
||||
|
@ -54,8 +54,8 @@ final class UserFetchedResultsController: NSObject {
|
|||
fetchedResultsController.delegate = self
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.domain.removeDuplicates(),
|
||||
self.userIDs.removeDuplicates()
|
||||
self.$domain.removeDuplicates(),
|
||||
self.$userIDs.removeDuplicates()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] domain, ids in
|
||||
|
@ -79,11 +79,11 @@ final class UserFetchedResultsController: NSObject {
|
|||
extension UserFetchedResultsController {
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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 items: [NSManagedObjectID] = objects
|
||||
|
|
|
@ -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>)
|
||||
}
|
|
@ -129,22 +129,29 @@ enum RecommendAccountSection: Equatable, Hashable {
|
|||
//
|
||||
//}
|
||||
//
|
||||
//extension RecommendAccountSection {
|
||||
//
|
||||
// static func tableViewDiffableDataSource(
|
||||
// for tableView: UITableView,
|
||||
// managedObjectContext: NSManagedObjectContext,
|
||||
// viewModel: SuggestionAccountViewModel,
|
||||
// delegate: SuggestionAccountTableViewCellDelegate
|
||||
// ) -> UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
|
||||
// UITableViewDiffableDataSource(tableView: tableView) { [weak viewModel, weak delegate] (tableView, indexPath, objectID) -> UITableViewCell? in
|
||||
// guard let viewModel = viewModel else { return nil }
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell
|
||||
// let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
// let isSelected = viewModel.selectedAccounts.value.contains(objectID)
|
||||
// cell.delegate = delegate
|
||||
// cell.config(with: user, isSelected: isSelected)
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
extension RecommendAccountSection {
|
||||
|
||||
struct Configuration {
|
||||
weak var suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate?
|
||||
}
|
||||
|
||||
static func tableViewDiffableDataSource(
|
||||
tableView: UITableView,
|
||||
context: AppContext,
|
||||
configuration: Configuration
|
||||
) -> UITableViewDiffableDataSource<RecommendAccountSection, RecommendAccountItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell
|
||||
switch item {
|
||||
case .account(let record):
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
cell.config(with: user)
|
||||
}
|
||||
}
|
||||
cell.delegate = configuration.suggestionAccountTableViewCellDelegate
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,6 +74,15 @@ extension HomeTimelineViewController {
|
|||
guard let self = self else { return }
|
||||
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
|
||||
guard let self = self else { return }
|
||||
guard let windowScene = self.view.window?.windowScene else { return }
|
||||
|
|
|
@ -383,10 +383,13 @@ extension HomeTimelineViewController {
|
|||
extension HomeTimelineViewController {
|
||||
|
||||
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
||||
// TODO:
|
||||
// let viewModel = SuggestionAccountViewModel(context: context)
|
||||
// viewModel.delegate = self.viewModel
|
||||
// coordinator.present(scene: .suggestionAccount(viewModel: viewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context)
|
||||
suggestionAccountViewModel.delegate = viewModel
|
||||
coordinator.present(
|
||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||
from: self,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func manuallySearchButtonPressed(_ sender: UIButton) {
|
||||
|
|
|
@ -119,8 +119,6 @@ final class HomeTimelineViewModel: NSObject {
|
|||
|
||||
}
|
||||
|
||||
//extension HomeTimelineViewModel: SuggestionAccountViewModelDelegate { }
|
||||
|
||||
extension HomeTimelineViewModel {
|
||||
struct ScrollPositionRecord {
|
||||
let item: StatusItem
|
||||
|
@ -197,3 +195,9 @@ extension HomeTimelineViewModel {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - SuggestionAccountViewModelDelegate
|
||||
extension HomeTimelineViewModel: SuggestionAccountViewModelDelegate {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ extension FollowerListViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.userFetchedResultsController.userIDs.value = []
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
|
||||
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")
|
||||
|
||||
var hasNewAppend = false
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs.value
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
for user in response.value {
|
||||
guard !userIDs.contains(user.id) else { continue }
|
||||
userIDs.append(user.id)
|
||||
|
@ -174,7 +174,7 @@ extension FollowerListViewModel.State {
|
|||
}
|
||||
|
||||
self.maxID = maxID
|
||||
viewModel.userFetchedResultsController.userIDs.value = userIDs
|
||||
viewModel.userFetchedResultsController.userIDs = userIDs
|
||||
|
||||
} catch {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch follower fail: \(error.localizedDescription)")
|
||||
|
|
|
@ -72,7 +72,7 @@ extension FollowingListViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.userFetchedResultsController.userIDs.value = []
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
|
||||
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)")
|
||||
|
||||
var hasNewAppend = false
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs.value
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
for user in response.value {
|
||||
guard !userIDs.contains(user.id) else { continue }
|
||||
userIDs.append(user.id)
|
||||
|
@ -174,7 +174,7 @@ extension FollowingListViewModel.State {
|
|||
await enter(state: NoMore.self)
|
||||
}
|
||||
self.maxID = maxID
|
||||
viewModel.userFetchedResultsController.userIDs.value = userIDs
|
||||
viewModel.userFetchedResultsController.userIDs = userIDs
|
||||
|
||||
} catch {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch following fail: \(error.localizedDescription)")
|
||||
|
|
|
@ -20,12 +20,12 @@ final class MeProfileViewModel: ProfileViewModel {
|
|||
optionalMastodonUser: context.authenticationService.activeMastodonAuthentication.value?.user
|
||||
)
|
||||
|
||||
self.currentMastodonUser
|
||||
.sink { [weak self] currentMastodonUser in
|
||||
os_log("%{public}s[%{public}ld], %{public}s: current active mastodon user: %s", ((#file as NSString).lastPathComponent), #line, #function, currentMastodonUser?.username ?? "<nil>")
|
||||
$me
|
||||
.sink { [weak self] me in
|
||||
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 }
|
||||
self.mastodonUser.value = currentMastodonUser
|
||||
self.user = me
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
|
|
@ -577,7 +577,7 @@ extension ProfileViewController {
|
|||
|
||||
private func bindProfileRelationship() {
|
||||
Publishers.CombineLatest(
|
||||
viewModel.mastodonUser,
|
||||
viewModel.$user,
|
||||
viewModel.relationshipActionOptionSet
|
||||
)
|
||||
.asyncMap { [weak self] user, relationshipSet -> UIMenu? in
|
||||
|
@ -725,7 +725,7 @@ extension ProfileViewController {
|
|||
|
||||
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
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)
|
||||
Task {
|
||||
let _activityViewController = try await DataSourceFacade.createActivityViewController(
|
||||
|
@ -754,7 +754,7 @@ extension ProfileViewController {
|
|||
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
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 mastodonUser = viewModel.mastodonUser.value else { return }
|
||||
guard let mastodonUser = viewModel.user else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
|
||||
|
@ -849,7 +849,7 @@ extension ProfileViewController: ProfilePagingViewControllerDelegate {
|
|||
// MARK: - ProfileHeaderViewDelegate
|
||||
extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||
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)
|
||||
|
||||
Task {
|
||||
|
@ -865,7 +865,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
Task {
|
||||
|
@ -956,7 +956,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
|||
case .none:
|
||||
break
|
||||
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)
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
|
@ -968,7 +968,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
|||
}
|
||||
case .muting:
|
||||
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 alertController = UIAlertController(
|
||||
|
@ -993,7 +993,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
|||
present(alertController, animated: true, completion: nil)
|
||||
case .blocking:
|
||||
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 alertController = UIAlertController(
|
||||
|
@ -1077,7 +1077,7 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate {
|
|||
extension ProfileViewController: MastodonMenuDelegate {
|
||||
func menuAction(_ action: MastodonMenu.Action) {
|
||||
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)
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ class ProfileViewModel: NSObject {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let mastodonUser: CurrentValueSubject<MastodonUser?, Never>
|
||||
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
|
||||
@Published var me: MastodonUser?
|
||||
@Published var user: MastodonUser?
|
||||
let viewDidAppear = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
|
@ -73,7 +73,7 @@ class ProfileViewModel: NSObject {
|
|||
|
||||
init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||
self.context = context
|
||||
self.mastodonUser = CurrentValueSubject(mastodonUser)
|
||||
self.user = mastodonUser
|
||||
self.domain = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value?.domain)
|
||||
self.userID = CurrentValueSubject(mastodonUser?.id)
|
||||
self.bannerImageURL = CurrentValueSubject(mastodonUser?.headerImageURL())
|
||||
|
@ -98,21 +98,21 @@ class ProfileViewModel: NSObject {
|
|||
.store(in: &disposeBag)
|
||||
|
||||
// bind active authentication
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.sink { [weak self] activeMastodonAuthentication in
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.sink { [weak self] authenticationBox in
|
||||
guard let self = self else { return }
|
||||
guard let activeMastodonAuthentication = activeMastodonAuthentication else {
|
||||
guard let authenticationBox = authenticationBox else {
|
||||
self.domain.value = nil
|
||||
self.currentMastodonUser.value = nil
|
||||
self.me = nil
|
||||
return
|
||||
}
|
||||
self.domain.value = activeMastodonAuthentication.domain
|
||||
self.currentMastodonUser.value = activeMastodonAuthentication.user
|
||||
self.domain.value = authenticationBox.domain
|
||||
self.me = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// 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) }
|
||||
}
|
||||
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||
|
@ -176,18 +176,18 @@ class ProfileViewModel: NSObject {
|
|||
extension ProfileViewModel {
|
||||
private func setup() {
|
||||
Publishers.CombineLatest(
|
||||
mastodonUser.eraseToAnyPublisher(),
|
||||
currentMastodonUser.eraseToAnyPublisher()
|
||||
$user,
|
||||
$me
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] mastodonUser, currentMastodonUser in
|
||||
.sink { [weak self] user, me in
|
||||
guard let self = self else { return }
|
||||
// Update view model attribute
|
||||
self.update(mastodonUser: mastodonUser)
|
||||
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
|
||||
self.update(mastodonUser: user)
|
||||
self.update(mastodonUser: user, currentMastodonUser: me)
|
||||
|
||||
// Setup observer for user
|
||||
if let mastodonUser = mastodonUser {
|
||||
if let mastodonUser = user {
|
||||
// setup observer
|
||||
self.mastodonUserObserver = ManagedObjectObserver.observe(object: mastodonUser)
|
||||
.sink { completion in
|
||||
|
@ -203,7 +203,7 @@ extension ProfileViewModel {
|
|||
switch changeType {
|
||||
case .update:
|
||||
self.update(mastodonUser: mastodonUser)
|
||||
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
|
||||
self.update(mastodonUser: mastodonUser, currentMastodonUser: me)
|
||||
case .delete:
|
||||
// TODO:
|
||||
break
|
||||
|
@ -215,7 +215,7 @@ extension ProfileViewModel {
|
|||
}
|
||||
|
||||
// Setup observer for user
|
||||
if let currentMastodonUser = currentMastodonUser {
|
||||
if let currentMastodonUser = me {
|
||||
// setup observer
|
||||
self.currentMastodonUserObserver = ManagedObjectObserver.observe(object: currentMastodonUser)
|
||||
.sink { completion in
|
||||
|
@ -230,7 +230,7 @@ extension ProfileViewModel {
|
|||
guard let changeType = change.changeType else { return }
|
||||
switch changeType {
|
||||
case .update:
|
||||
self.update(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
|
||||
self.update(mastodonUser: user, currentMastodonUser: currentMastodonUser)
|
||||
case .delete:
|
||||
// TODO:
|
||||
break
|
||||
|
@ -347,13 +347,14 @@ extension ProfileViewModel {
|
|||
|
||||
// fetch profile info before edit
|
||||
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
guard let currentMastodonUser = currentMastodonUser.value,
|
||||
let mastodonAuthentication = currentMastodonUser.mastodonAuthentication else {
|
||||
guard let me = me,
|
||||
let mastodonAuthentication = me.mastodonAuthentication
|
||||
else {
|
||||
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
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(
|
||||
|
|
|
@ -48,7 +48,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.mastodonUser.value = mastodonUser
|
||||
self.user = mastodonUser
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ extension SearchDetailViewController {
|
|||
assertionFailure()
|
||||
break
|
||||
case .people:
|
||||
viewController.viewModel.userFetchedResultsController.userIDs.value = allSearchScopeViewController.viewModel.userFetchedResultsController.userIDs.value
|
||||
viewController.viewModel.userFetchedResultsController.userIDs = allSearchScopeViewController.viewModel.userFetchedResultsController.userIDs
|
||||
case .hashtags:
|
||||
viewController.viewModel.hashtags = allSearchScopeViewController.viewModel.hashtags
|
||||
case .posts:
|
||||
|
|
|
@ -155,7 +155,7 @@ extension SearchResultViewModel.State {
|
|||
|
||||
// reset data source when the search is refresh
|
||||
if offset == nil {
|
||||
viewModel.userFetchedResultsController.userIDs.value = []
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
viewModel.statusFetchedResultsController.statusIDs.value = []
|
||||
viewModel.hashtags = []
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ final class SearchResultViewModel {
|
|||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.value, on: userFetchedResultsController.domain)
|
||||
.assign(to: \.domain, on: userFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
|
|
|
@ -15,12 +15,43 @@ import MastodonAsset
|
|||
import MastodonLocalization
|
||||
|
||||
class SuggestionAccountViewController: UIViewController, NeedsDependency {
|
||||
|
||||
static let collectionViewHeight: CGFloat = 24 + 64 + 24
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
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 = ControlContainableTableView()
|
||||
|
@ -32,34 +63,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency {
|
|||
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 {
|
||||
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 {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
fatalError()
|
||||
|
||||
// setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
||||
// ThemeService.shared.currentTheme
|
||||
// .receive(on: RunLoop.main)
|
||||
// .sink { [weak self] theme in
|
||||
// guard let self = self else { return }
|
||||
// self.setupBackgroundColor(theme: theme)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// title = L10n.Scene.SuggestionAccount.title
|
||||
// navigationItem.rightBarButtonItem
|
||||
// = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
|
||||
// target: self,
|
||||
// action: #selector(SuggestionAccountViewController.doneButtonDidClick(_:)))
|
||||
//
|
||||
// tableView.delegate = self
|
||||
// tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// view.addSubview(tableView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
// tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
// tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
// ])
|
||||
// viewModel.diffableDataSource = RecommendAccountSection.tableViewDiffableDataSource(
|
||||
// for: tableView,
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// viewModel: viewModel,
|
||||
// delegate: self
|
||||
// )
|
||||
//
|
||||
// viewModel.collectionDiffableDataSource = SelectedAccountSection.collectionViewDiffableDataSource(for: selectedCollectionView, managedObjectContext: context.managedObjectContext)
|
||||
//
|
||||
// viewModel.accounts
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] accounts in
|
||||
// guard let self = self else { return }
|
||||
// self.setupHeader(accounts: accounts)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
||||
ThemeService.shared.currentTheme
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] theme in
|
||||
guard let self = self else { return }
|
||||
self.setupBackgroundColor(theme: theme)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
title = L10n.Scene.SuggestionAccount.title
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: UIBarButtonItem.SystemItem.done,
|
||||
target: self,
|
||||
action: #selector(SuggestionAccountViewController.doneButtonDidClick(_:))
|
||||
)
|
||||
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(collectionView)
|
||||
NSLayoutConstraint.activate([
|
||||
collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
||||
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
collectionView.heightAnchor.constraint(equalToConstant: SuggestionAccountViewController.collectionViewHeight),
|
||||
])
|
||||
defer { view.bringSubviewToFront(collectionView) }
|
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(tableView)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
collectionView.delegate = self
|
||||
viewModel.setupDiffableDataSource(
|
||||
collectionView: collectionView
|
||||
)
|
||||
|
||||
tableView.delegate = self
|
||||
viewModel.setupDiffableDataSource(
|
||||
tableView: tableView,
|
||||
suggestionAccountTableViewCellDelegate: self
|
||||
)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(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) {
|
||||
view.backgroundColor = theme.systemBackgroundColor
|
||||
tableHeader.backgroundColor = theme.systemGroupedBackgroundColor
|
||||
collectionView.backgroundColor = theme.systemGroupedBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
extension SuggestionAccountViewController: UICollectionViewDelegateFlowLayout {
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||
15
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
CGSize(width: 56, height: 56)
|
||||
}
|
||||
// MARK: - UICollectionViewDelegateFlowLayout
|
||||
extension SuggestionAccountViewController: UICollectionViewDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let diffableDataSource = viewModel.collectionDiffableDataSource else { return }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
switch item {
|
||||
case .accountObjectID(let accountObjectID):
|
||||
let mastodonUser = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser
|
||||
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
|
||||
DispatchQueue.main.async {
|
||||
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
// guard let diffableDataSource = viewModel.collectionDiffableDataSource else { return }
|
||||
// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
// switch item {
|
||||
// case .accountObjectID(let accountObjectID):
|
||||
// let mastodonUser = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser
|
||||
// let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
|
||||
// DispatchQueue.main.async {
|
||||
// self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
|
||||
// }
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SuggestionAccountViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return }
|
||||
guard let objectID = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
let mastodonUser = context.managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser)
|
||||
DispatchQueue.main.async {
|
||||
self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show)
|
||||
guard let tableViewDiffableDataSource = viewModel.tableViewDiffableDataSource else { return }
|
||||
guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
switch item {
|
||||
case .account(let record):
|
||||
guard let account = record.object(in: context.managedObjectContext) else { return }
|
||||
let cachedProfileViewModel = CachedProfileViewModel(context: context, mastodonUser: account)
|
||||
coordinator.present(
|
||||
scene: .profile(viewModel: cachedProfileViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate {
|
||||
func accountButtonPressed(objectID: NSManagedObjectID, cell: SuggestionAccountTableViewCell) {
|
||||
let selected = !viewModel.selectedAccounts.value.contains(objectID)
|
||||
cell.startAnimating()
|
||||
viewModel.followAction(objectID: objectID)?
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
cell.stopAnimating()
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log("%{public}s[%{public}ld], %{public}s: follow failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
|
||||
case .finished:
|
||||
var selectedAccounts = self.viewModel.selectedAccounts.value
|
||||
if selected {
|
||||
selectedAccounts.append(objectID)
|
||||
} else {
|
||||
selectedAccounts.removeAll { $0 == objectID }
|
||||
}
|
||||
cell.button.isSelected = selected
|
||||
self.viewModel.selectedAccounts.value = selectedAccounts
|
||||
}
|
||||
}, receiveValue: { _ in
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
// let selected = !viewModel.selectedAccounts.value.contains(objectID)
|
||||
// cell.startAnimating()
|
||||
// viewModel.followAction(objectID: objectID)?
|
||||
// .sink(receiveCompletion: { [weak self] completion in
|
||||
// guard let self = self else { return }
|
||||
// cell.stopAnimating()
|
||||
// switch completion {
|
||||
// case .failure(let error):
|
||||
// os_log("%{public}s[%{public}ld], %{public}s: follow failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
|
||||
// case .finished:
|
||||
// var selectedAccounts = self.viewModel.selectedAccounts.value
|
||||
// if selected {
|
||||
// selectedAccounts.append(objectID)
|
||||
// } else {
|
||||
// selectedAccounts.removeAll { $0 == objectID }
|
||||
// }
|
||||
// cell.button.isSelected = selected
|
||||
// self.viewModel.selectedAccounts.value = selectedAccounts
|
||||
// }
|
||||
// }, receiveValue: { _ in
|
||||
// })
|
||||
// .store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
extension SuggestionAccountViewController {
|
||||
@objc func doneButtonDidClick(_ sender: UIButton) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
if viewModel.selectedAccounts.value.count > 0 {
|
||||
viewModel.delegate?.homeTimelineNeedRefresh.send()
|
||||
}
|
||||
// if viewModel.selectedAccounts.value.count > 0 {
|
||||
// viewModel.delegate?.homeTimelineNeedRefresh.send()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -20,177 +20,175 @@ protocol SuggestionAccountViewModelDelegate: AnyObject {
|
|||
final class SuggestionAccountViewModel: NSObject {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
weak var delegate: SuggestionAccountViewModelDelegate?
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
let currentMastodonUser = CurrentValueSubject<MastodonUser?, Never>(nil)
|
||||
weak var delegate: SuggestionAccountViewModelDelegate?
|
||||
// output
|
||||
let accounts = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
var selectedAccounts = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
var viewWillAppear = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
var collectionViewDiffableDataSource: UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem>?
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<RecommendAccountSection, RecommendAccountItem>?
|
||||
|
||||
@Published var selectedAccounts: [ManagedObjectRecord<MastodonUser>] = []
|
||||
var headerPlaceholderCount = CurrentValueSubject<Int?, Never>(nil)
|
||||
var suggestionAccountsFallback = PassthroughSubject<Void, Never>()
|
||||
|
||||
var viewWillAppear = PassthroughSubject<Void, Never>()
|
||||
|
||||
var diffableDataSource: UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID>? {
|
||||
didSet(value) {
|
||||
if !accounts.value.isEmpty {
|
||||
applyTableViewDataSource(accounts: accounts.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var collectionDiffableDataSource: UICollectionViewDiffableDataSource<SelectedAccountSection, SelectedAccountItem>?
|
||||
|
||||
init(context: AppContext, accounts: [NSManagedObjectID]? = nil) {
|
||||
init(
|
||||
context: AppContext
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
super.init()
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.accounts,
|
||||
self.selectedAccounts
|
||||
)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] accounts,selectedAccounts in
|
||||
self?.applyTableViewDataSource(accounts: accounts)
|
||||
self?.applySelectedCollectionViewDataSource(accounts: selectedAccounts)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// Publishers.CombineLatest(
|
||||
// $accounts,
|
||||
// $selectedAccounts
|
||||
// )
|
||||
// .receive(on: RunLoop.main)
|
||||
// .sink { [weak self] accounts,selectedAccounts in
|
||||
// self?.applyTableViewDataSource(accounts: accounts)
|
||||
// self?.applySelectedCollectionViewDataSource(accounts: selectedAccounts)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.selectedAccounts,
|
||||
self.headerPlaceholderCount
|
||||
)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] selectedAccount,count in
|
||||
self?.applySelectedCollectionViewDataSource(accounts: selectedAccount)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// Publishers.CombineLatest(
|
||||
// self.selectedAccounts,
|
||||
// self.headerPlaceholderCount
|
||||
// )
|
||||
// .receive(on: RunLoop.main)
|
||||
// .sink { [weak self] selectedAccount,count in
|
||||
// self?.applySelectedCollectionViewDataSource(accounts: selectedAccount)
|
||||
// }
|
||||
// .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
|
||||
.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 {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
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
|
||||
userFetchedResultsController.domain = authenticationBox.domain
|
||||
|
||||
Task {
|
||||
var userIDs: [MastodonUser.ID] = []
|
||||
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 {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
os_log("%{public}s[%{public}ld], %{public}s: fetch recommendAccountV2 failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
|
||||
}
|
||||
}()
|
||||
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)
|
||||
|
||||
guard !userIDs.isEmpty else { return }
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
}
|
||||
|
||||
// .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>? {
|
||||
fatalError()
|
||||
|
@ -203,8 +201,8 @@ final class SuggestionAccountViewModel: NSObject {
|
|||
// )
|
||||
}
|
||||
|
||||
func checkAccountsFollowState() {
|
||||
fatalError()
|
||||
// func checkAccountsFollowState() {
|
||||
// fatalError()
|
||||
// guard let currentMastodonUser = currentMastodonUser.value else {
|
||||
// return
|
||||
// }
|
||||
|
@ -229,5 +227,5 @@ final class SuggestionAccountViewModel: NSObject {
|
|||
// }.map(\.objectID)
|
||||
//
|
||||
// selectedAccounts.value = followingUsers
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ extension SuggestionAccountTableViewCell {
|
|||
])
|
||||
}
|
||||
|
||||
func config(with account: MastodonUser, isSelected: Bool) {
|
||||
func config(with account: MastodonUser) {
|
||||
if let url = account.avatarImageURL() {
|
||||
_imageView.af.setImage(
|
||||
withURL: url,
|
||||
|
|
|
@ -14,68 +14,62 @@ import OSLog
|
|||
|
||||
extension APIService {
|
||||
func suggestionAccount(
|
||||
domain: String,
|
||||
query: Mastodon.API.Suggestions.Query?,
|
||||
mastodonAuthenticationBox: MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
fatalError()
|
||||
// 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()
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
|
||||
let response = try await Mastodon.API.Suggestions.get(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
query: query,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
for entity in response.value {
|
||||
_ = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: authenticationBox.domain,
|
||||
entity: entity,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
} // end for … in
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func suggestionAccountV2(
|
||||
domain: String,
|
||||
query: Mastodon.API.Suggestions.Query?,
|
||||
mastodonAuthenticationBox: MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]>, Error> {
|
||||
fatalError()
|
||||
// let authorization = mastodonAuthenticationBox.userAuthorization
|
||||
//
|
||||
// return Mastodon.API.V2.Suggestions.get(session: session, domain: domain, query: query, authorization: authorization)
|
||||
// .flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]>, Error> in
|
||||
// let log = OSLog.api
|
||||
// return self.backgroundManagedObjectContext.performChanges {
|
||||
// response.value.forEach { suggestionAccount in
|
||||
// let user = suggestionAccount.account
|
||||
// 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.V2.SuggestionAccount]> in
|
||||
// switch result {
|
||||
// case .success:
|
||||
// return response
|
||||
// case .failure(let error):
|
||||
// throw error
|
||||
// }
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.V2.SuggestionAccount]> {
|
||||
let response = try await Mastodon.API.V2.Suggestions.get(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
query: query,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
for entity in response.value {
|
||||
_ = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: authenticationBox.domain,
|
||||
entity: entity.account,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
} // end for … in
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue