feat: add accessibility supports for compose scene
This commit is contained in:
parent
8d16a1cec4
commit
ec2be58952
|
@ -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",
|
||||
|
|
|
@ -32,6 +32,7 @@ extension CustomEmojiPickerSection {
|
|||
],
|
||||
completionHandler: nil
|
||||
)
|
||||
cell.accessibilityLabel = attribute.emoji.shortcode
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -47,6 +47,10 @@ extension CustomEmojiPickerItemCollectionViewCell {
|
|||
emojiImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
emojiImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .button
|
||||
accessibilityHint = "emoji"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue