feat: add accessibility supports for compose scene

This commit is contained in:
CMK 2021-05-13 14:27:57 +08:00
parent 8d16a1cec4
commit ec2be58952
11 changed files with 147 additions and 6 deletions

View File

@ -74,10 +74,17 @@
"settings": "Settings",
"delete": "Delete"
},
"tabs": {
"home": "Home",
"search": "Search",
"notification": "Notification",
"profile": "Profile"
},
"status": {
"user_reblogged": "%s reblogged",
"user_replied_to": "Replied to %s",
"show_post": "Show Post",
"show_user_profile": "Show user profile",
"content_warning": "content warning",
"content_warning_text": "cw: %s",
"media_content_warning": "Tap to reveal that may be sensitive",
@ -331,6 +338,17 @@
"unlisted": "Unlisted",
"private": "Followers only",
"direct": "Only people I mention"
},
"accessibility": {
"append_attachment": "Append attachment",
"append_poll": "Append poll",
"remove_poll": "Remove poll",
"custom_emoji_picker": "Custom emoji picker",
"enable_content_warning": "Enable content warning",
"disable_content_warning": "Disable content warning",
"post_visibility_menu": "Post visibility menu",
"input_limit_remains_count": "Input limit remains %ld",
"input_limit_exceeds_count": "Input limit exceeds %ld"
}
},
"profile": {
@ -338,7 +356,12 @@
"dashboard": {
"posts": "posts",
"following": "following",
"followers": "followers"
"followers": "followers",
"accessibility": {
"count_posts": "%ld posts",
"count_following": "%ld following",
"count_followers": "%ld followers"
}
},
"segmented_control": {
"posts": "Posts",

View File

@ -32,6 +32,7 @@ extension CustomEmojiPickerSection {
],
completionHandler: nil
)
cell.accessibilityLabel = attribute.emoji.shortcode
return cell
}
}

View File

@ -102,6 +102,7 @@ extension StatusSection {
case .root:
cell.statusView.activeTextLabel.isAccessibilityElement = false
var accessibilityElements: [Any] = []
accessibilityElements.append(cell.statusView.avatarView)
accessibilityElements.append(cell.statusView.nameLabel)
accessibilityElements.append(cell.statusView.dateLabel)
accessibilityElements.append(contentsOf: cell.statusView.activeTextLabel.createAccessibilityElements())

View File

@ -200,6 +200,8 @@ internal enum L10n {
internal static let mediaContentWarning = L10n.tr("Localizable", "Common.Controls.Status.MediaContentWarning")
/// Show Post
internal static let showPost = L10n.tr("Localizable", "Common.Controls.Status.ShowPost")
/// Show user profile
internal static let showUserProfile = L10n.tr("Localizable", "Common.Controls.Status.ShowUserProfile")
/// %@ reblogged
internal static func userReblogged(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Status.UserReblogged", String(describing: p1))
@ -267,6 +269,16 @@ internal enum L10n {
internal static let url = L10n.tr("Localizable", "Common.Controls.Status.Tag.Url")
}
}
internal enum Tabs {
/// Home
internal static let home = L10n.tr("Localizable", "Common.Controls.Tabs.Home")
/// Notification
internal static let notification = L10n.tr("Localizable", "Common.Controls.Tabs.Notification")
/// Profile
internal static let profile = L10n.tr("Localizable", "Common.Controls.Tabs.Profile")
/// Search
internal static let search = L10n.tr("Localizable", "Common.Controls.Tabs.Search")
}
internal enum Timeline {
internal enum Accessibility {
/// %@ favorites
@ -326,6 +338,30 @@ internal enum L10n {
internal static func replyingToUser(_ p1: Any) -> String {
return L10n.tr("Localizable", "Scene.Compose.ReplyingToUser", String(describing: p1))
}
internal enum Accessibility {
/// Append attachment
internal static let appendAttachment = L10n.tr("Localizable", "Scene.Compose.Accessibility.AppendAttachment")
/// Append poll
internal static let appendPoll = L10n.tr("Localizable", "Scene.Compose.Accessibility.AppendPoll")
/// Custom emoji picker
internal static let customEmojiPicker = L10n.tr("Localizable", "Scene.Compose.Accessibility.CustomEmojiPicker")
/// Disable content warning
internal static let disableContentWarning = L10n.tr("Localizable", "Scene.Compose.Accessibility.DisableContentWarning")
/// Enable content warning
internal static let enableContentWarning = L10n.tr("Localizable", "Scene.Compose.Accessibility.EnableContentWarning")
/// Input limit exceeds %ld
internal static func inputLimitExceedsCount(_ p1: Int) -> String {
return L10n.tr("Localizable", "Scene.Compose.Accessibility.InputLimitExceedsCount", p1)
}
/// Input limit remains %ld
internal static func inputLimitRemainsCount(_ p1: Int) -> String {
return L10n.tr("Localizable", "Scene.Compose.Accessibility.InputLimitRemainsCount", p1)
}
/// Post visibility menu
internal static let postVisibilityMenu = L10n.tr("Localizable", "Scene.Compose.Accessibility.PostVisibilityMenu")
/// Remove poll
internal static let removePoll = L10n.tr("Localizable", "Scene.Compose.Accessibility.RemovePoll")
}
internal enum Attachment {
/// This %@ is broken and can't be\nuploaded to Mastodon.
internal static func attachmentBroken(_ p1: Any) -> String {
@ -481,6 +517,20 @@ internal enum L10n {
internal static let following = L10n.tr("Localizable", "Scene.Profile.Dashboard.Following")
/// posts
internal static let posts = L10n.tr("Localizable", "Scene.Profile.Dashboard.Posts")
internal enum Accessibility {
/// %ld followers
internal static func countFollowers(_ p1: Int) -> String {
return L10n.tr("Localizable", "Scene.Profile.Dashboard.Accessibility.CountFollowers", p1)
}
/// %ld following
internal static func countFollowing(_ p1: Int) -> String {
return L10n.tr("Localizable", "Scene.Profile.Dashboard.Accessibility.CountFollowing", p1)
}
/// %ld posts
internal static func countPosts(_ p1: Int) -> String {
return L10n.tr("Localizable", "Scene.Profile.Dashboard.Accessibility.CountPosts", p1)
}
}
}
internal enum RelationshipActionAlert {
internal enum ConfirmUnblockUsre {

View File

@ -81,6 +81,7 @@ Please check your internet connection.";
"Common.Controls.Status.Poll.VoterCount.Multiple" = "%d voters";
"Common.Controls.Status.Poll.VoterCount.Single" = "%d voter";
"Common.Controls.Status.ShowPost" = "Show Post";
"Common.Controls.Status.ShowUserProfile" = "Show user profile";
"Common.Controls.Status.Tag.Email" = "Email";
"Common.Controls.Status.Tag.Emoji" = "Emoji";
"Common.Controls.Status.Tag.Hashtag" = "Hashtag";
@ -89,6 +90,10 @@ Please check your internet connection.";
"Common.Controls.Status.Tag.Url" = "URL";
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
"Common.Controls.Tabs.Home" = "Home";
"Common.Controls.Tabs.Notification" = "Notification";
"Common.Controls.Tabs.Profile" = "Profile";
"Common.Controls.Tabs.Search" = "Search";
"Common.Controls.Timeline.Accessibility.CountFavorites" = "%@ favorites";
"Common.Controls.Timeline.Accessibility.CountReblogs" = "%@ reblogs";
"Common.Controls.Timeline.Accessibility.CountReplies" = "%@ replies";
@ -105,6 +110,15 @@ Your account looks like this to them.";
"Common.Controls.Timeline.Loader.ShowMoreReplies" = "Show more replies";
"Common.Countable.Photo.Multiple" = "photos";
"Common.Countable.Photo.Single" = "photo";
"Scene.Compose.Accessibility.AppendAttachment" = "Append attachment";
"Scene.Compose.Accessibility.AppendPoll" = "Append poll";
"Scene.Compose.Accessibility.CustomEmojiPicker" = "Custom emoji picker";
"Scene.Compose.Accessibility.DisableContentWarning" = "Disable content warning";
"Scene.Compose.Accessibility.EnableContentWarning" = "Enable content warning";
"Scene.Compose.Accessibility.InputLimitExceedsCount" = "Input limit exceeds %ld";
"Scene.Compose.Accessibility.InputLimitRemainsCount" = "Input limit remains %ld";
"Scene.Compose.Accessibility.PostVisibilityMenu" = "Post visibility menu";
"Scene.Compose.Accessibility.RemovePoll" = "Remove poll";
"Scene.Compose.Attachment.AttachmentBroken" = "This %@ is broken and can't be
uploaded to Mastodon.";
"Scene.Compose.Attachment.DescriptionPhoto" = "Describe photo for low vision people...";
@ -159,6 +173,9 @@ tap the link to confirm your account.";
"Scene.Notification.Action.Reblog" = "rebloged your post";
"Scene.Notification.Title.Everything" = "Everything";
"Scene.Notification.Title.Mentions" = "Mentions";
"Scene.Profile.Dashboard.Accessibility.CountFollowers" = "%ld followers";
"Scene.Profile.Dashboard.Accessibility.CountFollowing" = "%ld following";
"Scene.Profile.Dashboard.Accessibility.CountPosts" = "%ld posts";
"Scene.Profile.Dashboard.Followers" = "followers";
"Scene.Profile.Dashboard.Following" = "following";
"Scene.Profile.Dashboard.Posts" = "posts";

View File

@ -47,6 +47,10 @@ extension CustomEmojiPickerItemCollectionViewCell {
emojiImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
emojiImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
isAccessibilityElement = true
accessibilityTraits = .button
accessibilityHint = "emoji"
}
}

View File

@ -260,6 +260,21 @@ extension ComposeViewController {
.receive(on: DispatchQueue.main)
.assign(to: \.isEnabled, on: composeToolbarView.pollButton)
.store(in: &disposeBag)
Publishers.CombineLatest(
viewModel.isPollComposing,
viewModel.isPollToolbarButtonEnabled
)
.receive(on: DispatchQueue.main)
.sink { [weak self] isPollComposing, isPollToolbarButtonEnabled in
guard let self = self else { return }
guard isPollToolbarButtonEnabled else {
self.composeToolbarView.pollButton.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
return
}
self.composeToolbarView.pollButton.accessibilityLabel = isPollComposing ? L10n.Scene.Compose.Accessibility.removePoll : L10n.Scene.Compose.Accessibility.appendPoll
}
.store(in: &disposeBag)
// bind image picker toolbar state
viewModel.attachmentServices
@ -271,6 +286,15 @@ extension ComposeViewController {
}
.store(in: &disposeBag)
// bind content warning button state
viewModel.isContentWarningComposing
.receive(on: DispatchQueue.main)
.sink { [weak self] isContentWarningComposing in
guard let self = self else { return }
self.composeToolbarView.contentWarningButton.accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
}
.store(in: &disposeBag)
// bind visibility toolbar UI
Publishers.CombineLatest(
viewModel.selectedStatusVisibility,
@ -294,9 +318,11 @@ extension ComposeViewController {
case _ where count < 0:
self.composeToolbarView.characterCountLabel.font = .systemFont(ofSize: 24, weight: .bold)
self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.danger.color
self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitExceedsCount(abs(count))
default:
self.composeToolbarView.characterCountLabel.font = .systemFont(ofSize: 15, weight: .regular)
self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.Label.secondary.color
self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(count)
}
}
.store(in: &disposeBag)

View File

@ -28,6 +28,7 @@ final class ComposeToolbarView: UIView {
let button = HighlightDimmableButton()
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
button.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal)
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendAttachment
return button
}()
@ -35,6 +36,7 @@ final class ComposeToolbarView: UIView {
let button = HighlightDimmableButton(type: .custom)
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
button.setImage(UIImage(systemName: "list.bullet", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium)), for: .normal)
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.appendPoll
return button
}()
@ -45,6 +47,7 @@ final class ComposeToolbarView: UIView {
.af.imageScaled(to: CGSize(width: 20, height: 20))
.withRenderingMode(.alwaysTemplate)
button.setImage(image, for: .normal)
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.customEmojiPicker
return button
}()
@ -52,6 +55,7 @@ final class ComposeToolbarView: UIView {
let button = HighlightDimmableButton()
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
button.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal)
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning
return button
}()
@ -59,6 +63,7 @@ final class ComposeToolbarView: UIView {
let button = HighlightDimmableButton()
ComposeToolbarView.configureToolbarButtonAppearance(button: button)
button.setImage(UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)), for: .normal)
button.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu
return button
}()
@ -67,6 +72,7 @@ final class ComposeToolbarView: UIView {
label.font = .systemFont(ofSize: 15, weight: .regular)
label.text = "500"
label.textColor = Asset.Colors.Label.secondary.color
label.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(500)
return label
}()

View File

@ -25,10 +25,10 @@ class MainTabBarController: UITabBarController {
var title: String {
switch self {
case .home: return "Home"
case .search: return "Search"
case .notification: return "Notification"
case .me: return "Me"
case .home: return L10n.Common.Controls.Tabs.home
case .search: return L10n.Common.Controls.Tabs.search
case .notification: return L10n.Common.Controls.Tabs.notification
case .me: return L10n.Common.Controls.Tabs.profile
}
}
@ -99,6 +99,7 @@ extension MainTabBarController {
let viewController = tab.viewController(context: context, coordinator: coordinator)
viewController.tabBarItem.title = "" // set text to empty string for image only style (SDK failed to layout when set to nil)
viewController.tabBarItem.image = tab.image
viewController.tabBarItem.accessibilityLabel = tab.title
return viewController
}
setViewControllers(viewControllers, animated: false)

View File

@ -479,6 +479,8 @@ extension ProfileViewController {
guard let self = self else { return }
let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-"
self.profileHeaderViewController.profileHeaderView.statusDashboardView.postDashboardMeterView.numberLabel.text = text
self.profileHeaderViewController.profileHeaderView.statusDashboardView.postDashboardMeterView.isAccessibilityElement = true
self.profileHeaderViewController.profileHeaderView.statusDashboardView.postDashboardMeterView.accessibilityLabel = L10n.Scene.Profile.Dashboard.Accessibility.countPosts(count ?? 0)
}
.store(in: &disposeBag)
viewModel.followingCount
@ -486,6 +488,8 @@ extension ProfileViewController {
guard let self = self else { return }
let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-"
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followingDashboardMeterView.numberLabel.text = text
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followingDashboardMeterView.isAccessibilityElement = true
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followingDashboardMeterView.accessibilityLabel = L10n.Scene.Profile.Dashboard.Accessibility.countFollowing(count ?? 0)
}
.store(in: &disposeBag)
viewModel.followersCount
@ -493,6 +497,8 @@ extension ProfileViewController {
guard let self = self else { return }
let text = count.flatMap { MastodonMetricFormatter().string(from: $0) } ?? "-"
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followersDashboardMeterView.numberLabel.text = text
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followersDashboardMeterView.isAccessibilityElement = true
self.profileHeaderViewController.profileHeaderView.statusDashboardView.followersDashboardMeterView.accessibilityLabel = L10n.Scene.Profile.Dashboard.Accessibility.countFollowers(count ?? 0)
}
.store(in: &disposeBag)

View File

@ -75,7 +75,13 @@ final class StatusView: UIView {
return label
}()
let avatarView = UIView()
let avatarView: UIView = {
let view = UIView()
view.isAccessibilityElement = true
view.accessibilityTraits = .button
view.accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
return view
}()
let avatarButton: UIButton = {
let button = HighlightDimmableButton(type: .custom)
let placeholderImage = UIImage.placeholder(size: avatarImageSize, color: .systemFill)