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
@@ -137,6 +137,7 @@ struct TimelineReplyView: View {
Text(sender.displayName ?? sender.id)
.font(.compound.bodySMSemibold)
.foregroundColor(.compound.textPrimary)
.accessibilityLabel(L10n.commonInReplyTo(sender.displayName ?? sender.id))
Text(messagePreview)
.font(.compound.bodyMD)
@@ -79,14 +79,13 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
if shouldShowSenderDetails {
HStack(alignment: .top, spacing: 4) {
TimelineSenderAvatarView(timelineItem: timelineItem)
.accessibilityHidden(true)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
.font(.compound.bodySMSemibold)
.foregroundColor(.compound.avatarColor(for: timelineItem.sender.id).foreground)
.lineLimit(1)
.padding(.vertical, senderNameVerticalPadding)
}
.accessibilityElement(children: .combine)
.accessibilityHidden(true)
.onTapGesture {
context.send(viewAction: .tappedOnUser(userID: timelineItem.sender.id))
}
@@ -98,6 +97,12 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
// Figma overlaps reactions by 3
VStack(alignment: alignment, spacing: -3) {
messageBubble
.accessibilityRepresentation {
VStack {
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
messageBubble
}
}
.accessibilityElement(children: .combine)
if !timelineItem.properties.reactions.isEmpty {
@@ -126,6 +131,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
}
.swipeRightAction {
Image(systemName: "arrowshape.turn.up.left")
.accessibilityHidden(true)
} shouldStartAction: {
context.viewState.timelineItemMenuActionProvider?(timelineItem.id)?.canReply ?? false
} action: {
@@ -122,6 +122,8 @@ struct TimelineReactionButton: View {
.longPressWithFeedback {
showReactionSummary(reaction.key)
}
.accessibilityHint(L10n.commonReaction)
.accessibilityAddTraits(reaction.isHighlighted ? .isSelected : [])
}
var label: some View {
@@ -29,6 +29,8 @@ struct AudioRoomTimelineView: View {
}
.padding(.vertical, 12)
.padding(.horizontal, 6)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonAudio)
}
}
}
@@ -29,6 +29,8 @@ struct FileRoomTimelineView: View {
}
.padding(.vertical, 12)
.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)))
.aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonImage)
}
}
@@ -22,25 +22,40 @@ struct LocationRoomTimelineView: View {
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
if let geoURI = timelineItem.content.geoURI {
VStack(alignment: .leading, spacing: 0) {
descriptionView
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
MapLibreStaticMapView(geoURI: geoURI, mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
LocationMarkerView()
}
.frame(maxHeight: mapMaxHeight)
.aspectRatio(mapAspectRatio, contentMode: .fit)
.clipped()
mainContent
.accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityLabel)
}
}
@ViewBuilder
private var mainContent: some View {
if let geoURI = timelineItem.content.geoURI {
VStack(alignment: .leading, spacing: 0) {
descriptionView
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
MapLibreStaticMapView(geoURI: geoURI, mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
LocationMarkerView()
}
} else {
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
.frame(maxHeight: mapMaxHeight)
.aspectRatio(mapAspectRatio, contentMode: .fit)
.clipped()
}
} else {
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
}
}
// MARK: - Private
private var accessibilityLabel: String {
if let description = timelineItem.content.description {
return "\(L10n.commonSharedLocation), \(description)"
}
return L10n.commonSharedLocation
}
@ViewBuilder
private var descriptionView: some View {
@@ -30,8 +30,9 @@ struct StickerRoomTimelineView: View {
}
.frame(maxHeight: min(300, max(100, timelineItem.height ?? .infinity)))
.aspectRatio(timelineItem.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)")
}
.accessibilityLabel(timelineItem.body)
}
private var placeholder: some View {
@@ -26,6 +26,8 @@ struct VideoRoomTimelineView: View {
thumbnail
.frame(maxHeight: min(300, max(100, timelineItem.content.height ?? .infinity)))
.aspectRatio(timelineItem.content.aspectRatio, contentMode: .fit)
.accessibilityElement(children: .ignore)
.accessibilityLabel(L10n.commonVideo)
}
}