Various accessibility fixes (#1694)

* Hide the accessibility label for the swipe to reply menu

* Fixes #1104 - Add accessibility labels for timeline media

* Import new common strings

* Add sender to accessibility lables for all messages

* Improve accessibility labels for replies

* Add hint and selection to reaction accessibility labels

* Add changelog

* Address PR comments
This commit is contained in:
Stefan Ceriu
2023-09-13 14:39:13 +03:00
committed by GitHub
parent e8b8da6049
commit f96969ac37
12 changed files with 58 additions and 16 deletions
@@ -82,6 +82,7 @@
"common_forward_message" = "Forward message"; "common_forward_message" = "Forward message";
"common_gif" = "GIF"; "common_gif" = "GIF";
"common_image" = "Image"; "common_image" = "Image";
"common_in_reply_to" = "In reply to %1$@";
"common_invite_unknown_profile" = "This Matrix ID can't be found, so the invite might not be received."; "common_invite_unknown_profile" = "This Matrix ID can't be found, so the invite might not be received.";
"common_leaving_room" = "Leaving room"; "common_leaving_room" = "Leaving room";
"common_link_copied_to_clipboard" = "Link copied to clipboard"; "common_link_copied_to_clipboard" = "Link copied to clipboard";
@@ -99,6 +100,7 @@
"common_poll_total_votes" = "Total votes: %1$@"; "common_poll_total_votes" = "Total votes: %1$@";
"common_poll_undisclosed_text" = "Results will show after the poll has ended"; "common_poll_undisclosed_text" = "Results will show after the poll has ended";
"common_privacy_policy" = "Privacy policy"; "common_privacy_policy" = "Privacy policy";
"common_reaction" = "Reaction";
"common_reactions" = "Reactions"; "common_reactions" = "Reactions";
"common_refreshing" = "Refreshing…"; "common_refreshing" = "Refreshing…";
"common_replying_to" = "Replying to %1$@"; "common_replying_to" = "Replying to %1$@";
+6
View File
@@ -182,6 +182,10 @@ public enum L10n {
public static var commonGif: String { return L10n.tr("Localizable", "common_gif") } public static var commonGif: String { return L10n.tr("Localizable", "common_gif") }
/// Image /// Image
public static var commonImage: String { return L10n.tr("Localizable", "common_image") } public static var commonImage: String { return L10n.tr("Localizable", "common_image") }
/// In reply to %1$@
public static func commonInReplyTo(_ p1: Any) -> String {
return L10n.tr("Localizable", "common_in_reply_to", String(describing: p1))
}
/// This Matrix ID can't be found, so the invite might not be received. /// This Matrix ID can't be found, so the invite might not be received.
public static var commonInviteUnknownProfile: String { return L10n.tr("Localizable", "common_invite_unknown_profile") } public static var commonInviteUnknownProfile: String { return L10n.tr("Localizable", "common_invite_unknown_profile") }
/// Leaving room /// Leaving room
@@ -226,6 +230,8 @@ public enum L10n {
} }
/// Privacy policy /// Privacy policy
public static var commonPrivacyPolicy: String { return L10n.tr("Localizable", "common_privacy_policy") } public static var commonPrivacyPolicy: String { return L10n.tr("Localizable", "common_privacy_policy") }
/// Reaction
public static var commonReaction: String { return L10n.tr("Localizable", "common_reaction") }
/// Reactions /// Reactions
public static var commonReactions: String { return L10n.tr("Localizable", "common_reactions") } public static var commonReactions: String { return L10n.tr("Localizable", "common_reactions") }
/// Refreshing… /// Refreshing…
@@ -137,6 +137,7 @@ struct TimelineReplyView: View {
Text(sender.displayName ?? sender.id) Text(sender.displayName ?? sender.id)
.font(.compound.bodySMSemibold) .font(.compound.bodySMSemibold)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.accessibilityLabel(L10n.commonInReplyTo(sender.displayName ?? sender.id))
Text(messagePreview) Text(messagePreview)
.font(.compound.bodyMD) .font(.compound.bodyMD)
@@ -79,14 +79,13 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
if shouldShowSenderDetails { if shouldShowSenderDetails {
HStack(alignment: .top, spacing: 4) { HStack(alignment: .top, spacing: 4) {
TimelineSenderAvatarView(timelineItem: timelineItem) TimelineSenderAvatarView(timelineItem: timelineItem)
.accessibilityHidden(true)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id) Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
.font(.compound.bodySMSemibold) .font(.compound.bodySMSemibold)
.foregroundColor(.compound.avatarColor(for: timelineItem.sender.id).foreground) .foregroundColor(.compound.avatarColor(for: timelineItem.sender.id).foreground)
.lineLimit(1) .lineLimit(1)
.padding(.vertical, senderNameVerticalPadding) .padding(.vertical, senderNameVerticalPadding)
} }
.accessibilityElement(children: .combine) .accessibilityHidden(true)
.onTapGesture { .onTapGesture {
context.send(viewAction: .tappedOnUser(userID: timelineItem.sender.id)) context.send(viewAction: .tappedOnUser(userID: timelineItem.sender.id))
} }
@@ -98,6 +97,12 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
// Figma overlaps reactions by 3 // Figma overlaps reactions by 3
VStack(alignment: alignment, spacing: -3) { VStack(alignment: alignment, spacing: -3) {
messageBubble messageBubble
.accessibilityRepresentation {
VStack {
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
messageBubble
}
}
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
if !timelineItem.properties.reactions.isEmpty { if !timelineItem.properties.reactions.isEmpty {
@@ -126,6 +131,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
} }
.swipeRightAction { .swipeRightAction {
Image(systemName: "arrowshape.turn.up.left") Image(systemName: "arrowshape.turn.up.left")
.accessibilityHidden(true)
} shouldStartAction: { } shouldStartAction: {
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
} action: { } action: {
@@ -122,6 +122,8 @@ struct TimelineReactionButton: View {
.longPressWithFeedback { .longPressWithFeedback {
showReactionSummary(reaction.key) showReactionSummary(reaction.key)
} }
.accessibilityHint(L10n.commonReaction)
.accessibilityAddTraits(reaction.isHighlighted ? .isSelected : [])
} }
var label: some View { var label: some View {
@@ -29,6 +29,8 @@ struct AudioRoomTimelineView: View {
} }
.padding(.vertical, 12) .padding(.vertical, 12)
.padding(.horizontal, 6) .padding(.horizontal, 6)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonAudio)
} }
} }
} }
@@ -29,6 +29,8 @@ struct FileRoomTimelineView: View {
} }
.padding(.vertical, 12) .padding(.vertical, 12)
.padding(.horizontal, 6) .padding(.horizontal, 6)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonFile)
} }
} }
} }
@@ -30,6 +30,8 @@ struct ImageRoomTimelineView: View {
} }
.frame(maxHeight: min(300, max(100, timelineItem.content.height ?? .infinity))) .frame(maxHeight: min(300, max(100, timelineItem.content.height ?? .infinity)))
.aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit) .aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonImage)
} }
} }
@@ -22,26 +22,41 @@ struct LocationRoomTimelineView: View {
var body: some View { var body: some View {
TimelineStyler(timelineItem: timelineItem) { TimelineStyler(timelineItem: timelineItem) {
if let geoURI = timelineItem.content.geoURI { mainContent
VStack(alignment: .leading, spacing: 0) { .accessibilityElement(children: .ignore)
descriptionView .accessibilityLabel(accessibilityLabel)
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading) }
}
MapLibreStaticMapView(geoURI: geoURI, mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) { @ViewBuilder
LocationMarkerView() private var mainContent: some View {
} if let geoURI = timelineItem.content.geoURI {
.frame(maxHeight: mapMaxHeight) VStack(alignment: .leading, spacing: 0) {
.aspectRatio(mapAspectRatio, contentMode: .fit) descriptionView
.clipped() .frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
MapLibreStaticMapView(geoURI: geoURI, mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
LocationMarkerView()
} }
} else { .frame(maxHeight: mapMaxHeight)
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle)) .aspectRatio(mapAspectRatio, contentMode: .fit)
.clipped()
} }
} else {
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
} }
} }
// MARK: - Private // MARK: - Private
private var accessibilityLabel: String {
if let description = timelineItem.content.description {
return "\(L10n.commonSharedLocation), \(description)"
}
return L10n.commonSharedLocation
}
@ViewBuilder @ViewBuilder
private var descriptionView: some View { private var descriptionView: some View {
if let description = timelineItem.content.description, !description.isEmpty { if let description = timelineItem.content.description, !description.isEmpty {
@@ -30,8 +30,9 @@ struct StickerRoomTimelineView: View {
} }
.frame(maxHeight: min(300, max(100, timelineItem.height ?? .infinity))) .frame(maxHeight: min(300, max(100, timelineItem.height ?? .infinity)))
.aspectRatio(timelineItem.aspectRatio, contentMode: .fit) .aspectRatio(timelineItem.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)")
} }
.accessibilityLabel(timelineItem.body)
} }
private var placeholder: some View { private var placeholder: some View {
@@ -26,6 +26,8 @@ struct VideoRoomTimelineView: View {
thumbnail thumbnail
.frame(maxHeight: min(300, max(100, timelineItem.content.height ?? .infinity))) .frame(maxHeight: min(300, max(100, timelineItem.content.height ?? .infinity)))
.aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit) .aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonVideo)
} }
} }
+1
View File
@@ -0,0 +1 @@
Various accessibility fixes: add labels on timeline media, hide swipe to reply button, add sender on all messages, improve replies and reactions