feat: implement image media content warning overlay

This commit is contained in:
CMK 2021-02-25 13:47:30 +08:00
parent ccc8741ccd
commit 414aa086b4
13 changed files with 178 additions and 28 deletions

4
.gitignore vendored
View File

@ -119,5 +119,5 @@ xcuserdata
# End of https://www.toptal.com/developers/gitignore/api/swift,swiftpm,xcode,cocoapods # End of https://www.toptal.com/developers/gitignore/api/swift,swiftpm,xcode,cocoapods
Mastodon/Localization/StringsConvertor/input Localization/StringsConvertor/input
Mastodon/Localization/StringsConvertor/output Localization/StringsConvertor/output

View File

@ -29,8 +29,9 @@
}, },
"status": { "status": {
"user_boosted": "%s boosted", "user_boosted": "%s boosted",
"content_warning": "content warning", "show_post": "Show Post",
"show_post": "Show Post" "status_content_warning": "content warning",
"media_content_warning": "Tap to reveal that may be sensitive"
}, },
"timeline": { "timeline": {
"load_more": "Load More" "load_more": "Load More"

View File

@ -26,24 +26,30 @@ enum Item {
protocol StatusContentWarningAttribute { protocol StatusContentWarningAttribute {
var isStatusTextSensitive: Bool { get set } var isStatusTextSensitive: Bool { get set }
var isStatusSensitive: Bool { get set }
} }
extension Item { extension Item {
class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute { class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute {
var isStatusTextSensitive: Bool = false var isStatusTextSensitive: Bool
var isStatusSensitive: Bool
public init( public init(
isStatusTextSensitive: Bool isStatusTextSensitive: Bool,
isStatusSensitive: Bool
) { ) {
self.isStatusTextSensitive = isStatusTextSensitive self.isStatusTextSensitive = isStatusTextSensitive
self.isStatusSensitive = isStatusSensitive
} }
static func == (lhs: Item.StatusTimelineAttribute, rhs: Item.StatusTimelineAttribute) -> Bool { static func == (lhs: Item.StatusTimelineAttribute, rhs: Item.StatusTimelineAttribute) -> Bool {
return lhs.isStatusTextSensitive == rhs.isStatusTextSensitive return lhs.isStatusTextSensitive == rhs.isStatusTextSensitive &&
lhs.isStatusSensitive == rhs.isStatusSensitive
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
hasher.combine(isStatusTextSensitive) hasher.combine(isStatusTextSensitive)
hasher.combine(isStatusSensitive)
} }
} }

View File

@ -94,14 +94,18 @@ extension StatusSection {
// set text // set text
cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content) cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content)
// set content warning // set status text content warning
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? (toot.reblog ?? toot).sensitive let spoilerText = (toot.reblog ?? toot).spoilerText ?? ""
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? !spoilerText.isEmpty
cell.statusView.isStatusTextSensitive = isStatusTextSensitive cell.statusView.isStatusTextSensitive = isStatusTextSensitive
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive) cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
cell.statusView.contentWarningTitle.text = (toot.reblog ?? toot).spoilerText.flatMap { spoilerText in cell.statusView.contentWarningTitle.text = {
guard !spoilerText.isEmpty else { return nil } if spoilerText.isEmpty {
return L10n.Common.Controls.Status.contentWarning + ": \(spoilerText)" return L10n.Common.Controls.Status.statusContentWarning
} ?? L10n.Common.Controls.Status.contentWarning } else {
return L10n.Common.Controls.Status.statusContentWarning + ": \(spoilerText)"
}
}()
// prepare media attachments // prepare media attachments
let mediaAttachments = Array((toot.reblog ?? toot).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending } let mediaAttachments = Array((toot.reblog ?? toot).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
@ -146,6 +150,9 @@ extension StatusSection {
} }
} }
cell.statusView.statusMosaicImageView.isHidden = mosiacImageViewModel.metas.isEmpty cell.statusView.statusMosaicImageView.isHidden = mosiacImageViewModel.metas.isEmpty
let isStatusSensitive = statusContentWarningAttribute?.isStatusSensitive ?? (toot.reblog ?? toot).sensitive
cell.statusView.statusMosaicImageView.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil
cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0
// toolbar // toolbar
let replyCountTitle: String = { let replyCountTitle: String = {

View File

@ -56,10 +56,12 @@ internal enum L10n {
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto") internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
} }
internal enum Status { internal enum Status {
/// content warning /// Tap to reveal that may be sensitive
internal static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning") internal static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning")
/// Show Post /// Show Post
internal static let showPost = L10n.tr("Localizable", "Common.Controls.Status.ShowPost") internal static let showPost = L10n.tr("Localizable", "Common.Controls.Status.ShowPost")
/// content warning
internal static let statusContentWarning = L10n.tr("Localizable", "Common.Controls.Status.StatusContentWarning")
/// %@ boosted /// %@ boosted
internal static func userBoosted(_ p1: Any) -> String { internal static func userBoosted(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1)) return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1))

View File

@ -43,3 +43,39 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
} }
} }
extension StatusTableViewCellDelegate where Self: StatusProvider {
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
}
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) {
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
item(for: cell, indexPath: nil)
.receive(on: DispatchQueue.main)
.sink { [weak self] item in
guard let _ = self else { return }
guard let item = item else { return }
switch item {
case .homeTimelineIndex(_, let attribute):
attribute.isStatusSensitive = false
case .toot(_, let attribute):
attribute.isStatusSensitive = false
default:
return
}
var snapshot = diffableDataSource.snapshot()
snapshot.reloadItems([item])
UIView.animate(withDuration: 0.33) {
cell.statusView.statusMosaicImageView.blurVisualEffectView.effect = nil
cell.statusView.statusMosaicImageView.vibrancyVisualEffectView.alpha = 0.0
} completion: { _ in
diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil)
}
}
.store(in: &cell.disposeBag)
}
}

View File

@ -15,8 +15,9 @@
"Common.Controls.Actions.SignIn" = "Sign in"; "Common.Controls.Actions.SignIn" = "Sign in";
"Common.Controls.Actions.SignUp" = "Sign up"; "Common.Controls.Actions.SignUp" = "Sign up";
"Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Actions.TakePhoto" = "Take photo";
"Common.Controls.Status.ContentWarning" = "content warning"; "Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive";
"Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.ShowPost" = "Show Post";
"Common.Controls.Status.StatusContentWarning" = "content warning";
"Common.Controls.Status.UserBoosted" = "%@ boosted"; "Common.Controls.Status.UserBoosted" = "%@ boosted";
"Common.Controls.Timeline.LoadMore" = "Load More"; "Common.Controls.Timeline.LoadMore" = "Load More";
"Common.Countable.Photo.Multiple" = "photos"; "Common.Countable.Photo.Multiple" = "photos";

View File

@ -83,7 +83,12 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
var newTimelineItems: [Item] = [] var newTimelineItems: [Item] = []
for (i, timelineIndex) in timelineIndexes.enumerated() { for (i, timelineIndex) in timelineIndexes.enumerated() {
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: timelineIndex.toot.sensitive) let toot = timelineIndex.toot.reblog ?? timelineIndex.toot
let isStatusTextSensitive: Bool = {
guard let spoilerText = toot.spoilerText, !spoilerText.isEmpty else { return false }
return true
}()
let attribute = oldSnapshotAttributeDict[timelineIndex.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: toot.sensitive)
// append new item into snapshot // append new item into snapshot
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute)) newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))

View File

@ -13,7 +13,7 @@ import GameplayKit
import os.log import os.log
import UIKit import UIKit
final class PublicTimelineViewController: UIViewController, NeedsDependency, StatusTableViewCellDelegate { final class PublicTimelineViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@ -203,3 +203,6 @@ extension PublicTimelineViewController: TimelineMiddleLoaderTableViewCellDelegat
} }
} }
} }
// MARK: - StatusTableViewCellDelegate
extension PublicTimelineViewController: StatusTableViewCellDelegate { }

View File

@ -58,7 +58,12 @@ extension PublicTimelineViewModel: NSFetchedResultsControllerDelegate {
var items = [Item]() var items = [Item]()
for (_, toot) in indexTootTuples { for (_, toot) in indexTootTuples {
let attribute = oldSnapshotAttributeDict[toot.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: toot.sensitive) let targetToot = toot.reblog ?? toot
let isStatusTextSensitive: Bool = {
guard let spoilerText = targetToot.spoilerText, !spoilerText.isEmpty else { return false }
return true
}()
let attribute = oldSnapshotAttributeDict[toot.objectID] ?? Item.StatusTimelineAttribute(isStatusTextSensitive: isStatusTextSensitive, isStatusSensitive: targetToot.sensitive)
items.append(Item.toot(objectID: toot.objectID, attribute: attribute)) items.append(Item.toot(objectID: toot.objectID, attribute: attribute))
if tootIDsWhichHasGap.contains(toot.id) { if tootIDsWhichHasGap.contains(toot.id) {
items.append(Item.publicMiddleLoader(tootID: toot.id)) items.append(Item.publicMiddleLoader(tootID: toot.id))

View File

@ -13,18 +13,21 @@ protocol MosaicImageViewContainerPresentable: class {
var mosaicImageViewContainer: MosaicImageViewContainer { get } var mosaicImageViewContainer: MosaicImageViewContainer { get }
} }
protocol MosaicImageViewDelegate: class { protocol MosaicImageViewContainerDelegate: class {
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
} }
final class MosaicImageViewContainer: UIView { final class MosaicImageViewContainer: UIView {
static let cornerRadius: CGFloat = 4 static let cornerRadius: CGFloat = 4
static let blurVisualEffect = UIBlurEffect(style: .systemUltraThinMaterial)
weak var delegate: MosaicImageViewDelegate? weak var delegate: MosaicImageViewContainerDelegate?
let container = UIStackView() let container = UIStackView()
var imageViews = [UIImageView]() { var imageViews: [UIImageView] = [] {
didSet { didSet {
imageViews.forEach { imageView in imageViews.forEach { imageView in
imageView.isUserInteractionEnabled = true imageView.isUserInteractionEnabled = true
@ -34,7 +37,16 @@ final class MosaicImageViewContainer: UIView {
} }
} }
} }
let blurVisualEffectView = UIVisualEffectView(effect: MosaicImageViewContainer.blurVisualEffect)
let vibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: MosaicImageViewContainer.blurVisualEffect))
let contentWarningLabel: UILabel = {
let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15))
label.text = L10n.Common.Controls.Status.mediaContentWarning
label.textAlignment = .center
return label
}()
private var containerHeightLayoutConstraint: NSLayoutConstraint! private var containerHeightLayoutConstraint: NSLayoutConstraint!
override init(frame: CGRect) { override init(frame: CGRect) {
@ -53,6 +65,8 @@ extension MosaicImageViewContainer {
private func _init() { private func _init() {
container.translatesAutoresizingMaskIntoConstraints = false container.translatesAutoresizingMaskIntoConstraints = false
container.axis = .horizontal
container.distribution = .fillEqually
addSubview(container) addSubview(container)
containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: 162).priority(.required - 1) containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: 162).priority(.required - 1)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@ -63,8 +77,32 @@ extension MosaicImageViewContainer {
containerHeightLayoutConstraint containerHeightLayoutConstraint
]) ])
container.axis = .horizontal // add blur visual effect view in the setup method
container.distribution = .fillEqually blurVisualEffectView.layer.masksToBounds = true
blurVisualEffectView.layer.cornerRadius = MosaicImageViewContainer.cornerRadius
blurVisualEffectView.layer.cornerCurve = .continuous
vibrancyVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
blurVisualEffectView.contentView.addSubview(vibrancyVisualEffectView)
NSLayoutConstraint.activate([
vibrancyVisualEffectView.topAnchor.constraint(equalTo: blurVisualEffectView.topAnchor),
vibrancyVisualEffectView.leadingAnchor.constraint(equalTo: blurVisualEffectView.leadingAnchor),
vibrancyVisualEffectView.trailingAnchor.constraint(equalTo: blurVisualEffectView.trailingAnchor),
vibrancyVisualEffectView.bottomAnchor.constraint(equalTo: blurVisualEffectView.bottomAnchor),
])
contentWarningLabel.translatesAutoresizingMaskIntoConstraints = false
vibrancyVisualEffectView.contentView.addSubview(contentWarningLabel)
NSLayoutConstraint.activate([
contentWarningLabel.leadingAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.layoutMarginsGuide.leadingAnchor),
contentWarningLabel.trailingAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.layoutMarginsGuide.trailingAnchor),
contentWarningLabel.centerYAnchor.constraint(equalTo: vibrancyVisualEffectView.contentView.centerYAnchor),
])
blurVisualEffectView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer.singleTapGestureRecognizer
tapGesture.addTarget(self, action: #selector(MosaicImageViewContainer.visualEffectViewTapGestureRecognizerHandler(_:)))
blurVisualEffectView.addGestureRecognizer(tapGesture)
} }
} }
@ -79,6 +117,9 @@ extension MosaicImageViewContainer {
container.subviews.forEach { subview in container.subviews.forEach { subview in
subview.removeFromSuperview() subview.removeFromSuperview()
} }
blurVisualEffectView.removeFromSuperview()
blurVisualEffectView.effect = MosaicImageViewContainer.blurVisualEffect
vibrancyVisualEffectView.alpha = 1.0
imageViews = [] imageViews = []
container.spacing = 1 container.spacing = 1
@ -100,6 +141,7 @@ extension MosaicImageViewContainer {
imageViews.append(imageView) imageViews.append(imageView)
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = MosaicImageViewContainer.cornerRadius imageView.layer.cornerRadius = MosaicImageViewContainer.cornerRadius
imageView.layer.cornerCurve = .continuous
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false imageView.translatesAutoresizingMaskIntoConstraints = false
@ -112,6 +154,15 @@ extension MosaicImageViewContainer {
]) ])
containerHeightLayoutConstraint.constant = floor(rect.height) containerHeightLayoutConstraint.constant = floor(rect.height)
containerHeightLayoutConstraint.isActive = true containerHeightLayoutConstraint.isActive = true
blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
addSubview(blurVisualEffectView)
NSLayoutConstraint.activate([
blurVisualEffectView.topAnchor.constraint(equalTo: imageView.topAnchor),
blurVisualEffectView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
blurVisualEffectView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
blurVisualEffectView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
])
return imageView return imageView
} }
@ -191,18 +242,34 @@ extension MosaicImageViewContainer {
} }
} }
blurVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
addSubview(blurVisualEffectView)
NSLayoutConstraint.activate([
blurVisualEffectView.topAnchor.constraint(equalTo: container.topAnchor),
blurVisualEffectView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
blurVisualEffectView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
blurVisualEffectView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
])
return imageViews return imageViews
} }
} }
extension MosaicImageViewContainer { extension MosaicImageViewContainer {
@objc private func visualEffectViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
delegate?.mosaicImageViewContainer(self, didTapContentWarningVisualEffectView: blurVisualEffectView)
}
@objc private func photoTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) { @objc private func photoTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
guard let imageView = sender.view as? UIImageView else { return } guard let imageView = sender.view as? UIImageView else { return }
guard let index = imageViews.firstIndex(of: imageView) else { return } guard let index = imageViews.firstIndex(of: imageView) else { return }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: tap photo at index: %ld", ((#file as NSString).lastPathComponent), #line, #function, index) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: tap photo at index: %ld", ((#file as NSString).lastPathComponent), #line, #function, index)
delegate?.mosaicImageViewContainer(self, didTapImageView: imageView, atIndex: index) delegate?.mosaicImageViewContainer(self, didTapImageView: imageView, atIndex: index)
} }
} }
#if DEBUG && canImport(SwiftUI) #if DEBUG && canImport(SwiftUI)

View File

@ -89,7 +89,7 @@ final class StatusView: UIView {
let label = UILabel() let label = UILabel()
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)) label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
label.textColor = Asset.Colors.Label.primary.color label.textColor = Asset.Colors.Label.primary.color
label.text = L10n.Common.Controls.Status.contentWarning label.text = L10n.Common.Controls.Status.statusContentWarning
return label return label
}() }()
let contentWarningActionButton: UIButton = { let contentWarningActionButton: UIButton = {

View File

@ -14,6 +14,9 @@ import Combine
protocol StatusTableViewCellDelegate: class { protocol StatusTableViewCellDelegate: class {
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton) func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
} }
final class StatusTableViewCell: UITableViewCell { final class StatusTableViewCell: UITableViewCell {
@ -79,10 +82,11 @@ extension StatusTableViewCell {
bottomPaddingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), bottomPaddingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
bottomPaddingView.heightAnchor.constraint(equalToConstant: StatusTableViewCell.bottomPaddingHeight).priority(.defaultHigh), bottomPaddingView.heightAnchor.constraint(equalToConstant: StatusTableViewCell.bottomPaddingHeight).priority(.defaultHigh),
]) ])
bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
statusView.delegate = self statusView.delegate = self
statusView.statusMosaicImageView.delegate = self
statusView.actionToolbarContainer.delegate = self statusView.actionToolbarContainer.delegate = self
bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
} }
} }
@ -94,6 +98,19 @@ extension StatusTableViewCell: StatusViewDelegate {
} }
} }
// MARK: - MosaicImageViewDelegate
extension StatusTableViewCell: MosaicImageViewContainerDelegate {
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
delegate?.statusTableViewCell(self, mosaicImageViewContainer: mosaicImageViewContainer, didTapImageView: imageView, atIndex: index)
}
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) {
delegate?.statusTableViewCell(self, mosaicImageViewContainer: mosaicImageViewContainer, didTapContentWarningVisualEffectView: visualEffectView)
}
}
// MARK: - ActionToolbarContainerDelegate // MARK: - ActionToolbarContainerDelegate
extension StatusTableViewCell: ActionToolbarContainerDelegate { extension StatusTableViewCell: ActionToolbarContainerDelegate {
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) { func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) {