diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index fca20c92b..eaad67b7c 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -362,7 +362,6 @@ extension HomeTimelineViewController { // 106093402888557459 let viewModel = ReportViewModel( context: self.context, - coordinator: self.coordinator, domain: authenticationBox.domain, userId: userId, statusId: statusId diff --git a/Mastodon/Scene/Report/ReportViewController.swift b/Mastodon/Scene/Report/ReportViewController.swift index 14eed14ca..0bdd72bc9 100644 --- a/Mastodon/Scene/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/ReportViewController.swift @@ -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,12 +167,28 @@ 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 - self?.dismiss(animated: true, completion: nil) - }) - .store(in: &disposeBag) + .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) } private func setupNavigation() { diff --git a/Mastodon/Scene/Report/ReportViewModel.swift b/Mastodon/Scene/Report/ReportViewModel.swift index a7bba0a7e..8ee5a44a9 100644 --- a/Mastodon/Scene/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/ReportViewModel.swift @@ -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? let continueEnableSubject = CurrentValueSubject(false) let sendEnableSubject = CurrentValueSubject(false) - let reportSuccess = PassthroughSubject() struct Input { let didToggleSelected: AnyPublisher @@ -44,24 +42,21 @@ class ReportViewModel: NSObject, NeedsDependency { let step2Continue: AnyPublisher let step2Skip: AnyPublisher let cancel: AnyPublisher - let tableView: UITableView } struct Output { let currentStep: AnyPublisher let continueEnableSubject: AnyPublisher let sendEnableSubject: AnyPublisher - let reportSuccess: AnyPublisher + 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() - } - - } receiveValue: { (data) in - } - .store(in: &self.disposeBag) + .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() } - .store(in: &disposeBag) + .eraseToAnyPublisher() } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift index 17bcd5331..1c63f744f 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift @@ -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() }