diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index d53252220..f84e8384d 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -135,7 +135,6 @@ 2DFAD5372617010500F9EE7C /* SearchingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFAD5362617010500F9EE7C /* SearchingTableViewCell.swift */; }; 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; }; 5B24BBDB262DB14800A9381B /* ReportViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */; }; - 5B24BBDC262DB14800A9381B /* ReportViewModel+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD9262DB14800A9381B /* ReportViewModel+Provider.swift */; }; 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBE1262DB19100A9381B /* APIService+Report.swift */; }; 5B8E055826319E47006E3C53 /* ReportFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8E055726319E47006E3C53 /* ReportFooterView.swift */; }; 5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C456262599800002E742 /* SettingsViewModel.swift */; }; @@ -561,7 +560,6 @@ 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = ""; }; 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = ""; }; 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+Diffable.swift"; sourceTree = ""; }; - 5B24BBD9262DB14800A9381B /* ReportViewModel+Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+Provider.swift"; sourceTree = ""; }; 5B24BBE1262DB19100A9381B /* APIService+Report.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+Report.swift"; sourceTree = ""; }; 5B8E055726319E47006E3C53 /* ReportFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportFooterView.swift; sourceTree = ""; }; 5B90C456262599800002E742 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; @@ -1249,7 +1247,6 @@ children = ( 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */, 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */, - 5B24BBD9262DB14800A9381B /* ReportViewModel+Provider.swift */, 5BB04FEE262F0DCB0043BFF6 /* ReportViewModel+Data.swift */, 5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */, 5BB04FDA262EA3070043BFF6 /* ReportHeaderView.swift */, @@ -2569,7 +2566,6 @@ 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */, DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */, - 5B24BBDC262DB14800A9381B /* ReportViewModel+Provider.swift in Sources */, 2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */, 2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */, 2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */, diff --git a/Mastodon/Diffiable/Item/Item.swift b/Mastodon/Diffiable/Item/Item.swift index e169be66f..04a1262d5 100644 --- a/Mastodon/Diffiable/Item/Item.swift +++ b/Mastodon/Diffiable/Item/Item.swift @@ -32,6 +32,9 @@ enum Item { case bottomLoader case emptyStateHeader(attribute: EmptyStateHeaderAttribute) + + // reports + case reportStatus(objectID: NSManagedObjectID, attribute: ReportStatusAttribute) } extension Item { @@ -79,6 +82,15 @@ extension Item { hasher.combine(id) } } + + class ReportStatusAttribute: StatusAttribute { + var isSelected: Bool + + init(isSeparatorLineHidden: Bool = false, isSelected: Bool = false) { + self.isSelected = isSelected + super.init(isSeparatorLineHidden: isSeparatorLineHidden) + } + } } extension Item: Equatable { @@ -106,6 +118,8 @@ extension Item: Equatable { return true case (.emptyStateHeader(let attributeLeft), .emptyStateHeader(let attributeRight)): return attributeLeft == attributeRight + case (.reportStatus(let objectIDLeft, _), .reportStatus(let objectIDRight, _)): + return objectIDLeft == objectIDRight default: return false } @@ -139,6 +153,8 @@ extension Item: Hashable { hasher.combine(String(describing: Item.bottomLoader.self)) case .emptyStateHeader(let attribute): hasher.combine(attribute) + case .reportStatus(let objectID, _): + hasher.combine(objectID) } } } diff --git a/Mastodon/Diffiable/Section/ReportSection.swift b/Mastodon/Diffiable/Section/ReportSection.swift index 7567488c5..86a12a9a3 100644 --- a/Mastodon/Diffiable/Section/ReportSection.swift +++ b/Mastodon/Diffiable/Section/ReportSection.swift @@ -23,8 +23,7 @@ extension ReportSection { for tableView: UITableView, dependency: NeedsDependency, managedObjectContext: NSManagedObjectContext, - timestampUpdatePublisher: AnyPublisher, - reportdStatusDelegate: ReportedStatusTableViewCellDelegate + timestampUpdatePublisher: AnyPublisher ) -> UITableViewDiffableDataSource { UITableViewDiffableDataSource(tableView: tableView) {[ weak dependency @@ -32,7 +31,7 @@ extension ReportSection { guard let dependency = dependency else { return UITableViewCell() } switch item { - case .status(let objectID, let attribute): + case .reportStatus(let objectID, let attribute): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportedStatusTableViewCell.self), for: indexPath) as! ReportedStatusTableViewCell let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value let requestUserID = activeMastodonAuthenticationBox?.userID ?? "" @@ -49,8 +48,7 @@ extension ReportSection { ) } - let isSelected = reportdStatusDelegate.reportedStatus(cell: cell, isSelected: indexPath) - cell.setupSelected(isSelected) + cell.setupSelected(attribute.isSelected) return cell default: return nil diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index 4f09142a7..97e4cdf9c 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -125,6 +125,8 @@ extension StatusSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineHeaderTableViewCell.self), for: indexPath) as! TimelineHeaderTableViewCell StatusSection.configureEmptyStateHeader(cell: cell, attribute: attribute) return cell + case .reportStatus: + return UITableViewCell() } } } diff --git a/Mastodon/Scene/Report/ReportViewModel+Data.swift b/Mastodon/Scene/Report/ReportViewModel+Data.swift index 4df2ccb20..005098b47 100644 --- a/Mastodon/Scene/Report/ReportViewModel+Data.swift +++ b/Mastodon/Scene/Report/ReportViewModel+Data.swift @@ -71,23 +71,24 @@ extension ReportViewModel { diffableDataSource.apply(snapshot, animatingDifferences: !items.isEmpty) } - var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:] + var oldSnapshotAttributeDict: [NSManagedObjectID : Item.ReportStatusAttribute] = [:] let oldSnapshot = diffableDataSource.snapshot() for item in oldSnapshot.itemIdentifiers { - guard case let .status(objectID, attribute) = item else { continue } + guard case let .reportStatus(objectID, attribute) = item else { continue } oldSnapshotAttributeDict[objectID] = attribute } for objectID in objectIDs { - let attribute = oldSnapshotAttributeDict[objectID] ?? Item.StatusAttribute() - let item = Item.status(objectID: objectID, attribute: attribute) + let attribute = oldSnapshotAttributeDict[objectID] ?? Item.ReportStatusAttribute() + let item = Item.reportStatus(objectID: objectID, attribute: attribute) items.append(item) guard let status = managedObjectContext.object(with: objectID) as? Status else { continue } if status.id == self.statusId { - self.selectedItems.append(item) + attribute.isSelected = true + self.reportQuery.append(statusId: status.id) self.continueEnableSubject.send(true) } } diff --git a/Mastodon/Scene/Report/ReportViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportViewModel+Diffable.swift index 38f2edb19..f737381b1 100644 --- a/Mastodon/Scene/Report/ReportViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportViewModel+Diffable.swift @@ -13,8 +13,7 @@ import CoreDataStack extension ReportViewModel { func setupDiffableDataSource( for tableView: UITableView, - dependency: NeedsDependency, - reportdStatusDelegate: ReportedStatusTableViewCellDelegate + dependency: NeedsDependency ) { let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() @@ -25,8 +24,7 @@ extension ReportViewModel { for: tableView, dependency: dependency, managedObjectContext: statusFetchedResultsController.fetchedResultsController.managedObjectContext, - timestampUpdatePublisher: timestampUpdatePublisher, - reportdStatusDelegate: reportdStatusDelegate + timestampUpdatePublisher: timestampUpdatePublisher ) // set empty section to make update animation top-to-bottom style diff --git a/Mastodon/Scene/Report/ReportViewModel+Provider.swift b/Mastodon/Scene/Report/ReportViewModel+Provider.swift deleted file mode 100644 index 052fc2ba1..000000000 --- a/Mastodon/Scene/Report/ReportViewModel+Provider.swift +++ /dev/null @@ -1,86 +0,0 @@ -//// -//// ReportViewModel+Provider.swift -//// Mastodon -//// -//// Created by ihugo on 2021/4/19. -//// -// -//import Combine -//import CoreData -//import CoreDataStack -//import Foundation -//import MastodonSDK -//import UIKit -//import os.log -// -//extension ReportViewController: StatusProvider { -// func status() -> Future { -// return Future { promise in promise(.success(nil)) } -// } -// -// func status(for cell: UITableViewCell?, indexPath: IndexPath?) -> Future { -// return Future { promise in -// guard let diffableDataSource = self.viewModel.diffableDataSource else { -// assertionFailure() -// promise(.success(nil)) -// return -// } -// guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }), -// let item = diffableDataSource.itemIdentifier(for: indexPath) else { -// promise(.success(nil)) -// return -// } -// -// switch item { -// case .status(let objectID, _): -// let managedObjectContext = self.viewModel.statusFetchedResultsController.fetchedResultsController.managedObjectContext -// managedObjectContext.perform { -// let status = managedObjectContext.object(with: objectID) as? Status -// promise(.success(status)) -// } -// default: -// promise(.success(nil)) -// } -// } -// } -// -// func status(for cell: UICollectionViewCell) -> Future { -// return Future { promise in promise(.success(nil)) } -// } -// -// var managedObjectContext: NSManagedObjectContext { -// return viewModel.statusFetchedResultsController.fetchedResultsController.managedObjectContext -// } -// -// var tableViewDiffableDataSource: UITableViewDiffableDataSource? { -// return viewModel.diffableDataSource -// } -// -// func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item? { -// guard let diffableDataSource = self.viewModel.diffableDataSource else { -// assertionFailure() -// return nil -// } -// -// guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }), -// let item = diffableDataSource.itemIdentifier(for: indexPath) else { -// return nil -// } -// -// return item -// } -// -// func items(indexPaths: [IndexPath]) -> [Item] { -// guard let diffableDataSource = self.viewModel.diffableDataSource else { -// assertionFailure() -// return [] -// } -// -// var items: [Item] = [] -// for indexPath in indexPaths { -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { continue } -// items.append(item) -// } -// return items -// } -//} diff --git a/Mastodon/Scene/Report/ReportViewModel.swift b/Mastodon/Scene/Report/ReportViewModel.swift index 6feedc0b8..a7bba0a7e 100644 --- a/Mastodon/Scene/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/ReportViewModel.swift @@ -26,8 +26,6 @@ class ReportViewModel: NSObject, NeedsDependency { weak var coordinator: SceneCoordinator! { willSet { precondition(coordinator == nil) } } var userId: String var statusId: String? - var selectedItems = [Item]() - var comment: String? var reportQuery: FileReportQuery var disposeBag = Set() @@ -74,7 +72,7 @@ class ReportViewModel: NSObject, NeedsDependency { self.reportQuery = FileReportQuery( accountId: userId, - statusIds: nil, + statusIds: [], comment: nil, forward: nil ) @@ -90,8 +88,7 @@ class ReportViewModel: NSObject, NeedsDependency { setupDiffableDataSource( for: input.tableView, - dependency: self, - reportdStatusDelegate: self + dependency: self ) // data binding @@ -125,38 +122,31 @@ class ReportViewModel: NSObject, NeedsDependency { func bindData(input: Input) { input.didToggleSelected.sink { [weak self] (item) in guard let self = self else { return } - guard case let .status(objectID, attribute) = item else { return } + guard case let .reportStatus(objectID, attribute) = item else { return } guard var snapshot = self.diffableDataSource?.snapshot() else { return } let managedObjectContext = self.statusFetchedResultsController.fetchedResultsController.managedObjectContext guard let status = managedObjectContext.object(with: objectID) as? Status else { return } - var items = [Item]() - if let index = self.selectedItems.firstIndex(of: item) { - self.selectedItems.remove(at: index) - items.append(.status(objectID: objectID, attribute: attribute)) - - if let index = self.reportQuery.statusIds?.firstIndex(of: status.id) { - self.reportQuery.statusIds?.remove(at: index) - } + attribute.isSelected = !attribute.isSelected + if attribute.isSelected { + self.reportQuery.append(statusId: status.id) } else { - self.selectedItems.append(item) - items.append(.status(objectID: objectID, attribute: attribute)) - self.reportQuery.statusIds?.append(status.id) + self.reportQuery.remove(statusId: status.id) } snapshot.reloadItems([item]) self.diffableDataSource?.apply(snapshot, animatingDifferences: false) - let continueEnable = self.selectedItems.count > 0 + let continueEnable = (self.reportQuery.statusIds?.count ?? 0) > 0 self.continueEnableSubject.send(continueEnable) } .store(in: &disposeBag) input.comment.assign( to: \.comment, - on: self + on: self.reportQuery ) .store(in: &disposeBag) input.comment.sink { [weak self] (comment) in @@ -170,7 +160,7 @@ class ReportViewModel: NSObject, NeedsDependency { func bindForStep1(input: Input) { let skip = input.step1Skip.map { [weak self] value -> Void in guard let self = self else { return value } - self.selectedItems.removeAll() + self.reportQuery.statusIds?.removeAll() return value } @@ -185,29 +175,13 @@ class ReportViewModel: NSObject, NeedsDependency { func bindForStep2(input: Input, domain: String, activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) { let skip = input.step2Skip.map { [weak self] value -> Void in guard let self = self else { return value } - self.comment = nil + self.reportQuery.comment = nil return value } Publishers.Merge(skip, input.step2Continue) .sink { [weak self] _ in guard let self = self else { return } - let managedObjectContext = self.statusFetchedResultsController.fetchedResultsController.managedObjectContext - - self.reportQuery.comment = self.comment - - var selectedStatusIds = [String]() - self.selectedItems.forEach { (item) in - guard case .status(let objectId, _) = item else { - return - } - guard let status = managedObjectContext.object(with: objectId) as? Status else { - return - } - selectedStatusIds.append(status.id) - } - self.reportQuery.statusIds = selectedStatusIds - self.context.apiService.report( domain: domain, query: self.reportQuery, @@ -237,13 +211,3 @@ class ReportViewModel: NSObject, NeedsDependency { .store(in: &disposeBag) } } - -extension ReportViewModel: ReportedStatusTableViewCellDelegate { - func reportedStatus(cell: ReportedStatusTableViewCell, isSelected indexPath: IndexPath) -> Bool { - guard let item = diffableDataSource?.itemIdentifier(for: indexPath) else { - return false - } - - return selectedItems.contains(item) - } -} diff --git a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift index a25c8b507..8329124cc 100644 --- a/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift +++ b/Mastodon/Scene/Report/ReportedStatusTableviewCell.swift @@ -13,15 +13,10 @@ import CoreData import CoreDataStack import ActiveLabel -protocol ReportedStatusTableViewCellDelegate: class { - func reportedStatus(cell: ReportedStatusTableViewCell, isSelected indexPath: IndexPath) -> Bool -} - final class ReportedStatusTableViewCell: UITableViewCell, StatusCell { static let bottomPaddingHeight: CGFloat = 10 - weak var delegate: ReportedStatusTableViewCellDelegate? var disposeBag = Set() var pollCountdownSubscription: AnyCancellable? var observations = Set() diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift index a0afabb2b..17bcd5331 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Report.swift @@ -75,5 +75,19 @@ public extension Mastodon.API.Reports { self.comment = comment self.forward = forward } + + public func append(statusId: String) { + guard self.statusIds?.contains(statusId) != true else { return } + if self.statusIds == nil { + self.statusIds = [] + } + + self.statusIds?.append(statusId) + } + + public func remove(statusId: String) { + guard let index = self.statusIds?.firstIndex(of: statusId) else { return } + self.statusIds?.remove(at: index) + } } }