diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 6d7b587..3422727 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -248,6 +248,7 @@ "search.scope.tags" = "Hashtags"; "share-extension-error.no-account-found" = "No account found"; "status.accessibility.view-author-profile" = "View author's profile"; +"status.accessibility.view-reblogger-profile" = "View booster's profile"; "status.bookmark" = "Bookmark"; "status.content-warning-abbreviation" = "CW"; "status.content-warning.accessibility" = "Content warning"; diff --git a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift index 62cd684..13bf759 100644 --- a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift @@ -88,6 +88,15 @@ public extension StatusViewModel { } } + var rebloggerAvatarURL: URL { + if !identityContext.appPreferences.shouldReduceMotion, + identityContext.appPreferences.animateAvatars == .everywhere { + return statusService.status.account.avatar + } else { + return statusService.status.account.avatarStatic + } + } + var time: String? { statusService.status.displayStatus.createdAt.timeAgo } var accessibilityTime: String? { statusService.status.displayStatus.createdAt.accessibilityTimeAgo } @@ -209,6 +218,16 @@ public extension StatusViewModel { .eraseToAnyPublisher()) } + func rebloggerAccountSelected() { + eventsSubject.send( + Just(.navigation( + .profile( + statusService.navigationService.profileService( + account: statusService.status.account)))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher()) + } + func rebloggedBySelected() { eventsSubject.send( Just(.navigation(.collection(statusService.rebloggedByService()))) diff --git a/Views/UIKit/Content Views/StatusView.swift b/Views/UIKit/Content Views/StatusView.swift index 3894647..9d5a338 100644 --- a/Views/UIKit/Content Views/StatusView.swift +++ b/Views/UIKit/Content Views/StatusView.swift @@ -12,6 +12,7 @@ final class StatusView: UIView { let avatarButton = UIButton() let infoIcon = UIImageView() let infoLabel = UILabel() + let rebloggerButton = UIButton() let displayNameLabel = UILabel() let accountLabel = UILabel() let timeLabel = UILabel() @@ -44,6 +45,7 @@ final class StatusView: UIView { private let inReplyToView = UIView() private let hasReplyFollowingView = UIView() private var statusConfiguration: StatusContentConfiguration + private var infoIconCenterYConstraint: NSLayoutConstraint? private var cancellables = Set() init(configuration: StatusContentConfiguration) { @@ -135,6 +137,7 @@ private extension StatusView { containerStackView.spacing = .defaultSpacing infoIcon.tintColor = .secondaryLabel + infoIcon.contentMode = .scaleAspectFit infoIcon.setContentCompressionResistancePriority(.required, for: .vertical) sideStackView.axis = .vertical @@ -154,6 +157,16 @@ private extension StatusView { infoLabel.setContentHuggingPriority(.required, for: .vertical) mainStackView.addArrangedSubview(infoLabel) + rebloggerButton.setTitleColor(.secondaryLabel, for: .normal) + rebloggerButton.titleLabel?.font = .preferredFont(forTextStyle: .caption1) + rebloggerButton.titleLabel?.adjustsFontForContentSizeCategory = true + rebloggerButton.contentHorizontalAlignment = .leading + rebloggerButton.setContentHuggingPriority(.required, for: .vertical) + mainStackView.addArrangedSubview(rebloggerButton) + rebloggerButton.addAction( + UIAction { [weak self] _ in self?.statusConfiguration.viewModel.rebloggerAccountSelected() }, + for: .touchUpInside) + displayNameLabel.font = .preferredFont(forTextStyle: .headline) displayNameLabel.adjustsFontForContentSizeCategory = true displayNameLabel.setContentHuggingPriority(.required, for: .horizontal) @@ -322,7 +335,6 @@ private extension StatusView { avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension), avatarHeightConstraint, sideStackView.widthAnchor.constraint(equalToConstant: .avatarDimension), - infoIcon.centerYAnchor.constraint(equalTo: infoLabel.centerYAnchor), avatarButton.leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor), avatarButton.topAnchor.constraint(equalTo: avatarImageView.topAnchor), avatarButton.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor), @@ -373,16 +385,31 @@ private extension StatusView { inReplyToView.isHidden = !viewModel.configuration.isReplyInContext hasReplyFollowingView.isHidden = !viewModel.configuration.hasReplyFollowing - if viewModel.isReblog { - infoLabel.attributedText = "status.reblogged-by".localizedBolding( + if viewModel.isReblog, let titleLabel = rebloggerButton.titleLabel { + let attributedTitle = "status.reblogged-by".localizedBolding( displayName: viewModel.rebloggedByDisplayName, emojis: viewModel.rebloggedByDisplayNameEmojis, - label: infoLabel) + label: titleLabel) + let highlightedAttributedTitle = NSMutableAttributedString(attributedString: attributedTitle) + + highlightedAttributedTitle.addAttribute( + .foregroundColor, + value: UIColor.tertiaryLabel, + range: .init(location: 0, length: highlightedAttributedTitle.length)) + rebloggerButton.setAttributedTitle( + attributedTitle, + for: .normal) + rebloggerButton.setAttributedTitle( + highlightedAttributedTitle, + for: .highlighted) + + infoIcon.centerYAnchor.constraint(equalTo: rebloggerButton.centerYAnchor).isActive = true infoIcon.image = UIImage( systemName: "arrow.2.squarepath", withConfiguration: UIImage.SymbolConfiguration(scale: .small)) - infoLabel.isHidden = false + infoLabel.isHidden = true infoIcon.isHidden = false + rebloggerButton.isHidden = false } else if viewModel.configuration.isPinned { let pinnedText: String @@ -394,16 +421,21 @@ private extension StatusView { } infoLabel.text = pinnedText + infoIcon.centerYAnchor.constraint(equalTo: infoLabel.centerYAnchor).isActive = true infoIcon.image = UIImage( systemName: "pin", withConfiguration: UIImage.SymbolConfiguration(scale: .small)) infoLabel.isHidden = false infoIcon.isHidden = false + rebloggerButton.isHidden = true } else { infoLabel.text = nil infoIcon.image = nil infoLabel.isHidden = true infoIcon.isHidden = true + rebloggerButton.setTitle(nil, for: .normal) + rebloggerButton.setImage(nil, for: .normal) + rebloggerButton.isHidden = true } mutableDisplayName.insert(emojis: viewModel.accountViewModel.emojis, view: displayNameLabel) @@ -496,6 +528,11 @@ private extension StatusView { let accessibilityAttributedLabel = NSMutableAttributedString(string: "") + if !rebloggerButton.isHidden, + let rebloggerAttributedText = rebloggerButton.attributedTitle(for: .normal) { + accessibilityAttributedLabel.appendWithSeparator(rebloggerAttributedText) + } + if !infoLabel.isHidden, let infoText = infoLabel.attributedText { accessibilityAttributedLabel.appendWithSeparator(infoText) } @@ -755,6 +792,17 @@ private extension StatusView { return true }) + if viewModel.isReblog { + actions.append( + UIAccessibilityCustomAction( + name: NSLocalizedString("status.accessibility.view-reblogger-profile", + comment: "")) { [weak self] _ in + self?.statusConfiguration.viewModel.rebloggerAccountSelected() + + return true + }) + } + actions.append( UIAccessibilityCustomAction( name: NSLocalizedString("accessibility.copy-text",