From 04d427ea93cec4a22843950ceacd9278cb3dea4e Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Apr 2021 13:18:27 +0800 Subject: [PATCH] feat: make content warning works in the notification scene --- .../xcschemes/xcschememanagement.plist | 2 +- .../Diffiable/Item/NotificationItem.swift | 6 +- .../Section/NotificationSection.swift | 23 ++++--- .../NotificationViewController.swift | 23 ++++++- .../NotificationViewModel+diffable.swift | 31 +++++++-- .../NotificationStatusTableViewCell.swift | 63 +++++++++++++++++-- .../NotificationTableViewCell.swift | 5 ++ 7 files changed, 125 insertions(+), 28 deletions(-) diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 6ec23cf5d..18c8840d8 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 10 + 13 Mastodon - RTL.xcscheme_^#shared#^_ diff --git a/Mastodon/Diffiable/Item/NotificationItem.swift b/Mastodon/Diffiable/Item/NotificationItem.swift index ba0d0c140..f26d2e43d 100644 --- a/Mastodon/Diffiable/Item/NotificationItem.swift +++ b/Mastodon/Diffiable/Item/NotificationItem.swift @@ -9,7 +9,7 @@ import CoreData import Foundation enum NotificationItem { - case notification(objectID: NSManagedObjectID) + case notification(objectID: NSManagedObjectID, attribute: Item.StatusAttribute) case bottomLoader } @@ -17,7 +17,7 @@ enum NotificationItem { extension NotificationItem: Equatable { static func == (lhs: NotificationItem, rhs: NotificationItem) -> Bool { switch (lhs, rhs) { - case (.notification(let idLeft), .notification(let idRight)): + case (.notification(let idLeft, _), .notification(let idRight, _)): return idLeft == idRight case (.bottomLoader, .bottomLoader): return true @@ -30,7 +30,7 @@ extension NotificationItem: Equatable { extension NotificationItem: Hashable { func hash(into hasher: inout Hasher) { switch self { - case .notification(let id): + case .notification(let id, _): hasher.combine(id) case .bottomLoader: hasher.combine(String(describing: NotificationItem.bottomLoader.self)) diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 5ccab431c..9c59350b4 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -22,15 +22,14 @@ extension NotificationSection { timestampUpdatePublisher: AnyPublisher, managedObjectContext: NSManagedObjectContext, delegate: NotificationTableViewCellDelegate, - dependency: NeedsDependency, - requestUserID: String + dependency: NeedsDependency ) -> UITableViewDiffableDataSource { UITableViewDiffableDataSource(tableView: tableView) { [weak delegate, weak dependency] (tableView, indexPath, notificationItem) -> UITableViewCell? in guard let dependency = dependency else { return nil } switch notificationItem { - case .notification(let objectID): + case .notification(let objectID, let attribute): let notification = managedObjectContext.object(with: objectID) as! MastodonNotification guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw) else { @@ -46,14 +45,18 @@ extension NotificationSection { if let status = notification.status { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell cell.delegate = delegate + let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value + let requestUserID = activeMastodonAuthenticationBox?.userID ?? "" let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height) - StatusSection.configure(cell: cell, - dependency: dependency, - readableLayoutFrame: frame, - timestampUpdatePublisher: timestampUpdatePublisher, - status: status, - requestUserID: requestUserID, - statusItemAttribute: Item.StatusAttribute(isStatusTextSensitive: false, isStatusSensitive: false)) + StatusSection.configure( + cell: cell, + dependency: dependency, + readableLayoutFrame: frame, + timestampUpdatePublisher: timestampUpdatePublisher, + status: status, + requestUserID: requestUserID, + statusItemAttribute: attribute + ) timestampUpdatePublisher .sink { _ in let timeText = notification.createAt.shortTimeAgoSinceNow diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index ad9a7472e..57b5dc639 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -36,6 +36,7 @@ final class NotificationViewController: UIViewController, NeedsDependency { tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) tableView.tableFooterView = UIView() tableView.estimatedRowHeight = UITableView.automaticDimension + tableView.backgroundColor = .clear return tableView }() @@ -45,13 +46,14 @@ final class NotificationViewController: UIViewController, NeedsDependency { extension NotificationViewController { override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color navigationItem.titleView = segmentControl segmentControl.addTarget(self, action: #selector(NotificationViewController.segmentedControlValueChanged(_:)), for: .valueChanged) tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), @@ -65,6 +67,7 @@ extension NotificationViewController { viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self viewModel.setupDiffableDataSource(for: tableView, delegate: self, dependency: self) viewModel.viewDidLoad.send() + // bind refresh control viewModel.isFetchingLatestNotification .receive(on: DispatchQueue.main) @@ -83,6 +86,8 @@ extension NotificationViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + tableView.deselectRow(with: transitionCoordinator, animated: animated) + // needs trigger manually after onboarding dismiss setNeedsStatusBarAppearanceUpdate() } @@ -159,11 +164,10 @@ extension NotificationViewController { extension NotificationViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) guard let diffableDataSource = viewModel.diffableDataSource else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } switch item { - case .notification(let objectID): + case .notification(let objectID, _): let notification = context.managedObjectContext.object(with: objectID) as! MastodonNotification if let status = notification.status { let viewModel = ThreadViewModel(context: context, optionalStatus: status) @@ -199,6 +203,7 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl } } +// MARK: - NotificationTableViewCellDelegate extension NotificationViewController: NotificationTableViewCellDelegate { func userAvatarDidPressed(notification: MastodonNotification) { let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) @@ -210,6 +215,18 @@ extension NotificationViewController: NotificationTableViewCellDelegate { func parent() -> UIViewController { self } + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) { + StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + } + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { + StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + } + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { + StatusProviderFacade.responseToStatusContentWarningRevealAction(dependency: self, cell: cell) + } } // MARK: - UIScrollViewDelegate diff --git a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift index 5bd2d92dd..cd28c5f5a 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift @@ -20,16 +20,13 @@ extension NotificationViewModel { .autoconnect() .share() .eraseToAnyPublisher() - guard let userid = activeMastodonAuthenticationBox.value?.userID else { - return - } + diffableDataSource = NotificationSection.tableViewDiffableDataSource( for: tableView, timestampUpdatePublisher: timestampUpdatePublisher, managedObjectContext: context.managedObjectContext, delegate: delegate, - dependency: dependency, - requestUserID: userid + dependency: dependency ) } } @@ -67,9 +64,31 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate { DispatchQueue.main.async { let oldSnapshot = diffableDataSource.snapshot() + var oldSnapshotAttributeDict: [NSManagedObjectID : Item.StatusAttribute] = [:] + for item in oldSnapshot.itemIdentifiers { + guard case let .notification(objectID, attribute) = item else { continue } + oldSnapshotAttributeDict[objectID] = attribute + } var newSnapshot = NSDiffableDataSourceSnapshot() newSnapshot.appendSections([.main]) - newSnapshot.appendItems(notifications.map { NotificationItem.notification(objectID: $0.objectID) }, toSection: .main) + let items: [NotificationItem] = notifications.map { notification in + let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute() + +// let attribute: Item.StatusAttribute = { +// if let attribute = oldSnapshotAttributeDict[notification.objectID] { +// return attribute +// } else if let status = notification.status { +// let attribute = Item.StatusAttribute() +// let isSensitive = status.sensitive || !(status.spoilerText ?? "").isEmpty +// attribute.isRevealing.value = !isSensitive +// return attribute +// } else { +// return Item.StatusAttribute() +// } +// }() + return NotificationItem.notification(objectID: notification.objectID, attribute: attribute) + } + newSnapshot.appendItems(items, toSection: .main) if !notifications.isEmpty, self.noMoreNotification.value == false { newSnapshot.appendItems([.bottomLoader], toSection: .main) } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 871adcaeb..7b76dd2f0 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -8,6 +8,7 @@ import Combine import Foundation import UIKit +import ActiveLabel final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { static let actionImageBorderWidth: CGFloat = 2 @@ -78,8 +79,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { override func prepareForReuse() { super.prepareForReuse() avatatImageView.af.cancelImageRequest() - statusView.isStatusTextSensitive = false - statusView.cleanUpContentWarning() + statusView.updateContentWarningDisplay(isHidden: true, animated: false) statusView.pollTableView.dataSource = nil statusView.playerContainerView.reset() statusView.playerContainerView.isHidden = true @@ -99,6 +99,9 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { override func layoutSubviews() { super.layoutSubviews() + + // precondition: app is active + guard UIApplication.shared.applicationState == .active else { return } DispatchQueue.main.async { self.statusView.drawContentWarningImageView() } @@ -107,6 +110,8 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { extension NotificationStatusTableViewCell { func configure() { + backgroundColor = Asset.Colors.Background.systemBackground.color + let containerStackView = UIStackView() containerStackView.axis = .horizontal containerStackView.alignment = .top @@ -154,7 +159,6 @@ extension NotificationStatusTableViewCell { actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor) ]) - let actionStackView = UIStackView() actionStackView.axis = .horizontal actionStackView.distribution = .fill @@ -187,13 +191,12 @@ extension NotificationStatusTableViewCell { statusBorder.trailingAnchor.constraint(equalTo: statusView.trailingAnchor, constant: 12), ]) + statusView.delegate = self statusStackView.addArrangedSubview(statusBorder) containerStackView.addArrangedSubview(statusStackView) - statusView.contentWarningBlurContentImageView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color - statusView.isUserInteractionEnabled = false // remove item don't display statusView.actionToolbarContainer.removeFromStackView() // it affect stackView's height,need remove @@ -206,4 +209,54 @@ extension NotificationStatusTableViewCell { statusBorder.layer.borderColor = Asset.Colors.Border.notification.color.cgColor actionImageBackground.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor } + + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + + resetContentOverlayBlurImageBackgroundColor(selected: highlighted) + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + resetContentOverlayBlurImageBackgroundColor(selected: selected) + } + + private func resetContentOverlayBlurImageBackgroundColor(selected: Bool) { + let imageViewBackgroundColor: UIColor? = selected ? selectedBackgroundView?.backgroundColor : backgroundColor + statusView.contentWarningOverlayView.blurContentImageView.backgroundColor = imageViewBackgroundColor + } +} + +// MARK: - StatusViewDelegate +extension NotificationStatusTableViewCell: StatusViewDelegate { + func statusView(_ statusView: StatusView, headerInfoLabelDidPressed label: UILabel) { + // do nothing + } + + func statusView(_ statusView: StatusView, avatarButtonDidPressed button: UIButton) { + // do nothing + } + + func statusView(_ statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) { + delegate?.notificationStatusTableViewCell(self, statusView: statusView, revealContentWarningButtonDidPressed: button) + } + + func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { + delegate?.notificationStatusTableViewCell(self, statusView: statusView, contentWarningOverlayViewDidPressed: contentWarningOverlayView) + } + + func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { + delegate?.notificationStatusTableViewCell(self, statusView: statusView, playerContainerView: playerContainerView, contentWarningOverlayViewDidPressed: contentWarningOverlayView) + } + + func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton) { + // do nothing + } + + func statusView(_ statusView: StatusView, activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { + // do nothing + } + + } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift index 60b43ac35..619bffa17 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift @@ -16,6 +16,11 @@ protocol NotificationTableViewCellDelegate: AnyObject { func parent() -> UIViewController func userAvatarDidPressed(notification: MastodonNotification) + + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) + func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) + } final class NotificationTableViewCell: UITableViewCell {