From c6da75487560a023826cc309894461eb29ab0df1 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 29 Nov 2022 21:49:47 -0500 Subject: [PATCH] Indicate when a followed hashtag caused a post to appear in the home timeline --- .../Timeline/TimelineViewController.swift | 5 ++ .../TimelineStatusCollectionViewCell.swift | 67 ++++++++++++------- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/Tusker/Screens/Timeline/TimelineViewController.swift b/Tusker/Screens/Timeline/TimelineViewController.swift index deb71f19..a9a86dcf 100644 --- a/Tusker/Screens/Timeline/TimelineViewController.swift +++ b/Tusker/Screens/Timeline/TimelineViewController.swift @@ -101,6 +101,11 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro // separate method because InstanceTimelineViewController needs to be able to customize it func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: StatusState) { cell.delegate = self + if case .home = timeline { + cell.showFollowedHashtags = true + } else { + cell.showFollowedHashtags = false + } cell.updateUI(statusID: id, state: state) } diff --git a/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift b/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift index d9bf59da..07c063c4 100644 --- a/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift +++ b/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift @@ -10,23 +10,26 @@ import UIKit import Pachyderm import Combine +private let reblogIcon = UIImage(systemName: "repeat") +private let hashtagIcon = UIImage(systemName: "number") + class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollectionViewCell { static let separatorInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 0) // MARK: Subviews - private lazy var rebloggerLabel = EmojiLabel().configure { + private lazy var timelineReasonLabel = EmojiLabel().configure { $0.textColor = .secondaryLabel $0.font = .preferredFont(forTextStyle: .body) $0.adjustsFontForContentSizeCategory = true } - private let reblogIcon = UIImageView(image: UIImage(systemName: "repeat")).configure { + private let timelineReasonIcon = UIImageView(image: reblogIcon).configure { $0.tintColor = .secondaryLabel } - private lazy var reblogHStack = UIStackView(arrangedSubviews: [ - reblogIcon, - rebloggerLabel, + private lazy var timelineReasonHStack = UIStackView(arrangedSubviews: [ + timelineReasonIcon, + timelineReasonLabel, ]).configure { $0.axis = .horizontal $0.spacing = 8 @@ -258,6 +261,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti true } var showPinned: Bool = false + var showFollowedHashtags: Bool = false // alas these need to be internal so they're accessible from the protocol extensions var statusID: String! @@ -275,12 +279,12 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti override init(frame: CGRect) { super.init(frame: frame) - for subview in [reblogHStack, mainContainer, actionsContainer] { + for subview in [timelineReasonHStack, mainContainer, actionsContainer] { subview.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(subview) } - mainContainerTopToReblogLabelConstraint = mainContainer.topAnchor.constraint(equalTo: reblogHStack.bottomAnchor, constant: 4) + mainContainerTopToReblogLabelConstraint = mainContainer.topAnchor.constraint(equalTo: timelineReasonHStack.bottomAnchor, constant: 4) mainContainerTopToSelfConstraint = mainContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8) mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4) mainContainerBottomToSelfConstraint = mainContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6) @@ -291,9 +295,9 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti NSLayoutConstraint.activate([ // why is this 4 but the mainContainerTopSelfConstraint constant 8? because this looks more balanced - reblogHStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4), - rebloggerLabel.leadingAnchor.constraint(equalTo: contentVStack.leadingAnchor), - reblogHStack.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -16), + timelineReasonHStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4), + timelineReasonLabel.leadingAnchor.constraint(equalTo: contentVStack.leadingAnchor), + timelineReasonHStack.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -16), mainContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), mainContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), @@ -427,23 +431,35 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti self.statusState = state + var hideTimelineReason = true + if let rebloggedStatus = status.reblog { reblogStatusID = statusID rebloggerID = status.account.id - reblogHStack.isHidden = false - mainContainerTopToReblogLabelConstraint.isActive = true - mainContainerTopToSelfConstraint.isActive = false - updateRebloggerLabel(reblogger: status.account) + hideTimelineReason = false + timelineReasonIcon.image = reblogIcon + updateRebloggerLabel(reblogger: status.account) status = rebloggedStatus } else { reblogStatusID = nil rebloggerID = nil - reblogHStack.isHidden = true - mainContainerTopToReblogLabelConstraint.isActive = false - mainContainerTopToSelfConstraint.isActive = true } + if showFollowedHashtags { + let hashtags = mastodonController.followedHashtags.filter({ followed in status.hashtags.contains(where: { followed.name == $0.name }) }) + if !hashtags.isEmpty { + hideTimelineReason = false + timelineReasonIcon.image = hashtagIcon + timelineReasonLabel.text = hashtags.map(\.name).formatted(.list(type: .and, width: .narrow)) + timelineReasonLabel.removeEmojis() + } + } + + timelineReasonHStack.isHidden = hideTimelineReason + mainContainerTopToReblogLabelConstraint.isActive = !hideTimelineReason + mainContainerTopToSelfConstraint.isActive = hideTimelineReason + doUpdateUI(status: status) doUpdateTimestamp(status: status) @@ -523,11 +539,11 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti private func updateRebloggerLabel(reblogger: AccountMO) { if Preferences.shared.hideCustomEmojiInUsernames { - rebloggerLabel.text = "\(reblogger.displayNameWithoutCustomEmoji) reblogged" - rebloggerLabel.removeEmojis() + timelineReasonLabel.text = "\(reblogger.displayNameWithoutCustomEmoji) reblogged" + timelineReasonLabel.removeEmojis() } else { - rebloggerLabel.text = "\(reblogger.displayOrUserName) reblogged" - rebloggerLabel.setEmojis(reblogger.emojis, identifier: reblogger.id) + timelineReasonLabel.text = "\(reblogger.displayOrUserName) reblogged" + timelineReasonLabel.setEmojis(reblogger.emojis, identifier: reblogger.id) } } @@ -566,10 +582,13 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti // MARK: Interaction @objc private func reblogLabelPressed() { - guard let rebloggerID else { - return + if let rebloggerID { + delegate?.selected(account: rebloggerID) + } else if showFollowedHashtags, + let status = mastodonController.persistentContainer.status(for: statusID), + let hashtag = mastodonController.followedHashtags.first(where: { followed in status.hashtags.contains(where: { followed.name == $0.name }) }) { + delegate?.selected(tag: Hashtag(name: hashtag.name, url: hashtag.url)) } - delegate?.selected(account: rebloggerID) } @objc private func accountPressed() {