From 65887f963a74e17a2856af1abf3fd8ca225af40a Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 29 Jun 2021 17:08:41 +0800 Subject: [PATCH] feat: using replica status view in compose scene --- Mastodon.xcodeproj/project.pbxproj | 8 +- .../Compose/ComposeViewModel+Diffable.swift | 18 +- ...eRepliedToStatusContentTableViewCell.swift | 8 +- ...ComposeStatusAttachmentTableViewCell.swift | 4 +- .../ComposeStatusContentTableViewCell.swift | 10 +- .../ComposeStatusPollTableViewCell.swift | 4 +- .../Compose/View/ComposeCollectionView.swift | 28 -- .../Compose/View/ReplicaStatusView.swift | 263 ++++++++++++++++++ .../Scene/Share/View/Content/StatusView.swift | 1 - 9 files changed, 286 insertions(+), 58 deletions(-) delete mode 100644 Mastodon/Scene/Compose/View/ComposeCollectionView.swift create mode 100644 Mastodon/Scene/Compose/View/ReplicaStatusView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 6f195f704..9911352ce 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -193,7 +193,6 @@ DB03F7F026899097007B274C /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; }; - DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */; }; DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; }; DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; @@ -232,6 +231,7 @@ DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */; }; DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */; }; DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; + DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; @@ -811,7 +811,6 @@ DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = ""; }; - DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeCollectionView.swift; sourceTree = ""; }; DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; @@ -853,6 +852,7 @@ DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollTableViewCell.swift; sourceTree = ""; }; DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = ""; }; DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; + DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplicaStatusView.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1975,8 +1975,8 @@ DB55D32225FB4D320002F825 /* View */ = { isa = PBXGroup; children = ( + DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */, DB03F7F42689B782007B274C /* ComposeTableView.swift */, - DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */, DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */, DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */, DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */, @@ -3484,8 +3484,8 @@ DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */, DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, - DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */, DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */, + DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */, DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */, 0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */, DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift index 1999152d3..7f60a5b3f 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift @@ -190,11 +190,8 @@ extension ComposeViewModel: UITableViewDataSource { // set avatar cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL())) // set name username - cell.statusView.nameLabel.text = { - let author = status.author - return author.displayName.isEmpty ? author.username : author.displayName - }() - cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct + cell.statusView.nameLabel.configure(content: status.author.displayNameWithFallback, emojiDict: status.author.emojiDict) + cell.statusView.usernameLabel.text = "@" + status.author.acct // set text let content = MastodonContent(content: status.content, emojis: status.emojiMeta) do { @@ -228,6 +225,8 @@ extension ComposeViewModel: UITableViewDataSource { } // configure author ComposeStatusSection.configureStatusContent(cell: cell, attribute: composeStatusAttribute) + // configure content warning + cell.statusContentWarningEditorView.textView.text = composeStatusAttribute.contentWarningContent.value // bind content warning composeStatusAttribute.isContentWarningComposing .receive(on: DispatchQueue.main) @@ -235,7 +234,6 @@ extension ComposeViewModel: UITableViewDataSource { guard let cell = cell else { return } guard let tableView = tableView else { return } // self size input cell - //tableView. cell.statusContentWarningEditorView.isHidden = !isContentWarningComposing cell.statusContentWarningEditorView.alpha = 0 UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut]) { @@ -245,19 +243,21 @@ extension ComposeViewModel: UITableViewDataSource { } } .store(in: &cell.disposeBag) + cell.contentWarningContent .removeDuplicates() .receive(on: DispatchQueue.main) .sink { [weak tableView, weak self] text in - guard let tableView = tableView else { return } guard let self = self else { return } + // bind input data + self.composeStatusAttribute.contentWarningContent.value = text + // self size input cell + guard let tableView = tableView else { return } UIView.performWithoutAnimation { tableView.beginUpdates() tableView.endUpdates() } - // bind input data - self.composeStatusAttribute.contentWarningContent.value = text } .store(in: &cell.disposeBag) // configure custom emoji picker diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToStatusContentTableViewCell.swift index 3e8d732f6..4ba68cedd 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeRepliedToStatusContentTableViewCell.swift @@ -12,14 +12,13 @@ final class ComposeRepliedToStatusContentTableViewCell: UITableViewCell { var disposeBag = Set() - let statusView = StatusView() + let statusView = ReplicaStatusView() let framePublisher = PassthroughSubject() override func prepareForReuse() { super.prepareForReuse() - statusView.updateContentWarningDisplay(isHidden: true, animated: false) disposeBag.removeAll() } @@ -46,9 +45,6 @@ extension ComposeRepliedToStatusContentTableViewCell { selectionStyle = .none backgroundColor = .clear - statusView.actionToolbarContainer.isHidden = true - statusView.revealContentWarningButton.isHidden = true - statusView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(statusView) NSLayoutConstraint.activate([ @@ -57,6 +53,8 @@ extension ComposeRepliedToStatusContentTableViewCell { contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: statusView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10).identifier("ComposeRepliedToStatusContentCollectionViewCell.contentView.bottom to statusView.bottom"), ]) + + statusView.headerContainerView.isHidden = true } } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift index 713fcc356..8b2ce455d 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift @@ -26,9 +26,9 @@ final class ComposeStatusAttachmentTableViewCell: UITableViewCell { } private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint! - let collectionView: ComposeCollectionView = { + let collectionView: UICollectionView = { let collectionViewLayout = ComposeStatusAttachmentTableViewCell.createLayout() - let collectionView = ComposeCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) collectionView.register(ComposeStatusAttachmentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self)) collectionView.backgroundColor = Asset.Scene.Compose.background.color collectionView.alwaysBounceVertical = true diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift index c9f6b758b..9946b1c9c 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift @@ -15,7 +15,7 @@ final class ComposeStatusContentTableViewCell: UITableViewCell { var disposeBag = Set() - let statusView = StatusView() + let statusView = ReplicaStatusView() let statusContentWarningEditorView = StatusContentWarningEditorView() @@ -108,12 +108,10 @@ extension ComposeStatusContentTableViewCell { ]) statusContentWarningEditorView.textView.delegate = self + statusView.nameTrialingDotLabel.isHidden = true + statusView.dateLabel.isHidden = true statusContentWarningEditorView.isHidden = true statusView.statusContainerStackView.isHidden = true - statusView.actionToolbarContainer.isHidden = true - statusView.revealContentWarningButton.isHidden = true - - statusView.contentMetaText.textView.delegate = self } } @@ -123,8 +121,6 @@ extension ComposeStatusContentTableViewCell: UITextViewDelegate { func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { switch textView { - case statusView.contentMetaText.textView: - return false case statusContentWarningEditorView.textView: // disable input line break guard text != "\n" else { return false } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusPollTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusPollTableViewCell.swift index b39a86daf..c12c346f3 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusPollTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusPollTableViewCell.swift @@ -34,9 +34,9 @@ final class ComposeStatusPollTableViewCell: UITableViewCell { } private(set) var collectionViewHeightLayoutConstraint: NSLayoutConstraint! - let collectionView: ComposeCollectionView = { + let collectionView: UICollectionView = { let collectionViewLayout = ComposeStatusPollTableViewCell.createLayout() - let collectionView = ComposeCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) collectionView.register(ComposeStatusPollOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self)) collectionView.register(ComposeStatusPollOptionAppendEntryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self)) collectionView.register(ComposeStatusPollExpiresOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self)) diff --git a/Mastodon/Scene/Compose/View/ComposeCollectionView.swift b/Mastodon/Scene/Compose/View/ComposeCollectionView.swift deleted file mode 100644 index 2dc03bb84..000000000 --- a/Mastodon/Scene/Compose/View/ComposeCollectionView.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ComposeCollectionView.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-5-17. -// - -import UIKit - -final class ComposeCollectionView: UICollectionView { - - weak var autoCompleteViewController: AutoCompleteViewController? - - // adjust hitTest for auto-complete - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - guard let autoCompleteViewController = autoCompleteViewController else { - return super.hitTest(point, with: event) - } - - let thePoint = convert(point, to: autoCompleteViewController.view) - if let hitView = autoCompleteViewController.view.hitTest(thePoint, with: event) { - return hitView - } else { - return super.hitTest(point, with: event) - } - } - -} diff --git a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift new file mode 100644 index 000000000..7b1378625 --- /dev/null +++ b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift @@ -0,0 +1,263 @@ +// +// ReplicaStatusView.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-6-29. +// + +import os.log +import UIKit +import ActiveLabel +import FLAnimatedImage +import MetaTextView + +final class ReplicaStatusView: UIView { + + static let avatarImageSize = CGSize(width: 42, height: 42) + static let avatarImageCornerRadius: CGFloat = 4 + static let avatarToLabelSpacing: CGFloat = 5 + static let contentWarningBlurRadius: CGFloat = 12 + static let containerStackViewSpacing: CGFloat = 10 + + let containerStackView = UIStackView() + let headerContainerView = UIView() + let authorContainerView = UIView() + + static let reblogIconImage: UIImage = { + let font = UIFont.systemFont(ofSize: 13, weight: .medium) + let configuration = UIImage.SymbolConfiguration(font: font) + let image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color) + return image + }() + + static let replyIconImage: UIImage = { + let font = UIFont.systemFont(ofSize: 13, weight: .medium) + let configuration = UIImage.SymbolConfiguration(font: font) + let image = UIImage(systemName: "arrowshape.turn.up.left.fill", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color) + return image + }() + + static func iconAttributedString(image: UIImage) -> NSAttributedString { + let attributedString = NSMutableAttributedString() + let imageTextAttachment = NSTextAttachment() + let imageAttribute = NSAttributedString(attachment: imageTextAttachment) + imageTextAttachment.image = image + attributedString.append(imageAttribute) + return attributedString + } + + let headerIconLabel: UILabel = { + let label = UILabel() + label.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage) + return label + }() + + let headerInfoLabel: ActiveLabel = { + let label = ActiveLabel(style: .statusHeader) + label.text = "Bob reblogged" + label.layer.masksToBounds = false + return label + }() + + let avatarView: UIView = { + let view = UIView() + view.isAccessibilityElement = true + view.accessibilityTraits = .button + view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile + return view + }() + let avatarImageView: UIImageView = FLAnimatedImageView() + let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() + + let nameLabel: ActiveLabel = { + let label = ActiveLabel(style: .statusName) + return label + }() + + let nameTrialingDotLabel: UILabel = { + let label = UILabel() + label.textColor = Asset.Colors.Label.secondary.color + label.font = .systemFont(ofSize: 17) + label.text = "·" + label.isAccessibilityElement = false + return label + }() + + let usernameLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.textColor = Asset.Colors.Label.secondary.color + label.text = "@alice" + label.isAccessibilityElement = false + return label + }() + + let dateLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 13, weight: .regular) + label.textColor = Asset.Colors.Label.secondary.color + label.text = "1d" + return label + }() + + let contentMetaText: MetaText = { + let metaText = MetaText() + metaText.textView.backgroundColor = .clear + metaText.textView.isEditable = false + metaText.textView.isSelectable = false + metaText.textView.isScrollEnabled = false + metaText.textView.textContainer.lineFragmentPadding = 0 + metaText.textView.textContainerInset = .zero + metaText.textView.layer.masksToBounds = false + return metaText + }() + + let statusContainerStackView = UIStackView() + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ReplicaStatusView { + private func _init() { + // container: [reblog | author | status | action toolbar] + // note: do not set spacing for nested stackView to avoid SDK layout conflict issue + containerStackView.axis = .vertical + // containerStackView.spacing = 10 + containerStackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(containerStackView) + NSLayoutConstraint.activate([ + containerStackView.topAnchor.constraint(equalTo: topAnchor), + containerStackView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), + bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), + ]) + containerStackView.setContentHuggingPriority(.required - 1, for: .vertical) + containerStackView.setContentCompressionResistancePriority(.required - 1, for: .vertical) + + // header container: [icon | info] + let headerContainerStackView = UIStackView() + headerContainerStackView.axis = .horizontal + headerContainerStackView.spacing = 4 + headerContainerStackView.addArrangedSubview(headerIconLabel) + headerContainerStackView.addArrangedSubview(headerInfoLabel) + headerIconLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + headerContainerStackView.translatesAutoresizingMaskIntoConstraints = false + headerContainerView.addSubview(headerContainerStackView) + NSLayoutConstraint.activate([ + headerContainerStackView.topAnchor.constraint(equalTo: headerContainerView.topAnchor), + headerContainerStackView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor), + headerContainerStackView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor), + headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + ]) + containerStackView.addArrangedSubview(headerContainerView) + defer { + containerStackView.bringSubviewToFront(headerContainerView) + } + + // author container: [avatar | author meta container | reveal button] + let authorContainerStackView = UIStackView() + authorContainerStackView.axis = .horizontal + authorContainerStackView.spacing = StatusView.avatarToLabelSpacing + authorContainerStackView.distribution = .fill + + // avatar + avatarView.translatesAutoresizingMaskIntoConstraints = false + authorContainerStackView.addArrangedSubview(avatarView) + NSLayoutConstraint.activate([ + avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1), + avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1), + ]) + avatarImageView.translatesAutoresizingMaskIntoConstraints = false + avatarView.addSubview(avatarImageView) + NSLayoutConstraint.activate([ + avatarImageView.topAnchor.constraint(equalTo: avatarView.topAnchor), + avatarImageView.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), + avatarImageView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), + avatarImageView.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), + ]) + avatarStackedContainerButton.translatesAutoresizingMaskIntoConstraints = false + avatarView.addSubview(avatarStackedContainerButton) + NSLayoutConstraint.activate([ + avatarStackedContainerButton.topAnchor.constraint(equalTo: avatarView.topAnchor), + avatarStackedContainerButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), + avatarStackedContainerButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), + avatarStackedContainerButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), + ]) + + // author meta container: [title container | subtitle container] + let authorMetaContainerStackView = UIStackView() + authorContainerStackView.addArrangedSubview(authorMetaContainerStackView) + authorMetaContainerStackView.axis = .vertical + authorMetaContainerStackView.spacing = 4 + + // title container: [display name | "·" | date | padding] + let titleContainerStackView = UIStackView() + authorMetaContainerStackView.addArrangedSubview(titleContainerStackView) + titleContainerStackView.axis = .horizontal + titleContainerStackView.spacing = 4 + nameLabel.translatesAutoresizingMaskIntoConstraints = false + titleContainerStackView.addArrangedSubview(nameLabel) + NSLayoutConstraint.activate([ + nameLabel.heightAnchor.constraint(equalToConstant: 22).priority(.defaultHigh), + ]) + titleContainerStackView.alignment = .firstBaseline + titleContainerStackView.addArrangedSubview(nameTrialingDotLabel) + titleContainerStackView.addArrangedSubview(dateLabel) + titleContainerStackView.addArrangedSubview(UIView()) // padding + nameLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal) + nameTrialingDotLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal) + nameTrialingDotLabel.setContentCompressionResistancePriority(.required - 2, for: .horizontal) + dateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + dateLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal) + + // subtitle container: [username] + let subtitleContainerStackView = UIStackView() + authorMetaContainerStackView.addArrangedSubview(subtitleContainerStackView) + subtitleContainerStackView.axis = .horizontal + subtitleContainerStackView.addArrangedSubview(usernameLabel) + + authorContainerStackView.translatesAutoresizingMaskIntoConstraints = false + authorContainerView.addSubview(authorContainerStackView) + NSLayoutConstraint.activate([ + authorContainerStackView.topAnchor.constraint(equalTo: authorContainerView.topAnchor), + authorContainerStackView.leadingAnchor.constraint(equalTo: authorContainerView.leadingAnchor), + authorContainerStackView.trailingAnchor.constraint(equalTo: authorContainerView.trailingAnchor), + authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + ]) + containerStackView.addArrangedSubview(authorContainerView) + + // status container: [status] + containerStackView.addArrangedSubview(statusContainerStackView) + statusContainerStackView.axis = .vertical + statusContainerStackView.spacing = 10 + + // avoid overlay behind other views + defer { + containerStackView.bringSubviewToFront(authorContainerView) + } + + // status + statusContainerStackView.addArrangedSubview(contentMetaText.textView) + contentMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) + + avatarStackedContainerButton.isHidden = true + } +} + +// MARK: - AvatarConfigurableView +extension ReplicaStatusView: AvatarConfigurableView { + static var configurableAvatarImageSize: CGSize { return Self.avatarImageSize } + static var configurableAvatarImageCornerRadius: CGFloat { return 4 } + var configurableAvatarImageView: UIImageView? { avatarImageView } + var configurableAvatarButton: UIButton? { nil } +} diff --git a/Mastodon/Scene/Share/View/Content/StatusView.swift b/Mastodon/Scene/Share/View/Content/StatusView.swift index cdabdc1c1..84166c627 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView.swift @@ -207,7 +207,6 @@ final class StatusView: UIView { return actionToolbarContainer }() - //let activeTextLabel = ActiveLabel(style: .default) let contentMetaText: MetaText = { let metaText = MetaText() metaText.textView.backgroundColor = .clear