diff --git a/Tusker/Views/CachedImageView.swift b/Tusker/Views/CachedImageView.swift index 617a301d..f72d1b7a 100644 --- a/Tusker/Views/CachedImageView.swift +++ b/Tusker/Views/CachedImageView.swift @@ -16,6 +16,12 @@ class CachedImageView: UIImageView { private var fetchTask: Task? private var blurHashTask: DispatchWorkItem? + override var image: UIImage? { + didSet { + fetchTask?.cancel() + } + } + init(cache: ImageCache) { self.cache = cache super.init(frame: .zero) @@ -31,10 +37,10 @@ class CachedImageView: UIImageView { NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) } - func update(for url: URL?, blurhash: String? = nil) { + func update(for url: URL?, blurhash: String? = nil, placeholder: UIImage? = nil) { if url != self.url || (url != nil && self.image == nil) { self.url = url - self.image = nil + super.image = placeholder updateBlurhash(blurhash, for: url) updateImage() } @@ -82,7 +88,7 @@ class CachedImageView: UIImageView { return } try Task.checkCancellation() - self.image = transformedImage + super.image = transformedImage self.isGrayscale = Preferences.shared.grayscaleImages self.blurHashTask?.cancel() } diff --git a/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift b/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift index 843301a8..da3540a0 100644 --- a/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift +++ b/Tusker/Views/Status/TimelineStatusCollectionViewCell.swift @@ -20,6 +20,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti static let monospaceFont = UIFontMetrics.default.scaledFont(for: .monospacedSystemFont(ofSize: 16, weight: .regular)) static let contentParagraphStyle = HTMLConverter.defaultParagraphStyle + private static let timelineReasonIconSize: CGFloat = 25 + // MARK: Subviews private lazy var timelineReasonLabel = EmojiLabel().configure { @@ -27,8 +29,16 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti $0.font = .preferredFont(forTextStyle: .body) $0.adjustsFontForContentSizeCategory = true } - private let timelineReasonIcon = UIImageView(image: reblogIcon).configure { + private let timelineReasonIcon = CachedImageView(cache: .avatars).configure { + $0.image = reblogIcon + $0.contentMode = .scaleAspectFit + $0.layer.masksToBounds = true $0.tintColor = .secondaryLabel + NSLayoutConstraint.activate([ + // this needs to be lessThanOrEqualTo not just equalTo b/c otherwise intermediate layouts are broken + $0.heightAnchor.constraint(lessThanOrEqualToConstant: TimelineStatusCollectionViewCell.timelineReasonIconSize), + $0.widthAnchor.constraint(equalTo: $0.heightAnchor), + ]) } private lazy var timelineReasonHStack = UIStackView(arrangedSubviews: [ timelineReasonIcon, @@ -304,6 +314,9 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti mainContainerTopToReblogLabelConstraint = mainContainer.topAnchor.constraint(equalTo: timelineReasonHStack.bottomAnchor, constant: 4) mainContainerTopToSelfConstraint = mainContainer.topAnchor.constraint(equalTo: statusContainer.topAnchor, constant: 8) + // when flipping between topToReblog and topToSelf constraints, the framework sometimes thinks both of them should be active simultaneously + // even though the code never does that; so let this one get broken temporarily + mainContainerTopToSelfConstraint.priority = .init(999) mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4) mainContainerBottomToSelfConstraint = mainContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6) @@ -546,7 +559,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti rebloggerID = status.account.id hideTimelineReason = false - timelineReasonIcon.image = reblogIcon updateRebloggerLabel(reblogger: status.account) status = rebloggedStatus } else { @@ -604,6 +616,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti } metaIndicatorsView.updateUI(status: status) + timelineReasonIcon.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.timelineReasonIconSize if let rebloggerID, let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) { updateRebloggerLabel(reblogger: reblogger) @@ -650,6 +663,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti } private func updateRebloggerLabel(reblogger: AccountMO) { + timelineReasonIcon.update(for: reblogger.avatar, placeholder: reblogIcon) + if Preferences.shared.hideCustomEmojiInUsernames { timelineReasonLabel.text = "\(reblogger.displayNameWithoutCustomEmoji) reblogged" timelineReasonLabel.removeEmojis()