refactor: remove UI part from ReportViewmodel

This commit is contained in:
ihugo 2021-04-25 15:36:40 +08:00
parent 479f21fd9f
commit cbc828eec2
4 changed files with 67 additions and 52 deletions

View File

@ -362,7 +362,6 @@ extension HomeTimelineViewController {
// 106093402888557459
let viewModel = ReportViewModel(
context: self.context,
coordinator: self.coordinator,
domain: authenticationBox.domain,
userId: userId,
statusId: statusId

View File

@ -12,6 +12,7 @@ import CoreDataStack
import os.log
import UIKit
import TwitterTextEditor
import MastodonSDK
class ReportViewController: UIViewController, NeedsDependency {
static let kAnimationDuration: TimeInterval = 0.33
@ -84,6 +85,12 @@ class ReportViewController: UIViewController, NeedsDependency {
super.viewDidLoad()
setupView()
viewModel.setupDiffableDataSource(
for: tableView,
dependency: self
)
bindViewModel()
bindActions()
}
@ -127,8 +134,7 @@ class ReportViewController: UIViewController, NeedsDependency {
step1Skip: step1Skip.eraseToAnyPublisher(),
step2Continue: step2Continue.eraseToAnyPublisher(),
step2Skip: step2Skip.eraseToAnyPublisher(),
cancel: cancel.eraseToAnyPublisher(),
tableView: tableView
cancel: cancel.eraseToAnyPublisher()
)
let output = viewModel.transform(input: input)
output?.currentStep
@ -161,10 +167,26 @@ class ReportViewController: UIViewController, NeedsDependency {
.assign(to: \.nextStepButton.isEnabled, on: footer)
.store(in: &disposeBag)
output?.reportSuccess
output?.reportResult
.print()
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] (_) in
.sink(receiveCompletion: { _ in
}, receiveValue: { [weak self] data in
let (success, error) = data
if success {
self?.dismiss(animated: true, completion: nil)
} else if let error = error {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fail to file a report : %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(okAction)
self?.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
)
}
})
.store(in: &disposeBag)
}

View File

@ -13,7 +13,7 @@ import MastodonSDK
import UIKit
import os.log
class ReportViewModel: NSObject, NeedsDependency {
class ReportViewModel: NSObject {
typealias FileReportQuery = Mastodon.API.Reports.FileReportQuery
enum Step: Int {
@ -23,7 +23,6 @@ class ReportViewModel: NSObject, NeedsDependency {
// confirm set only once
weak var context: AppContext! { willSet { precondition(context == nil) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(coordinator == nil) } }
var userId: String
var statusId: String?
@ -34,7 +33,6 @@ class ReportViewModel: NSObject, NeedsDependency {
var diffableDataSource: UITableViewDiffableDataSource<ReportSection, Item>?
let continueEnableSubject = CurrentValueSubject<Bool, Never>(false)
let sendEnableSubject = CurrentValueSubject<Bool, Never>(false)
let reportSuccess = PassthroughSubject<Void, Never>()
struct Input {
let didToggleSelected: AnyPublisher<Item, Never>
@ -44,24 +42,21 @@ class ReportViewModel: NSObject, NeedsDependency {
let step2Continue: AnyPublisher<Void, Never>
let step2Skip: AnyPublisher<Void, Never>
let cancel: AnyPublisher<Void, Never>
let tableView: UITableView
}
struct Output {
let currentStep: AnyPublisher<Step, Never>
let continueEnableSubject: AnyPublisher<Bool, Never>
let sendEnableSubject: AnyPublisher<Bool, Never>
let reportSuccess: AnyPublisher<Void, Never>
let reportResult: AnyPublisher<(Bool, Error?), Never>
}
init(context: AppContext,
coordinator: SceneCoordinator,
domain: String,
userId: String,
statusId: String?
) {
self.context = context
self.coordinator = coordinator
self.userId = userId
self.statusId = statusId
self.statusFetchedResultsController = StatusFetchedResultsController(
@ -86,17 +81,12 @@ class ReportViewModel: NSObject, NeedsDependency {
}
let domain = activeMastodonAuthenticationBox.domain
setupDiffableDataSource(
for: input.tableView,
dependency: self
)
// data binding
bindData(input: input)
// step1 and step2 binding
bindForStep1(input: input)
bindForStep2(
let reportResult = bindForStep2(
input: input,
domain: domain,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox
@ -114,7 +104,7 @@ class ReportViewModel: NSObject, NeedsDependency {
currentStep: currentStep.eraseToAnyPublisher(),
continueEnableSubject: continueEnableSubject.eraseToAnyPublisher(),
sendEnableSubject: sendEnableSubject.eraseToAnyPublisher(),
reportSuccess: reportSuccess.eraseToAnyPublisher()
reportResult: reportResult
)
}
@ -172,42 +162,35 @@ class ReportViewModel: NSObject, NeedsDependency {
.store(in: &disposeBag)
}
func bindForStep2(input: Input, domain: String, activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) {
func bindForStep2(input: Input, domain: String, activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) -> AnyPublisher<(Bool, Error?), Never> {
let skip = input.step2Skip.map { [weak self] value -> Void in
guard let self = self else { return value }
self.reportQuery.comment = nil
return value
}
Publishers.Merge(skip, input.step2Continue)
.sink { [weak self] _ in
guard let self = self else { return }
self.context.apiService.report(
return Publishers.Merge(skip, input.step2Continue)
.flatMap { [weak self] (_) -> AnyPublisher<(Bool, Error?), Never> in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}
return self.context.apiService.report(
domain: domain,
query: self.reportQuery,
mastodonAuthenticationBox: activeMastodonAuthenticationBox
)
.sink { [weak self](data) in
switch data {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fail to file a report : %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(okAction)
self?.coordinator.present(
scene: .alertController(alertController: alertController),
from: nil,
transition: .alertController(animated: true, completion: nil)
)
case .finished:
self?.reportSuccess.send()
.map({ (content) -> (Bool, Error?) in
return (true, nil)
})
.eraseToAnyPublisher()
.tryCatch({ (error) -> AnyPublisher<(Bool, Error?), Never> in
return Just((false, error)).eraseToAnyPublisher()
})
// to covert to AnyPublisher<(Bool, Error?), Never>
.replaceError(with: (false, nil))
.eraseToAnyPublisher()
}
} receiveValue: { (data) in
}
.store(in: &self.disposeBag)
}
.store(in: &disposeBag)
.eraseToAnyPublisher()
}
}

View File

@ -7,6 +7,7 @@
import Combine
import Foundation
import enum NIOHTTP1.HTTPResponseStatus
extension Mastodon.API.Reports {
static func reportsEndpointURL(domain: String) -> URL {
@ -39,13 +40,23 @@ extension Mastodon.API.Reports {
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
if let response = response as? HTTPURLResponse {
guard let response = response as? HTTPURLResponse else {
assertionFailure()
throw NSError()
}
if response.statusCode == 200 {
return Mastodon.Response.Content(
value: response.statusCode == 200,
value: true,
response: response
)
} else {
let httpResponseStatus = HTTPResponseStatus(statusCode: response.statusCode)
throw Mastodon.API.Error(
httpResponseStatus: httpResponseStatus,
mastodonError: nil
)
}
return Mastodon.Response.Content(value: false, response: response)
}
.eraseToAnyPublisher()
}