mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-05 03:39:34 +01:00
feat: implement image media content warning overlay
This commit is contained in:
parent
ccc8741ccd
commit
414aa086b4
4
.gitignore
vendored
4
.gitignore
vendored
@ -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
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 = {
|
||||||
|
@ -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))
|
||||||
|
@ -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.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";
|
||||||
|
@ -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))
|
||||||
|
@ -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 { }
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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 = {
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user