// Copyright © 2020 Metabolist. All rights reserved. import Combine import Foundation import Mastodon import ServiceLayer final public class ProfileViewModel { @Published public private(set) var accountViewModel: AccountViewModel? @Published public var collection = ProfileCollection.statuses @Published public var alertItem: AlertItem? public let imagePresentations: AnyPublisher private let profileService: ProfileService private let collectionViewModel: CurrentValueSubject private let accountEventsSubject: PassthroughSubject, Never> private let imagePresentationsSubject = PassthroughSubject() private var cancellables = Set() public init(profileService: ProfileService, identityContext: IdentityContext) { self.profileService = profileService imagePresentations = imagePresentationsSubject.eraseToAnyPublisher() collectionViewModel = CurrentValueSubject( CollectionItemsViewModel( collectionService: profileService.timelineService(profileCollection: .statuses), identityContext: identityContext)) let accountEventsSubject = PassthroughSubject, Never>() self.accountEventsSubject = accountEventsSubject profileService.profilePublisher .map { let vm = AccountViewModel(accountService: identityContext.service .navigationService .accountService(account: $0.account), identityContext: identityContext, eventsSubject: accountEventsSubject) vm.relationship = $0.relationship vm.identityProofs = $0.identityProofs vm.featuredTags = $0.featuredTags return vm } .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$accountViewModel) $collection.dropFirst() .map(profileService.timelineService(profileCollection:)) .map { CollectionItemsViewModel(collectionService: $0, identityContext: identityContext) } .sink { [weak self] in guard let self = self else { return } self.collectionViewModel.send($0) $0.$alertItem.assign(to: &self.$alertItem) } .store(in: &cancellables) } } public extension ProfileViewModel { func presentHeader() { guard let accountViewModel = accountViewModel else { return } imagePresentationsSubject.send(accountViewModel.headerURL) } func presentAvatar() { guard let accountViewModel = accountViewModel else { return } imagePresentationsSubject.send(accountViewModel.avatarURL(profile: true)) } func fetchProfile() -> AnyPublisher { profileService.fetchProfile().assignErrorsToAlertItem(to: \.alertItem, on: self) } func sendDirectMessage() { guard let accountViewModel = accountViewModel else { return } collectionViewModel.value.sendDirectMessage(accountViewModel: accountViewModel) } } extension ProfileViewModel: CollectionViewModel { public var identityContext: IdentityContext { collectionViewModel.value.identityContext } public var updates: AnyPublisher { collectionViewModel.flatMap(\.updates) .combineLatest($accountViewModel.map { $0?.relationship }) .map { let (updates, relationship) = $0 if let relationship = relationship, relationship.blockedBy { return .empty } else { return updates } } .eraseToAnyPublisher() } public var title: AnyPublisher { $accountViewModel.compactMap { $0?.accountName }.eraseToAnyPublisher() } public var titleLocalizationComponents: AnyPublisher<[String], Never> { collectionViewModel.flatMap(\.titleLocalizationComponents).eraseToAnyPublisher() } public var expandAll: AnyPublisher { Empty().eraseToAnyPublisher() } public var alertItems: AnyPublisher { collectionViewModel.flatMap(\.alertItems).eraseToAnyPublisher() } public var loading: AnyPublisher { collectionViewModel.flatMap(\.loading).eraseToAnyPublisher() } public var events: AnyPublisher { accountEventsSubject .flatMap { [weak self] eventPublisher -> AnyPublisher in guard let self = self else { return Empty().eraseToAnyPublisher() } return eventPublisher.assignErrorsToAlertItem(to: \.alertItem, on: self).eraseToAnyPublisher() } .merge(with: collectionViewModel.flatMap(\.events)) .eraseToAnyPublisher() } public var searchScopeChanges: AnyPublisher { collectionViewModel.flatMap(\.searchScopeChanges).eraseToAnyPublisher() } public var nextPageMaxId: String? { collectionViewModel.value.nextPageMaxId } public var canRefresh: Bool { collectionViewModel.value.canRefresh } public var announcesNewItems: Bool { collectionViewModel.value.canRefresh } public func request(maxId: String?, minId: String?, search: Search?) { if case .statuses = collection, maxId == nil { profileService.fetchPinnedStatuses() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } collectionViewModel.value.request(maxId: maxId, minId: minId, search: nil) } public func cancelRequests() { collectionViewModel.value.cancelRequests() } public func requestNextPage(fromIndexPath indexPath: IndexPath) { collectionViewModel.value.requestNextPage(fromIndexPath: indexPath) } public func viewedAtTop(indexPath: IndexPath) { collectionViewModel.value.viewedAtTop(indexPath: indexPath) } public func select(indexPath: IndexPath) { collectionViewModel.value.select(indexPath: indexPath) } public func canSelect(indexPath: IndexPath) -> Bool { collectionViewModel.value.canSelect(indexPath: indexPath) } public func viewModel(indexPath: IndexPath) -> Any? { collectionViewModel.value.viewModel(indexPath: indexPath) } public func toggleExpandAll() { collectionViewModel.value.toggleExpandAll() } public func applyAccountListEdit(viewModel: AccountViewModel, edit: CollectionItemEvent.AccountListEdit) { collectionViewModel.value.applyAccountListEdit(viewModel: viewModel, edit: edit) } }