From 783b88b54af412e1949d152a923077d39b3ad93b Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Tue, 2 Feb 2021 12:18:15 -0800 Subject: [PATCH] VoiceOver wip --- Localizations/Localizable.strings | 2 ++ .../View Models/StatusViewModel.swift | 27 +++++++++++++++++++ Views/UIKit/AccountHeaderView.swift | 6 +++++ Views/UIKit/Content Views/StatusView.swift | 21 ++++++++++++--- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 3305f98..4e73da6 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -3,6 +3,7 @@ "about" = "About"; "about.acknowledgments" = "Acknowledgments"; "account.%@-followers" = "%@'s Followers"; +"account.avatar.accessibility-label-%@" = "Avatar: %@"; "account.block" = "Block"; "account.block.confirm-%@" = "Block %@?"; "account.domain-block-%@" = "Block domain %@"; @@ -15,6 +16,7 @@ "account.following-count" = "%ld Following"; "account.followed-by-%@" = "Followed by %@"; "account.follows-you" = "Follows you"; +"account.header.accessibility-label-%@" = "Header image: %@"; "account.hide-reblogs" = "Hide boosts"; "account.mute" = "Mute"; "account.request" = "Request"; diff --git a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift index c48399b..0607f79 100644 --- a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift @@ -89,10 +89,20 @@ public extension StatusViewModel { var time: String? { statusService.status.displayStatus.createdAt.timeAgo } + var accessibilityTime: String { + Self.accessiblityTimeFormatter.localizedString( + for: statusService.status.displayStatus.createdAt, + relativeTo: Date()) + } + var contextParentTime: String { Self.contextParentDateFormatter.string(from: statusService.status.displayStatus.createdAt) } + var accessibilityContextParentTime: String { + Self.contextParentAccessibilityDateFormatter.string(from: statusService.status.displayStatus.createdAt) + } + var applicationName: String? { statusService.status.displayStatus.application?.name } var applicationURL: URL? { @@ -335,6 +345,14 @@ public extension StatusViewModel { } private extension StatusViewModel { + private static let accessiblityTimeFormatter: RelativeDateTimeFormatter = { + let dateFormatter = RelativeDateTimeFormatter() + + dateFormatter.unitsStyle = .full + + return dateFormatter + }() + private static let contextParentDateFormatter: DateFormatter = { let dateFormatter = DateFormatter() @@ -343,4 +361,13 @@ private extension StatusViewModel { return dateFormatter }() + + private static let contextParentAccessibilityDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .short + + return dateFormatter + }() } diff --git a/Views/UIKit/AccountHeaderView.swift b/Views/UIKit/AccountHeaderView.swift index d9bf293..fbd71fb 100644 --- a/Views/UIKit/AccountHeaderView.swift +++ b/Views/UIKit/AccountHeaderView.swift @@ -36,8 +36,14 @@ final class AccountHeaderView: UIView { } } headerImageView.tag = accountViewModel.headerURL.hashValue + headerButton.accessibilityLabel = String.localizedStringWithFormat( + NSLocalizedString("account.header.accessibility-label-%@", comment: ""), + accountViewModel.displayName) avatarImageView.kf.setImage(with: accountViewModel.avatarURL(profile: true)) avatarImageView.tag = accountViewModel.avatarURL(profile: true).hashValue + avatarButton.accessibilityLabel = String.localizedStringWithFormat( + NSLocalizedString("account.avatar.accessibility-label-%@", comment: ""), + accountViewModel.displayName) if !accountViewModel.isSelf, let relationship = accountViewModel.relationship { followsYouLabel.isHidden = !relationship.followedBy diff --git a/Views/UIKit/Content Views/StatusView.swift b/Views/UIKit/Content Views/StatusView.swift index 57f6020..db23ff5 100644 --- a/Views/UIKit/Content Views/StatusView.swift +++ b/Views/UIKit/Content Views/StatusView.swift @@ -190,6 +190,7 @@ private extension StatusView { label.textColor = .secondaryLabel label.text = "•" label.setContentHuggingPriority(.required, for: .horizontal) + label.isAccessibilityElement = false } contextParentTimeApplicationStackView.addArrangedSubview(timeVisibilityDividerLabel) @@ -197,6 +198,7 @@ private extension StatusView { contextParentTimeApplicationStackView.addArrangedSubview(visibilityImageView) visibilityImageView.contentMode = .scaleAspectFit visibilityImageView.tintColor = .secondaryLabel + visibilityImageView.isAccessibilityElement = true contextParentTimeApplicationStackView.addArrangedSubview(visibilityApplicationDividerLabel) @@ -330,13 +332,12 @@ private extension StatusView { interactionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension) ]) - - NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) .sink { [weak self] _ in self?.configureUserInteractionEnabledForAccessibility() } .store(in: &cancellables) } + // swiftlint:disable:next cyclomatic_complexity func applyStatusConfiguration() { let viewModel = statusConfiguration.viewModel let isContextParent = viewModel.configuration.isContextParent @@ -347,6 +348,9 @@ private extension StatusView { menuButton.menu = menu(viewModel: viewModel) avatarImageView.kf.setImage(with: viewModel.avatarURL) + avatarButton.accessibilityLabel = String.localizedStringWithFormat( + NSLocalizedString("account.avatar.accessibility-label-%@", comment: ""), + viewModel.displayName) sideStackView.isHidden = isContextParent avatarImageView.removeFromSuperview() @@ -420,12 +424,15 @@ private extension StatusView { accountLabel.text = viewModel.accountName timeLabel.text = viewModel.time + timeLabel.accessibilityLabel = viewModel.accessibilityTime timeLabel.isHidden = isContextParent bodyView.viewModel = viewModel contextParentTimeLabel.text = viewModel.contextParentTime + contextParentTimeLabel.accessibilityLabel = viewModel.accessibilityContextParentTime visibilityImageView.image = UIImage(systemName: viewModel.visibility.systemImageName) + visibilityImageView.accessibilityLabel = viewModel.visibility.title visibilityApplicationDividerLabel.isHidden = viewModel.applicationName == nil applicationButton.isHidden = viewModel.applicationName == nil applicationButton.setTitle(viewModel.applicationName, for: .normal) @@ -485,12 +492,20 @@ private extension StatusView { isAccessibilityElement = !viewModel.configuration.isContextParent - let accessibilityAttributedLabel = NSMutableAttributedString(attributedString: mutableDisplayName) + let accessibilityAttributedLabel = NSMutableAttributedString(string: "") + + if let infoText = infoLabel.attributedText { + accessibilityAttributedLabel.appendWithSeparator(infoText) + } + + accessibilityAttributedLabel.append(mutableDisplayName) if let bodyAccessibilityAttributedLabel = bodyView.accessibilityAttributedLabel { accessibilityAttributedLabel.appendWithSeparator(bodyAccessibilityAttributedLabel) } + accessibilityAttributedLabel.appendWithSeparator(viewModel.accessibilityTime) + self.accessibilityAttributedLabel = accessibilityAttributedLabel configureUserInteractionEnabledForAccessibility()