mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
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:
@@ -182,6 +182,10 @@ public enum L10n {
|
||||
public static var commonGif: String { return L10n.tr("Localizable", "common_gif") }
|
||||
/// 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.
|
||||
public static var commonInviteUnknownProfile: String { return L10n.tr("Localizable", "common_invite_unknown_profile") }
|
||||
/// Leaving room
|
||||
@@ -226,6 +230,8 @@ public enum L10n {
|
||||
}
|
||||
/// 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
|
||||
public static var commonReactions: String { return L10n.tr("Localizable", "common_reactions") }
|
||||
/// Refreshing…
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user