feat: make content warning works in the notification scene

This commit is contained in:
CMK 2021-04-20 13:18:27 +08:00
parent f8127428dd
commit 04d427ea93
7 changed files with 125 additions and 28 deletions

View File

@ -7,7 +7,7 @@
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>10</integer>
<integer>13</integer>
</dict>
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>

View File

@ -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))

View File

@ -22,15 +22,14 @@ extension NotificationSection {
timestampUpdatePublisher: AnyPublisher<Date, Never>,
managedObjectContext: NSManagedObjectContext,
delegate: NotificationTableViewCellDelegate,
dependency: NeedsDependency,
requestUserID: String
dependency: NeedsDependency
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
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,
StatusSection.configure(
cell: cell,
dependency: dependency,
readableLayoutFrame: frame,
timestampUpdatePublisher: timestampUpdatePublisher,
status: status,
requestUserID: requestUserID,
statusItemAttribute: Item.StatusAttribute(isStatusTextSensitive: false, isStatusSensitive: false))
statusItemAttribute: attribute
)
timestampUpdatePublisher
.sink { _ in
let timeText = notification.createAt.shortTimeAgoSinceNow

View File

@ -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

View File

@ -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<NotificationSection, NotificationItem>()
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)
}

View File

@ -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
}
}

View File

@ -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 {