diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 98bfe207..84aade87 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; }; 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; }; 2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */; }; + 2D694A7425F9EB4E0038ADDC /* MosaicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D694A7325F9EB4E0038ADDC /* MosaicView.swift */; }; 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; }; 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */; }; 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; }; @@ -301,6 +302,7 @@ 2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+Timeline.swift"; sourceTree = ""; }; 2D61335D25C1894B00CAE157 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; 2D650FAA25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error+Detail.swift"; sourceTree = ""; }; + 2D694A7325F9EB4E0038ADDC /* MosaicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MosaicView.swift; sourceTree = ""; }; 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = ""; }; 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Toot.swift"; sourceTree = ""; }; 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = ""; }; @@ -1177,6 +1179,7 @@ isa = PBXGroup; children = ( DB9D6C0D25E4F9780051B173 /* MosaicImageViewContainer.swift */, + 2D694A7325F9EB4E0038ADDC /* MosaicView.swift */, 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */, 5DF1057825F88A1D00D6C0D4 /* MosaicPlayerView.swift */, 5DF1057E25F88A4100D6C0D4 /* TouchBlockingView.swift */, @@ -1632,6 +1635,7 @@ 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, + 2D694A7425F9EB4E0038ADDC /* MosaicView.swift in Sources */, DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */, DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */, DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */, diff --git a/Mastodon/Diffiable/Section/StatusSection.swift b/Mastodon/Diffiable/Section/StatusSection.swift index daaa9852..f742e473 100644 --- a/Mastodon/Diffiable/Section/StatusSection.swift +++ b/Mastodon/Diffiable/Section/StatusSection.swift @@ -192,6 +192,14 @@ extension StatusSection { let scale: CGFloat = 1.3 return CGSize(width: maxWidth, height: maxWidth * scale) }() + cell.statusView.mosaicPlayerView.mosaicView.blurVisualEffectView.effect = isStatusSensitive ? MosaicImageViewContainer.blurVisualEffect : nil + cell.statusView.mosaicPlayerView.mosaicView.vibrancyVisualEffectView.alpha = isStatusSensitive ? 1.0 : 0.0 + cell.statusView.mosaicPlayerView.mosaicView.mosaicButton.publisher(for: .touchUpInside) + .sink { [weak cell] _ in + guard let cell = cell else { return } + cell.delegate?.statusTableViewCell(cell, mosaicView: cell.statusView.mosaicPlayerView.mosaicView, didTapContentWarningVisualEffectView: cell.statusView.mosaicPlayerView.mosaicView.blurVisualEffectView) + } + .store(in: &cell.disposeBag) if let videoAttachment = mediaAttachments.filter({ $0.type == .gifv || $0.type == .video }).first, let videoPlayerViewModel = dependency.context.videoPlaybackService.dequeueVideoPlayerViewModel(for: videoAttachment) diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift index cd4e5160..db26930c 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+StatusTableViewCellDelegate.swift @@ -45,7 +45,28 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) { } - + func statusTableViewCell(_ cell: StatusTableViewCell, mosaicView: MosaicView, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) { + guard let diffableDataSource = self.tableViewDiffableDataSource else { return } + guard let item = item(for: cell, indexPath: nil) 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) { + mosaicView.blurVisualEffectView.effect = nil + mosaicView.vibrancyVisualEffectView.alpha = 0.0 + } completion: { _ in + diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil) + } + } func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) { guard let diffableDataSource = self.tableViewDiffableDataSource else { return } guard let item = item(for: cell, indexPath: nil) else { return } diff --git a/Mastodon/Scene/Share/View/Container/MosaicPlayerView.swift b/Mastodon/Scene/Share/View/Container/MosaicPlayerView.swift index e7c478ce..52596162 100644 --- a/Mastodon/Scene/Share/View/Container/MosaicPlayerView.swift +++ b/Mastodon/Scene/Share/View/Container/MosaicPlayerView.swift @@ -15,6 +15,11 @@ final class MosaicPlayerView: UIView { private let touchBlockingView = TouchBlockingView() private var containerHeightLayoutConstraint: NSLayoutConstraint! + let mosaicView: MosaicView = { + let mosaicView = MosaicView() + return mosaicView + }() + let playerViewController = AVPlayerViewController() let gifIndicatorLabel: UILabel = { @@ -38,6 +43,14 @@ final class MosaicPlayerView: UIView { extension MosaicPlayerView { private func _init() { + addSubview(mosaicView) + NSLayoutConstraint.activate([ + mosaicView.topAnchor.constraint(equalTo: topAnchor), + mosaicView.leadingAnchor.constraint(equalTo: leadingAnchor), + mosaicView.trailingAnchor.constraint(equalTo: trailingAnchor), + mosaicView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + container.translatesAutoresizingMaskIntoConstraints = false addSubview(container) containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: 162).priority(.required - 1) diff --git a/Mastodon/Scene/Share/View/Container/MosaicView.swift b/Mastodon/Scene/Share/View/Container/MosaicView.swift new file mode 100644 index 00000000..10adf3ab --- /dev/null +++ b/Mastodon/Scene/Share/View/Container/MosaicView.swift @@ -0,0 +1,74 @@ +// +// MosaicView.swift +// Mastodon +// +// Created by sxiaojian on 2021/3/11. +// + +import Foundation +import UIKit + +class MosaicView: UIView { + static let cornerRadius: CGFloat = 4 + static let blurVisualEffect = UIBlurEffect(style: .systemUltraThinMaterial) + let blurVisualEffectView = UIVisualEffectView(effect: MosaicView.blurVisualEffect) + let vibrancyVisualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: MosaicView.blurVisualEffect)) + + let mosaicButton: UIButton = { + let button = UIButton(type: .custom) + button.backgroundColor = .clear + return button + }() + + 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 + }() + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } +} + +extension MosaicView { + private func _init() { + translatesAutoresizingMaskIntoConstraints = false + addSubview(mosaicButton) + NSLayoutConstraint.activate([ + mosaicButton.topAnchor.constraint(equalTo: topAnchor), + mosaicButton.trailingAnchor.constraint(equalTo: trailingAnchor), + mosaicButton.bottomAnchor.constraint(equalTo: bottomAnchor), + mosaicButton.leadingAnchor.constraint(equalTo: leadingAnchor), + ]) + // add blur visual effect view in the setup method + blurVisualEffectView.layer.masksToBounds = true + blurVisualEffectView.layer.cornerRadius = MosaicView.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), + ]) + } +} diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 2f4000b9..d1b5a5c2 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -23,6 +23,7 @@ protocol StatusTableViewCellDelegate: class { func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton) func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) + func statusTableViewCell(_ cell: StatusTableViewCell, mosaicView: MosaicView, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)