forked from zelo72/mastodon-ios
feat: make content warning works in the notification scene
This commit is contained in:
parent
f8127428dd
commit
04d427ea93
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue