forked from zelo72/mastodon-ios
feat: update status content warning UI
This commit is contained in:
parent
9051e5d1ec
commit
bdf7114fef
|
@ -140,7 +140,8 @@
|
||||||
"unreblog": "Undo reblog",
|
"unreblog": "Undo reblog",
|
||||||
"favorite": "Favorite",
|
"favorite": "Favorite",
|
||||||
"unfavorite": "Unfavorite",
|
"unfavorite": "Unfavorite",
|
||||||
"menu": "Menu"
|
"menu": "Menu",
|
||||||
|
"hide": "Hide"
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import CoreDataStack
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
import CoreDataStack
|
|
||||||
|
|
||||||
// MARK: - header
|
// MARK: - header
|
||||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
|
@ -367,7 +367,29 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||||
func tableViewCell(
|
func tableViewCell(
|
||||||
_ cell: UITableViewCell,
|
_ cell: UITableViewCell,
|
||||||
statusView: StatusView,
|
statusView: StatusView,
|
||||||
contentWarningToggleButtonDidPressed button: UIButton
|
spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView
|
||||||
|
) {
|
||||||
|
Task {
|
||||||
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
|
guard let item = await item(from: source) else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard case let .status(status) = item else {
|
||||||
|
assertionFailure("only works for status data provider")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try await DataSourceFacade.responseToToggleSensitiveAction(
|
||||||
|
dependency: self,
|
||||||
|
status: status
|
||||||
|
)
|
||||||
|
} // end Task
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableViewCell(
|
||||||
|
_ cell: UITableViewCell,
|
||||||
|
statusView: StatusView,
|
||||||
|
spoilerBannerViewDidPressed bannerView: SpoilerBannerView
|
||||||
) {
|
) {
|
||||||
Task {
|
Task {
|
||||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||||
|
|
|
@ -37,9 +37,11 @@ extension SettingsAppearanceTableViewCell {
|
||||||
extension SettingsAppearanceTableViewCell.ViewModel {
|
extension SettingsAppearanceTableViewCell.ViewModel {
|
||||||
func bind(cell: SettingsAppearanceTableViewCell) {
|
func bind(cell: SettingsAppearanceTableViewCell) {
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
$customUserInterfaceStyle,
|
$customUserInterfaceStyle.removeDuplicates(),
|
||||||
$preferredTrueBlackDarkMode
|
$preferredTrueBlackDarkMode.removeDuplicates()
|
||||||
)
|
)
|
||||||
|
.debounce(for: 0.1, scheduler: DispatchQueue.main)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { customUserInterfaceStyle, preferredTrueBlackDarkMode in
|
.sink { customUserInterfaceStyle, preferredTrueBlackDarkMode in
|
||||||
cell.appearanceViews.forEach { view in
|
cell.appearanceViews.forEach { view in
|
||||||
view.selected = false
|
view.selected = false
|
||||||
|
|
|
@ -31,7 +31,8 @@ protocol StatusTableViewCellDelegate: AnyObject, AutoGenerateProtocolDelegate {
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
|
||||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, contentWarningToggleButtonDidPressed button: UIButton)
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
|
||||||
|
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, spoilerBannerViewDidPressed bannerView: SpoilerBannerView)
|
||||||
// sourcery:end
|
// sourcery:end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +73,12 @@ extension StatusViewDelegate where Self: StatusViewContainerTableViewCell {
|
||||||
delegate?.tableViewCell(self, statusView: statusView, menuButton: button, didSelectAction: action)
|
delegate?.tableViewCell(self, statusView: statusView, menuButton: button, didSelectAction: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusView(_ statusView: StatusView, contentWarningToggleButtonDidPressed button: UIButton) {
|
func statusView(_ statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) {
|
||||||
delegate?.tableViewCell(self, statusView: statusView, contentWarningToggleButtonDidPressed: button)
|
delegate?.tableViewCell(self, statusView: statusView, spoilerOverlayViewDidPressed: overlayView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusView(_ statusView: StatusView, spoilerBannerViewDidPressed bannerView: SpoilerBannerView) {
|
||||||
|
delegate?.tableViewCell(self, statusView: statusView, spoilerBannerViewDidPressed: bannerView)
|
||||||
}
|
}
|
||||||
// sourcery:end
|
// sourcery:end
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ extension MetaLabel {
|
||||||
case statusHeader
|
case statusHeader
|
||||||
case statusName
|
case statusName
|
||||||
case statusUsername
|
case statusUsername
|
||||||
case statusSpoiler
|
case statusSpoilerOverlay
|
||||||
|
case statusSpoilerBanner
|
||||||
case notificationTitle
|
case notificationTitle
|
||||||
case profileFieldName
|
case profileFieldName
|
||||||
case profileFieldValue
|
case profileFieldValue
|
||||||
|
@ -57,12 +58,16 @@ extension MetaLabel {
|
||||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||||
textColor = Asset.Colors.Label.secondary.color
|
textColor = Asset.Colors.Label.secondary.color
|
||||||
|
|
||||||
case .statusSpoiler:
|
case .statusSpoilerOverlay:
|
||||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
|
||||||
textColor = Asset.Colors.Label.secondary.color
|
textColor = Asset.Colors.Label.primary.color
|
||||||
textAlignment = .center
|
textAlignment = .center
|
||||||
paragraphStyle.alignment = .center
|
paragraphStyle.alignment = .center
|
||||||
|
|
||||||
|
case .statusSpoilerBanner:
|
||||||
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||||
|
textColor = Asset.Colors.Label.primary.color
|
||||||
|
|
||||||
case .notificationTitle:
|
case .notificationTitle:
|
||||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .regular))
|
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 14, weight: .regular))
|
||||||
textColor = Asset.Colors.Label.secondary.color
|
textColor = Asset.Colors.Label.secondary.color
|
||||||
|
|
|
@ -380,7 +380,11 @@ extension NotificationView: StatusViewDelegate {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func statusView(_ statusView: StatusView, contentWarningToggleButtonDidPressed button: UIButton) {
|
public func statusView(_ statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView) {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func statusView(_ statusView: StatusView, spoilerBannerViewDidPressed bannerView: SpoilerBannerView) {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -256,8 +256,12 @@ extension StatusView.ViewModel {
|
||||||
.sink { spoilerContent, content, isContentReveal in
|
.sink { spoilerContent, content, isContentReveal in
|
||||||
if let spoilerContent = spoilerContent {
|
if let spoilerContent = spoilerContent {
|
||||||
statusView.spoilerOverlayView.spoilerMetaLabel.configure(content: spoilerContent)
|
statusView.spoilerOverlayView.spoilerMetaLabel.configure(content: spoilerContent)
|
||||||
|
statusView.spoilerBannerView.label.configure(content: spoilerContent)
|
||||||
|
statusView.setSpoilerBannerViewHidden(isHidden: !isContentReveal)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
statusView.spoilerOverlayView.spoilerMetaLabel.reset()
|
statusView.spoilerOverlayView.spoilerMetaLabel.reset()
|
||||||
|
statusView.spoilerBannerView.label.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let content = content {
|
if let content = content {
|
||||||
|
@ -273,7 +277,7 @@ extension StatusView.ViewModel {
|
||||||
statusView.contentMetaText.textView.accessibilityLabel = ""
|
statusView.contentMetaText.textView.accessibilityLabel = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
statusView.setSpoilerOverlayViewHidden(isContentReveal)
|
statusView.setSpoilerOverlayViewHidden(isHidden: isContentReveal)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
// visibility
|
// visibility
|
||||||
|
@ -299,16 +303,13 @@ extension StatusView.ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
$isSensitive
|
// $isSensitive
|
||||||
.sink { isSensitive in
|
// .sink { isSensitive in
|
||||||
if isSensitive {
|
// if isSensitive {
|
||||||
let image = Asset.Human.eyeCircleFill.image
|
// statusView.setStatusSpoilerBannerViewDisplay()
|
||||||
statusView.contentWarningToggleButton.setImage(image, for: .normal)
|
// }
|
||||||
statusView.contentWarningToggleButton.tintColor = .systemGray
|
// }
|
||||||
statusView.setContentWarningToggleButtonDisplay()
|
// .store(in: &disposeBag)
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
// $spoilerContent
|
// $spoilerContent
|
||||||
// .sink { metaContent in
|
// .sink { metaContent in
|
||||||
// guard let metaContent = metaContent else {
|
// guard let metaContent = metaContent else {
|
||||||
|
|
|
@ -22,7 +22,8 @@ public protocol StatusViewDelegate: AnyObject {
|
||||||
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
func statusView(_ statusView: StatusView, pollVoteButtonPressed button: UIButton)
|
||||||
func statusView(_ statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
|
func statusView(_ statusView: StatusView, actionToolbarContainer: ActionToolbarContainer, buttonDidPressed button: UIButton, action: ActionToolbarContainer.Action)
|
||||||
func statusView(_ statusView: StatusView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
|
func statusView(_ statusView: StatusView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
|
||||||
func statusView(_ statusView: StatusView, contentWarningToggleButtonDidPressed button: UIButton)
|
func statusView(_ statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
|
||||||
|
func statusView(_ statusView: StatusView, spoilerBannerViewDidPressed bannerView: SpoilerBannerView)
|
||||||
// func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
// func statusView(_ statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
// func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
// func statusView(_ statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
|
||||||
}
|
}
|
||||||
|
@ -100,9 +101,6 @@ public final class StatusView: UIView {
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// contentWarningToggleButton
|
|
||||||
public let contentWarningToggleButton = UIButton(type: .system)
|
|
||||||
|
|
||||||
// content
|
// content
|
||||||
let contentContainer = UIStackView()
|
let contentContainer = UIStackView()
|
||||||
public let contentMetaText: MetaText = {
|
public let contentMetaText: MetaText = {
|
||||||
|
@ -134,6 +132,7 @@ public final class StatusView: UIView {
|
||||||
return metaText
|
return metaText
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// content warning
|
||||||
let spoilerOverlayView = SpoilerOverlayView()
|
let spoilerOverlayView = SpoilerOverlayView()
|
||||||
|
|
||||||
// media
|
// media
|
||||||
|
@ -197,6 +196,9 @@ public final class StatusView: UIView {
|
||||||
// visibility
|
// visibility
|
||||||
public let statusVisibilityView = StatusVisibilityView()
|
public let statusVisibilityView = StatusVisibilityView()
|
||||||
|
|
||||||
|
// spoiler banner
|
||||||
|
public let spoilerBannerView = SpoilerBannerView()
|
||||||
|
|
||||||
// toolbar
|
// toolbar
|
||||||
public let actionToolbarContainer = ActionToolbarContainer()
|
public let actionToolbarContainer = ActionToolbarContainer()
|
||||||
|
|
||||||
|
@ -222,11 +224,11 @@ public final class StatusView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
headerContainerView.isHidden = true
|
headerContainerView.isHidden = true
|
||||||
contentWarningToggleButton.isHidden = true
|
setSpoilerOverlayViewHidden(isHidden: true)
|
||||||
setSpoilerOverlayViewHidden(true)
|
|
||||||
mediaContainerView.isHidden = true
|
mediaContainerView.isHidden = true
|
||||||
pollContainerView.isHidden = true
|
pollContainerView.isHidden = true
|
||||||
statusVisibilityView.isHidden = true
|
statusVisibilityView.isHidden = true
|
||||||
|
setSpoilerBannerViewHidden(isHidden: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
|
@ -265,12 +267,15 @@ extension StatusView {
|
||||||
authorNameLabel.isUserInteractionEnabled = false
|
authorNameLabel.isUserInteractionEnabled = false
|
||||||
authorUsernameLabel.isUserInteractionEnabled = false
|
authorUsernameLabel.isUserInteractionEnabled = false
|
||||||
|
|
||||||
// contentWarningToggleButton
|
|
||||||
contentWarningToggleButton.addTarget(self, action: #selector(StatusView.contentWarningToggleButtonDidPressed(_:)), for: .touchUpInside)
|
|
||||||
|
|
||||||
// dateLabel
|
// dateLabel
|
||||||
dateLabel.isUserInteractionEnabled = false
|
dateLabel.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
// content warning
|
||||||
|
let spoilerOverlayViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
spoilerOverlayView.addGestureRecognizer(spoilerOverlayViewTapGestureRecognizer)
|
||||||
|
spoilerOverlayViewTapGestureRecognizer.addTarget(self, action: #selector(StatusView.spoilerOverlayViewTapGestureRecognizerHandler(_:)))
|
||||||
|
|
||||||
// content
|
// content
|
||||||
contentMetaText.textView.delegate = self
|
contentMetaText.textView.delegate = self
|
||||||
contentMetaText.textView.linkDelegate = self
|
contentMetaText.textView.linkDelegate = self
|
||||||
|
@ -287,6 +292,11 @@ extension StatusView {
|
||||||
pollTableView.delegate = self
|
pollTableView.delegate = self
|
||||||
pollVoteButton.addTarget(self, action: #selector(StatusView.pollVoteButtonDidPressed(_:)), for: .touchUpInside)
|
pollVoteButton.addTarget(self, action: #selector(StatusView.pollVoteButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
|
// statusSpoilerBannerView
|
||||||
|
let spoilerBannerViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
spoilerBannerView.addGestureRecognizer(spoilerBannerViewTapGestureRecognizer)
|
||||||
|
spoilerBannerViewTapGestureRecognizer.addTarget(self, action: #selector(StatusView.spoilerBannerViewTapGestureRecognizerHandler(_:)))
|
||||||
|
|
||||||
// toolbar
|
// toolbar
|
||||||
actionToolbarContainer.delegate = self
|
actionToolbarContainer.delegate = self
|
||||||
}
|
}
|
||||||
|
@ -305,16 +315,22 @@ extension StatusView {
|
||||||
delegate?.statusView(self, authorAvatarButtonDidPressed: avatarButton)
|
delegate?.statusView(self, authorAvatarButtonDidPressed: avatarButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func contentWarningToggleButtonDidPressed(_ sender: UIButton) {
|
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
delegate?.statusView(self, contentWarningToggleButtonDidPressed: contentWarningToggleButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func pollVoteButtonDidPressed(_ sender: UIButton) {
|
@objc private func pollVoteButtonDidPressed(_ sender: UIButton) {
|
||||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
delegate?.statusView(self, pollVoteButtonPressed: pollVoteButton)
|
delegate?.statusView(self, pollVoteButtonPressed: pollVoteButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func spoilerOverlayViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
delegate?.statusView(self, spoilerOverlayViewDidPressed: spoilerOverlayView)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func spoilerBannerViewTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
delegate?.statusView(self, spoilerBannerViewDidPressed: spoilerBannerView)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
|
@ -354,7 +370,7 @@ extension StatusView.Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func inline(statusView: StatusView) {
|
private func base(statusView: StatusView) {
|
||||||
// container: V - [ header container | author container | content container | media container | pollTableView | actionToolbarContainer ]
|
// container: V - [ header container | author container | content container | media container | pollTableView | actionToolbarContainer ]
|
||||||
statusView.containerStackView.layoutMargins = StatusView.containerLayoutMargin
|
statusView.containerStackView.layoutMargins = StatusView.containerLayoutMargin
|
||||||
|
|
||||||
|
@ -440,11 +456,6 @@ extension StatusView.Style {
|
||||||
statusView.dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
statusView.dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||||
authorSecondaryMetaContainer.addArrangedSubview(UIView())
|
authorSecondaryMetaContainer.addArrangedSubview(UIView())
|
||||||
|
|
||||||
// contentWarningToggleButton
|
|
||||||
statusView.authorContainerView.addArrangedSubview(statusView.contentWarningToggleButton)
|
|
||||||
statusView.contentWarningToggleButton.setContentHuggingPriority(.required - 2, for: .horizontal)
|
|
||||||
statusView.contentWarningToggleButton.setContentCompressionResistancePriority(.required - 2, for: .horizontal)
|
|
||||||
|
|
||||||
// content container: V - [ contentMetaText ]
|
// content container: V - [ contentMetaText ]
|
||||||
statusView.contentContainer.axis = .vertical
|
statusView.contentContainer.axis = .vertical
|
||||||
statusView.contentContainer.spacing = 12
|
statusView.contentContainer.spacing = 12
|
||||||
|
@ -508,15 +519,24 @@ extension StatusView.Style {
|
||||||
statusView.statusVisibilityView.preservesSuperviewLayoutMargins = true
|
statusView.statusVisibilityView.preservesSuperviewLayoutMargins = true
|
||||||
statusView.containerStackView.addArrangedSubview(statusView.statusVisibilityView)
|
statusView.containerStackView.addArrangedSubview(statusView.statusVisibilityView)
|
||||||
|
|
||||||
|
statusView.spoilerBannerView.preservesSuperviewLayoutMargins = true
|
||||||
|
statusView.containerStackView.addArrangedSubview(statusView.spoilerBannerView)
|
||||||
|
|
||||||
// action toolbar
|
// action toolbar
|
||||||
statusView.actionToolbarContainer.configure(for: .inline)
|
statusView.actionToolbarContainer.configure(for: .inline)
|
||||||
statusView.actionToolbarContainer.preservesSuperviewLayoutMargins = true
|
statusView.actionToolbarContainer.preservesSuperviewLayoutMargins = true
|
||||||
statusView.containerStackView.addArrangedSubview(statusView.actionToolbarContainer)
|
statusView.containerStackView.addArrangedSubview(statusView.actionToolbarContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inline(statusView: StatusView) {
|
||||||
|
base(statusView: statusView)
|
||||||
|
|
||||||
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
func plain(statusView: StatusView) {
|
func plain(statusView: StatusView) {
|
||||||
// container: V - [ … | statusMetricView ]
|
// container: V - [ … | statusMetricView ]
|
||||||
inline(statusView: statusView) // override the inline style
|
base(statusView: statusView) // override the base style
|
||||||
|
|
||||||
// statusMetricView
|
// statusMetricView
|
||||||
statusView.statusMetricView.layoutMargins = StatusView.containerLayoutMargin
|
statusView.statusMetricView.layoutMargins = StatusView.containerLayoutMargin
|
||||||
|
@ -530,7 +550,7 @@ extension StatusView.Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func report(statusView: StatusView) {
|
func report(statusView: StatusView) {
|
||||||
inline(statusView: statusView) // override the inline style
|
base(statusView: statusView) // override the base style
|
||||||
|
|
||||||
statusView.menuButton.removeFromSuperview()
|
statusView.menuButton.removeFromSuperview()
|
||||||
statusView.statusVisibilityView.removeFromSuperview()
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
@ -538,31 +558,36 @@ extension StatusView.Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
func notification(statusView: StatusView) {
|
func notification(statusView: StatusView) {
|
||||||
inline(statusView: statusView) // override the inline style
|
base(statusView: statusView) // override the base style
|
||||||
|
|
||||||
statusView.headerContainerView.removeFromSuperview()
|
statusView.headerContainerView.removeFromSuperview()
|
||||||
statusView.authorContainerView.removeFromSuperview()
|
statusView.authorContainerView.removeFromSuperview()
|
||||||
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
statusView.spoilerBannerView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationQuote(statusView: StatusView) {
|
func notificationQuote(statusView: StatusView) {
|
||||||
inline(statusView: statusView) // override the inline style
|
base(statusView: statusView) // override the base style
|
||||||
|
|
||||||
statusView.contentContainer.layoutMargins.bottom = 16 // fix contentText align to edge issue
|
statusView.contentContainer.layoutMargins.bottom = 16 // fix contentText align to edge issue
|
||||||
statusView.menuButton.removeFromSuperview()
|
statusView.menuButton.removeFromSuperview()
|
||||||
statusView.statusVisibilityView.removeFromSuperview()
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
statusView.spoilerBannerView.removeFromSuperview()
|
||||||
statusView.actionToolbarContainer.removeFromSuperview()
|
statusView.actionToolbarContainer.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
func composeStatusReplica(statusView: StatusView) {
|
func composeStatusReplica(statusView: StatusView) {
|
||||||
inline(statusView: statusView)
|
base(statusView: statusView)
|
||||||
|
|
||||||
statusView.avatarButton.isUserInteractionEnabled = false
|
statusView.avatarButton.isUserInteractionEnabled = false
|
||||||
statusView.menuButton.removeFromSuperview()
|
statusView.menuButton.removeFromSuperview()
|
||||||
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
statusView.spoilerBannerView.removeFromSuperview()
|
||||||
statusView.actionToolbarContainer.removeFromSuperview()
|
statusView.actionToolbarContainer.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
func composeStatusAuthor(statusView: StatusView) {
|
func composeStatusAuthor(statusView: StatusView) {
|
||||||
inline(statusView: statusView)
|
base(statusView: statusView)
|
||||||
|
|
||||||
statusView.avatarButton.isUserInteractionEnabled = false
|
statusView.avatarButton.isUserInteractionEnabled = false
|
||||||
statusView.menuButton.removeFromSuperview()
|
statusView.menuButton.removeFromSuperview()
|
||||||
|
@ -573,6 +598,7 @@ extension StatusView.Style {
|
||||||
statusView.mediaContainerView.removeFromSuperview()
|
statusView.mediaContainerView.removeFromSuperview()
|
||||||
statusView.pollContainerView.removeFromSuperview()
|
statusView.pollContainerView.removeFromSuperview()
|
||||||
statusView.statusVisibilityView.removeFromSuperview()
|
statusView.statusVisibilityView.removeFromSuperview()
|
||||||
|
statusView.spoilerBannerView.removeFromSuperview()
|
||||||
statusView.actionToolbarContainer.removeFromSuperview()
|
statusView.actionToolbarContainer.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,11 +609,7 @@ extension StatusView {
|
||||||
headerContainerView.isHidden = false
|
headerContainerView.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func setContentWarningToggleButtonDisplay() {
|
func setSpoilerOverlayViewHidden(isHidden: Bool) {
|
||||||
contentWarningToggleButton.isHidden = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSpoilerOverlayViewHidden(_ isHidden: Bool) {
|
|
||||||
spoilerOverlayView.isHidden = isHidden
|
spoilerOverlayView.isHidden = isHidden
|
||||||
spoilerOverlayView.setComponentHidden(isHidden)
|
spoilerOverlayView.setComponentHidden(isHidden)
|
||||||
}
|
}
|
||||||
|
@ -604,6 +626,10 @@ extension StatusView {
|
||||||
statusVisibilityView.isHidden = false
|
statusVisibilityView.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSpoilerBannerViewHidden(isHidden: Bool) {
|
||||||
|
spoilerBannerView.isHidden = isHidden
|
||||||
|
}
|
||||||
|
|
||||||
// content text Width
|
// content text Width
|
||||||
public var contentMaxLayoutWidth: CGFloat {
|
public var contentMaxLayoutWidth: CGFloat {
|
||||||
let inset = contentLayoutInset
|
let inset = contentLayoutInset
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
//
|
||||||
|
// SpoilerBannerView.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by MainasuK on 2022-2-8.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MastodonAsset
|
||||||
|
import MetaTextKit
|
||||||
|
|
||||||
|
public final class SpoilerBannerView: UIView {
|
||||||
|
|
||||||
|
static let cornerRadius: CGFloat = 8
|
||||||
|
static let containerMargin: CGFloat = 14
|
||||||
|
|
||||||
|
public let containerView = UIView()
|
||||||
|
|
||||||
|
public let label = MetaLabel(style: .statusSpoilerBanner)
|
||||||
|
|
||||||
|
public let hideLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = Asset.Colors.Label.primary.color
|
||||||
|
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.text = "Hide" // TODO: i18n
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SpoilerBannerView {
|
||||||
|
|
||||||
|
private func _init() {
|
||||||
|
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(containerView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
containerView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
containerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
|
||||||
|
containerView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
|
||||||
|
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
containerView.backgroundColor = .secondarySystemBackground
|
||||||
|
|
||||||
|
containerView.layoutMargins = UIEdgeInsets(
|
||||||
|
top: StatusVisibilityView.containerMargin,
|
||||||
|
left: StatusVisibilityView.containerMargin,
|
||||||
|
bottom: StatusVisibilityView.containerMargin,
|
||||||
|
right: StatusVisibilityView.containerMargin
|
||||||
|
)
|
||||||
|
|
||||||
|
let labelContainer = UIStackView()
|
||||||
|
labelContainer.axis = .horizontal
|
||||||
|
labelContainer.spacing = 16
|
||||||
|
labelContainer.alignment = .center
|
||||||
|
|
||||||
|
labelContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
containerView.addSubview(labelContainer)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
labelContainer.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor),
|
||||||
|
labelContainer.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor),
|
||||||
|
labelContainer.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor),
|
||||||
|
labelContainer.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
labelContainer.addArrangedSubview(label)
|
||||||
|
labelContainer.addArrangedSubview(hideLabel)
|
||||||
|
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||||
|
hideLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
|
||||||
|
hideLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||||
|
|
||||||
|
label.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
containerView.layer.masksToBounds = false
|
||||||
|
containerView.layer.cornerCurve = .continuous
|
||||||
|
containerView.layer.cornerRadius = StatusVisibilityView.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,33 +10,27 @@ import MastodonLocalization
|
||||||
import MastodonAsset
|
import MastodonAsset
|
||||||
import MetaTextKit
|
import MetaTextKit
|
||||||
|
|
||||||
final class SpoilerOverlayView: UIView {
|
public final class SpoilerOverlayView: UIView {
|
||||||
|
|
||||||
let containerStackView: UIStackView = {
|
let containerStackView: UIStackView = {
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.axis = .vertical
|
stackView.axis = .vertical
|
||||||
// stackView.spacing = 8
|
stackView.spacing = 8
|
||||||
stackView.alignment = .center
|
stackView.alignment = .center
|
||||||
return stackView
|
return stackView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let iconImageView: UIImageView = {
|
let spoilerMetaLabel = MetaLabel(style: .statusSpoilerOverlay)
|
||||||
let imageView = UIImageView()
|
|
||||||
imageView.image = UIImage(systemName: "eye", withConfiguration: UIImage.SymbolConfiguration(font: .systemFont(ofSize: 34, weight: .light)))
|
|
||||||
imageView.tintColor = Asset.Colors.Label.secondary.color
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
|
|
||||||
let titleLabel: UILabel = {
|
let hintLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
|
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
label.textColor = Asset.Colors.Label.secondary.color
|
||||||
label.text = L10n.Common.Controls.Status.contentWarning
|
label.text = L10n.Common.Controls.Status.mediaContentWarning
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let spoilerMetaLabel = MetaLabel(style: .statusSpoiler)
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
@ -61,19 +55,11 @@ extension SpoilerOverlayView {
|
||||||
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
containerStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
let topPaddingView = UIView()
|
let topPaddingView = UIView()
|
||||||
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
topPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerStackView.addArrangedSubview(topPaddingView)
|
containerStackView.addArrangedSubview(topPaddingView)
|
||||||
iconImageView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
containerStackView.addArrangedSubview(iconImageView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
iconImageView.widthAnchor.constraint(equalToConstant: 52.0).priority(.required - 1),
|
|
||||||
iconImageView.heightAnchor.constraint(equalToConstant: 32.0).priority(.required - 1),
|
|
||||||
])
|
|
||||||
iconImageView.setContentCompressionResistancePriority(.required, for: .vertical)
|
|
||||||
containerStackView.addArrangedSubview(titleLabel)
|
|
||||||
containerStackView.addArrangedSubview(spoilerMetaLabel)
|
containerStackView.addArrangedSubview(spoilerMetaLabel)
|
||||||
|
containerStackView.addArrangedSubview(hintLabel)
|
||||||
let bottomPaddingView = UIView()
|
let bottomPaddingView = UIView()
|
||||||
bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
containerStackView.addArrangedSubview(bottomPaddingView)
|
containerStackView.addArrangedSubview(bottomPaddingView)
|
||||||
|
@ -82,6 +68,8 @@ extension SpoilerOverlayView {
|
||||||
])
|
])
|
||||||
topPaddingView.setContentCompressionResistancePriority(.defaultLow - 100, for: .vertical)
|
topPaddingView.setContentCompressionResistancePriority(.defaultLow - 100, for: .vertical)
|
||||||
bottomPaddingView.setContentCompressionResistancePriority(.defaultLow - 100, for: .vertical)
|
bottomPaddingView.setContentCompressionResistancePriority(.defaultLow - 100, for: .vertical)
|
||||||
|
|
||||||
|
spoilerMetaLabel.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setComponentHidden(_ isHidden: Bool) {
|
public func setComponentHidden(_ isHidden: Bool) {
|
||||||
|
|
Loading…
Reference in New Issue