// // HomeTimelineNavigationBarTitleViewModel.swift // Mastodon // // Created by sxiaojian on 2021/3/15. // import Combine import Foundation import UIKit import MastodonCore final class HomeTimelineNavigationBarTitleViewModel { static let offlineCounterLimit = 3 var disposeBag = Set() private(set) var publishingProgressSubscription: AnyCancellable? // input let context: AppContext var networkErrorCount = CurrentValueSubject(0) // output let state = CurrentValueSubject(.logo) let hasNewPosts = CurrentValueSubject(false) let isOffline = CurrentValueSubject(false) let isPublishingPost = CurrentValueSubject(false) let isPublished = CurrentValueSubject(false) let publishingProgress = PassthroughSubject() init(context: AppContext) { self.context = context networkErrorCount .receive(on: DispatchQueue.main) .map { count in return count >= HomeTimelineNavigationBarTitleViewModel.offlineCounterLimit } .assign(to: \.value, on: isOffline) .store(in: &disposeBag) Publishers.CombineLatest( context.publisherService.$statusPublishers, context.publisherService.statusPublishResult.prepend(.failure(AppError.badRequest)) ) .receive(on: DispatchQueue.main) .sink { [weak self] statusPublishers, publishResult in guard let self = self else { return } if statusPublishers.isEmpty { self.isPublishingPost.value = false self.isPublished.value = false } else { self.isPublishingPost.value = true switch publishResult { case .success: self.isPublished.value = true case .failure: self.isPublished.value = false } } } .store(in: &disposeBag) Publishers.CombineLatest4( hasNewPosts.eraseToAnyPublisher(), isOffline.eraseToAnyPublisher(), isPublishingPost.eraseToAnyPublisher(), isPublished.eraseToAnyPublisher() ) .map { hasNewPosts, isOffline, isPublishingPost, isPublished -> State in guard !isPublished else { return .publishedButton } guard !isPublishingPost else { return .publishingPostLabel } guard !isOffline else { return .offlineButton } guard !hasNewPosts else { return .newPostButton } return .logo } .receive(on: DispatchQueue.main) .assign(to: \.value, on: state) .store(in: &disposeBag) // state // .removeDuplicates() // .receive(on: DispatchQueue.main) // .sink { [weak self] state in // guard let self = self else { return } // switch state { // case .publishingPostLabel: // self.setupPublishingProgress() // default: // self.suspendPublishingProgress() // } // } // .store(in: &disposeBag) } } extension HomeTimelineNavigationBarTitleViewModel { // state order by priority from low to high enum State: String { case logo case newPostButton case offlineButton case publishingPostLabel case publishedButton } } // MARK: - New post state extension HomeTimelineNavigationBarTitleViewModel { func newPostsIncoming() { hasNewPosts.value = true } private func resetNewPostState() { hasNewPosts.value = false } } // MARK: - Offline state extension HomeTimelineNavigationBarTitleViewModel { func receiveLoadingStateCompletion(_ completion: Subscribers.Completion) { switch completion { case .failure: networkErrorCount.value += 1 case .finished: networkErrorCount.value = 0 } } func handleScrollViewDidScroll(_ scrollView: UIScrollView) { guard hasNewPosts.value else { return } let contentOffsetY = scrollView.contentOffset.y let isScrollToTop = contentOffsetY < -scrollView.contentInset.top guard isScrollToTop else { return } resetNewPostState() } }