feat: implement image media content warning overlay
This commit is contained in:
parent
ccc8741ccd
commit
414aa086b4
|
@ -119,5 +119,5 @@ xcuserdata
|
|||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/swift,swiftpm,xcode,cocoapods
|
||||
|
||||
Mastodon/Localization/StringsConvertor/input
|
||||
Mastodon/Localization/StringsConvertor/output
|
||||
Localization/StringsConvertor/input
|
||||
Localization/StringsConvertor/output
|
|
@ -29,8 +29,9 @@
|
|||
},
|
||||
"status": {
|
||||
"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": {
|
||||
"load_more": "Load More"
|
||||
|
|
|
@ -26,24 +26,30 @@ enum Item {
|
|||
|
||||
protocol StatusContentWarningAttribute {
|
||||
var isStatusTextSensitive: Bool { get set }
|
||||
var isStatusSensitive: Bool { get set }
|
||||
}
|
||||
|
||||
extension Item {
|
||||
class StatusTimelineAttribute: Hashable, StatusContentWarningAttribute {
|
||||
var isStatusTextSensitive: Bool = false
|
||||
var isStatusTextSensitive: Bool
|
||||
var isStatusSensitive: Bool
|
||||
|
||||
public init(
|
||||
isStatusTextSensitive: Bool
|
||||
isStatusTextSensitive: Bool,
|
||||
isStatusSensitive: Bool
|
||||
) {
|
||||
self.isStatusTextSensitive = isStatusTextSensitive
|
||||
self.isStatusSensitive = isStatusSensitive
|
||||
}
|
||||
|
||||
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) {
|
||||
hasher.combine(isStatusTextSensitive)
|
||||
hasher.combine(isStatusSensitive)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -94,14 +94,18 @@ extension StatusSection {
|
|||
// set text
|
||||
cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content)
|
||||
|
||||
// set content warning
|
||||
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? (toot.reblog ?? toot).sensitive
|
||||
// set status text content warning
|
||||
let spoilerText = (toot.reblog ?? toot).spoilerText ?? ""
|
||||
let isStatusTextSensitive = statusContentWarningAttribute?.isStatusTextSensitive ?? !spoilerText.isEmpty
|
||||
cell.statusView.isStatusTextSensitive = isStatusTextSensitive
|
||||
cell.statusView.updateContentWarningDisplay(isHidden: !isStatusTextSensitive)
|
||||
cell.statusView.contentWarningTitle.text = (toot.reblog ?? toot).spoilerText.flatMap { spoilerText in
|
||||
guard !spoilerText.isEmpty else { return nil }
|
||||
return L10n.Common.Controls.Status.contentWarning + ": \(spoilerText)"
|
||||
} ?? L10n.Common.Controls.Status.contentWarning
|
||||
cell.statusView.contentWarningTitle.text = {
|
||||
if spoilerText.isEmpty {
|
||||
return L10n.Common.Controls.Status.statusContentWarning
|
||||
} else {
|
||||
return L10n.Common.Controls.Status.statusContentWarning + ": \(spoilerText)"
|
||||
}
|
||||
}()
|
||||
|
||||
// prepare media attachments
|
||||
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
|
||||
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
|
||||
let replyCountTitle: String = {
|
||||
|
|
|
@ -56,10 +56,12 @@ internal enum L10n {
|
|||
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
|
||||
}
|
||||
internal enum Status {
|
||||
/// content warning
|
||||
internal static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning")
|
||||
/// Tap to reveal that may be sensitive
|
||||
internal static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning")
|
||||
/// Show Post
|
||||
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
|
||||
internal static func userBoosted(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
"Common.Controls.Actions.SignIn" = "Sign in";
|
||||
"Common.Controls.Actions.SignUp" = "Sign up";
|
||||
"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.StatusContentWarning" = "content warning";
|
||||
"Common.Controls.Status.UserBoosted" = "%@ boosted";
|
||||
"Common.Controls.Timeline.LoadMore" = "Load More";
|
||||
"Common.Countable.Photo.Multiple" = "photos";
|
||||
|
|
|
@ -83,7 +83,12 @@ extension HomeTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
var newTimelineItems: [Item] = []
|
||||
|
||||
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
|
||||
newTimelineItems.append(.homeTimelineIndex(objectID: timelineIndex.objectID, attribute: attribute))
|
||||
|
|
|
@ -13,7 +13,7 @@ import GameplayKit
|
|||
import os.log
|
||||
import UIKit
|
||||
|
||||
final class PublicTimelineViewController: UIViewController, NeedsDependency, StatusTableViewCellDelegate {
|
||||
final class PublicTimelineViewController: UIViewController, NeedsDependency {
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
|
@ -203,3 +203,6 @@ extension PublicTimelineViewController: TimelineMiddleLoaderTableViewCellDelegat
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - StatusTableViewCellDelegate
|
||||
extension PublicTimelineViewController: StatusTableViewCellDelegate { }
|
||||
|
|
|
@ -58,7 +58,12 @@ extension PublicTimelineViewModel: NSFetchedResultsControllerDelegate {
|
|||
|
||||
var items = [Item]()
|
||||
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))
|
||||
if tootIDsWhichHasGap.contains(toot.id) {
|
||||
items.append(Item.publicMiddleLoader(tootID: toot.id))
|
||||
|
|
|
@ -13,18 +13,21 @@ protocol MosaicImageViewContainerPresentable: class {
|
|||
var mosaicImageViewContainer: MosaicImageViewContainer { get }
|
||||
}
|
||||
|
||||
protocol MosaicImageViewDelegate: class {
|
||||
protocol MosaicImageViewContainerDelegate: class {
|
||||
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
func mosaicImageViewContainer(_ mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
|
||||
|
||||
}
|
||||
|
||||
final class MosaicImageViewContainer: UIView {
|
||||
|
||||
static let cornerRadius: CGFloat = 4
|
||||
static let blurVisualEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||
|
||||
weak var delegate: MosaicImageViewDelegate?
|
||||
weak var delegate: MosaicImageViewContainerDelegate?
|
||||
|
||||
let container = UIStackView()
|
||||
var imageViews = [UIImageView]() {
|
||||
var imageViews: [UIImageView] = [] {
|
||||
didSet {
|
||||
imageViews.forEach { imageView in
|
||||
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!
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
@ -53,6 +65,8 @@ extension MosaicImageViewContainer {
|
|||
|
||||
private func _init() {
|
||||
container.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.axis = .horizontal
|
||||
container.distribution = .fillEqually
|
||||
addSubview(container)
|
||||
containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: 162).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
|
@ -63,8 +77,32 @@ extension MosaicImageViewContainer {
|
|||
containerHeightLayoutConstraint
|
||||
])
|
||||
|
||||
container.axis = .horizontal
|
||||
container.distribution = .fillEqually
|
||||
// add blur visual effect view in the setup method
|
||||
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
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
blurVisualEffectView.removeFromSuperview()
|
||||
blurVisualEffectView.effect = MosaicImageViewContainer.blurVisualEffect
|
||||
vibrancyVisualEffectView.alpha = 1.0
|
||||
imageViews = []
|
||||
|
||||
container.spacing = 1
|
||||
|
@ -100,6 +141,7 @@ extension MosaicImageViewContainer {
|
|||
imageViews.append(imageView)
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = MosaicImageViewContainer.cornerRadius
|
||||
imageView.layer.cornerCurve = .continuous
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -112,6 +154,15 @@ extension MosaicImageViewContainer {
|
|||
])
|
||||
containerHeightLayoutConstraint.constant = floor(rect.height)
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
guard let imageView = sender.view as? UIImageView 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)
|
||||
delegate?.mosaicImageViewContainer(self, didTapImageView: imageView, atIndex: index)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG && canImport(SwiftUI)
|
||||
|
|
|
@ -89,7 +89,7 @@ final class StatusView: UIView {
|
|||
let label = UILabel()
|
||||
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||
label.textColor = Asset.Colors.Label.primary.color
|
||||
label.text = L10n.Common.Controls.Status.contentWarning
|
||||
label.text = L10n.Common.Controls.Status.statusContentWarning
|
||||
return label
|
||||
}()
|
||||
let contentWarningActionButton: UIButton = {
|
||||
|
|
|
@ -14,6 +14,9 @@ import Combine
|
|||
protocol StatusTableViewCellDelegate: class {
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: 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 {
|
||||
|
@ -79,10 +82,11 @@ extension StatusTableViewCell {
|
|||
bottomPaddingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
bottomPaddingView.heightAnchor.constraint(equalToConstant: StatusTableViewCell.bottomPaddingHeight).priority(.defaultHigh),
|
||||
])
|
||||
bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
|
||||
statusView.delegate = self
|
||||
statusView.statusMosaicImageView.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
|
||||
extension StatusTableViewCell: ActionToolbarContainerDelegate {
|
||||
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) {
|
||||
|
|
Loading…
Reference in New Issue