mastodon-app-ufficiale-ipho.../Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTi...

144 lines
4.4 KiB
Swift

//
// 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<AnyCancellable>()
private(set) var publishingProgressSubscription: AnyCancellable?
// input
let context: AppContext
var networkErrorCount = CurrentValueSubject<Int, Never>(0)
// output
let state = CurrentValueSubject<State, Never>(.logo)
let hasNewPosts = CurrentValueSubject<Bool, Never>(false)
let isOffline = CurrentValueSubject<Bool, Never>(false)
let isPublishingPost = CurrentValueSubject<Bool, Never>(false)
let isPublished = CurrentValueSubject<Bool, Never>(false)
let publishingProgress = PassthroughSubject<Float, Never>()
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<Error>) {
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()
}
}