// // HomeTimelineViewModel+LoadOldestState.swift // Mastodon // // Created by sxiaojian on 2021/2/5. // import os.log import Foundation import GameplayKit extension HomeTimelineViewModel { class LoadOldestState: GKState { weak var viewModel: HomeTimelineViewModel? init(viewModel: HomeTimelineViewModel) { self.viewModel = viewModel } override func didEnter(from previousState: GKState?) { os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription) viewModel?.loadOldestStateMachinePublisher.send(self) } } } extension HomeTimelineViewModel.LoadOldestState { class Initial: HomeTimelineViewModel.LoadOldestState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { guard let viewModel = viewModel else { return false } guard !(viewModel.fetchedResultsController.fetchedObjects ?? []).isEmpty else { return false } return stateClass == Loading.self } } class Loading: HomeTimelineViewModel.LoadOldestState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { return stateClass == Fail.self || stateClass == Idle.self || stateClass == NoMore.self } override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) guard let viewModel = viewModel, let stateMachine = stateMachine else { return } guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { assertionFailure() stateMachine.enter(Fail.self) return } guard let last = viewModel.fetchedResultsController.fetchedObjects?.last else { stateMachine.enter(Idle.self) return } // TODO: only set large count when using Wi-Fi let maxID = last.toot.id viewModel.context.apiService.homeTimeline(domain: activeMastodonAuthenticationBox.domain, maxID: maxID, authorizationBox: activeMastodonAuthenticationBox) .delay(for: .seconds(1), scheduler: DispatchQueue.main) .receive(on: DispatchQueue.main) .sink { completion in viewModel.homeTimelineNavigationBarState.receiveCompletion(completion: completion) switch completion { case .failure(let error): os_log("%{public}s[%{public}ld], %{public}s: fetch toots failed. %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) case .finished: // handle isFetchingLatestTimeline in fetch controller delegate break } } receiveValue: { response in let toots = response.value // enter no more state when no new toots if toots.isEmpty || (toots.count == 1 && toots[0].id == maxID) { stateMachine.enter(NoMore.self) } else { stateMachine.enter(Idle.self) } } .store(in: &viewModel.disposeBag) } } class Fail: HomeTimelineViewModel.LoadOldestState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { return stateClass == Loading.self || stateClass == Idle.self } } class Idle: HomeTimelineViewModel.LoadOldestState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { return stateClass == Loading.self } } class NoMore: HomeTimelineViewModel.LoadOldestState { override func isValidNextState(_ stateClass: AnyClass) -> Bool { // reset state if needs return stateClass == Idle.self } override func didEnter(from previousState: GKState?) { guard let viewModel = viewModel else { return } guard let diffableDataSource = viewModel.diffableDataSource else { assertionFailure() return } var snapshot = diffableDataSource.snapshot() snapshot.deleteItems([.bottomLoader]) diffableDataSource.apply(snapshot) } } }