From 9e347c75b926fe31a216038398c548342d0b5661 Mon Sep 17 00:00:00 2001 From: Chris Kolbu Date: Wed, 29 Mar 2023 03:48:58 +1100 Subject: [PATCH] Timeline & Timeline detail accessibility uplift (#1323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve accessibility of StatusPollView Previously, this view did not provide the proper context to indicate that it represented a poll. Now, we’ve added - A container that will stay “Active poll” or “Poll results” when the cursor first hits one of the options; - A prefix to say “Option X of Y” before each option; - A Selected trait on the selected option(s), if present - Consolidating and adding an `.updatesFrequently` trait to the footer view with the countdown. * Add poll description in StatusRowView combinedAccessibilityLabel This largely duplicates the logic in `StatusPollView`. * Improve accessibility of media attachments Previously, the media attachments without alt text would not show up in the consolidated `StatusRowView`, nor would they be meaningfully explained on the status detail screen. Now, they are presented with their attachment type. * Change accessibilityRepresentation of AppAcountsSelectorView * Change Notifications tab title view accessibility representation to Menu Previously it would present as a button * Hide layout `Rectangle`s from accessibility * Consolidate `StatusRowDetailView` accessibility representation * Improve readability of Poll accessibility label * Ensure poll options don’t present as interactive when the poll is finished * Improve accessibility of StatusRowCardView Previously, it would present as four separate elements, including an image without a description, all interactive, none with an interactive trait. Now, it presents as a single element with the `.link` trait * Improve accessibility of StatusRowHeaderView Previously, it had no traits and no actions except inherited ones. Now it presents as a button, triggering its primary action. It also has custom actions corresponding to its context menu * Avoid applying the StatusRowView custom actions to every view when contained * Provide context for the application name * Add pauses to StatusRowView combinedAccessibilityLabel * Hide `TimelineView.scrollToTopView` from accessibility * Set appropriate font style on Notification header After the change the Text needed a `.headline` style to match the prior appearance. * Fix bug in accessibilityRepresentation of TimelineView nav bar title Previously, it would not display the proper label for .remoteLocal filter options. * Ensure that pop-up button nav bar titles are interactive * Ensure TextView responds to Environment.sizeCategory This resolves #1309 * Fix button --------- Co-authored-by: Thomas Ricouard --- .../Localization/be.lproj/Localizable.strings | 10 +++++ .../Localization/ca.lproj/Localizable.strings | 10 +++++ .../Localization/de.lproj/Localizable.strings | 11 +++++ .../en-GB.lproj/Localizable.strings | 10 +++++ .../Localization/en.lproj/Localizable.strings | 10 +++++ .../Localization/es.lproj/Localizable.strings | 10 +++++ .../Localization/eu.lproj/Localizable.strings | 10 +++++ .../Localization/fr.lproj/Localizable.strings | 10 +++++ .../Localization/it.lproj/Localizable.strings | 10 +++++ .../Localization/ja.lproj/Localizable.strings | 10 +++++ .../Localization/ko.lproj/Localizable.strings | 10 +++++ .../Localization/nb.lproj/Localizable.strings | 10 +++++ .../Localization/nl.lproj/Localizable.strings | 10 +++++ .../Localization/pl.lproj/Localizable.strings | 10 +++++ .../pt-BR.lproj/Localizable.strings | 10 +++++ .../Localization/tr.lproj/Localizable.strings | 10 +++++ .../Localization/uk.lproj/Localizable.strings | 10 +++++ .../zh-Hans.lproj/Localizable.strings | 10 +++++ .../zh-Hant.lproj/Localizable.strings | 10 +++++ .../Account/AccountDetailHeaderView.swift | 1 + .../Sources/Account/AccountDetailView.swift | 1 + .../AppAccount/AppAccountsSelectorView.swift | 7 ++- .../Detail/ConversationDetailView.swift | 1 + .../DesignSystem/Views/ThemePreviewView.swift | 1 + .../Sources/Models/MediaAttachement.swift | 16 +++++++ .../Notifications/NotificationsListView.swift | 22 ++++++++- .../Status/Detail/StatusDetailView.swift | 5 ++- .../Components/StatusEditorMediaView.swift | 1 + .../Editor/UITextView/Coordinator.swift | 3 ++ .../Editor/UITextView/Representable.swift | 2 + .../Status/Media/VideoPlayerView.swift | 1 + .../Sources/Status/Poll/StatusPollView.swift | 26 ++++++++++- .../Sources/Status/Row/StatusRowView.swift | 45 ++++++++++++++++--- .../Row/Subviews/StatusRowCardView.swift | 5 +++ .../Row/Subviews/StatusRowDetailView.swift | 31 ++++++++----- .../Row/Subviews/StatusRowHeaderView.swift | 12 ++++- .../Subviews/StatusRowMediaPreviewView.swift | 7 ++- .../Sources/Timeline/TimelineView.swift | 20 +++++++-- 38 files changed, 368 insertions(+), 30 deletions(-) diff --git a/IceCubesApp/Resources/Localization/be.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/be.lproj/Localizable.strings index c5467ee4..ebcd9725 100644 --- a/IceCubesApp/Resources/Localization/be.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/be.lproj/Localizable.strings @@ -569,6 +569,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Дадатковая інфармацыя"; diff --git a/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings index 44af98a2..7ab39e50 100644 --- a/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings @@ -563,6 +563,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Additional Info"; diff --git a/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings index a03d5038..efba0ca0 100644 --- a/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings @@ -559,6 +559,17 @@ "accessibility.image.alt-text-%@" = "Alternativer Bildtext: %@"; "accessibility.image.alt-text-more.label" = "Weiterer Alt.-Text verfügbar"; "accessibility.tabs.messages.unread.label" = "Ungelesen"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; + // MARK: Report "report.comment.placeholder" = "Zusätzliche Informationen"; diff --git a/IceCubesApp/Resources/Localization/en-GB.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/en-GB.lproj/Localizable.strings index 4df9fb13..043f85ae 100644 --- a/IceCubesApp/Resources/Localization/en-GB.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/en-GB.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Additional Info"; diff --git a/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings index 4d3a0e18..20c80242 100644 --- a/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings @@ -565,6 +565,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Additional Info"; diff --git a/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings index 14644e3a..fe55c8a2 100644 --- a/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings @@ -565,6 +565,16 @@ "accessibility.image.alt-text-%@" = "Texto alt de la imagen: %@"; "accessibility.image.alt-text-more.label" = "Hay más text alt disponible"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Información adicional"; diff --git a/IceCubesApp/Resources/Localization/eu.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/eu.lproj/Localizable.strings index b1817c70..11e5c85d 100644 --- a/IceCubesApp/Resources/Localization/eu.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/eu.lproj/Localizable.strings @@ -553,6 +553,16 @@ "accessibility.image.alt-text-%@" = "Irudiaren deskribapena: %@"; "accessibility.image.alt-text-more.label" = "Deskribapen testu gehiago dago"; "accessibility.tabs.messages.unread.label" = "Irakurri gabe"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Informazio gehigarria"; diff --git a/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings index b48b56ca..7cdfa322 100644 --- a/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings @@ -560,6 +560,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Information supplémentaire"; diff --git a/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings index 9e099d77..4c99133c 100644 --- a/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Informazioni aggiuntive"; diff --git a/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings index 9be37646..9e0eebb9 100644 --- a/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "画像の代替テキスト: %@"; "accessibility.image.alt-text-more.label" = "より多くの代替テキストを利用できます"; "accessibility.tabs.messages.unread.label" = "未読"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "追加情報"; diff --git a/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings index 9762d4be..decdb08c 100644 --- a/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings @@ -566,6 +566,16 @@ "accessibility.image.alt-text-%@" = "미디어 설명: %@"; "accessibility.image.alt-text-more.label" = "더 많은 미디어 설명 사용 가능"; /* 추가 확인 필요 */ "accessibility.tabs.messages.unread.label" = "읽지 않음"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "추가 정보"; diff --git a/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings index a197c699..99e51db8 100644 --- a/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Additional Info"; diff --git a/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings index 09a105e2..dc0f2b2d 100644 --- a/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings @@ -561,6 +561,16 @@ "accessibility.image.alt-text-%@" = "Tekst voor afbeedling: %@"; "accessibility.image.alt-text-more.label" = "Meer tekst beschikbaar"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Aanvullende informatie"; diff --git a/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings index 765b81eb..c4e11d41 100644 --- a/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings @@ -555,6 +555,16 @@ "accessibility.image.alt-text-%@" = "Tekst alternatywny obrazka: %@"; "accessibility.image.alt-text-more.label" = "Dostępna jest większa ilość tekstu alternatywnego"; "accessibility.tabs.messages.unread.label" = "Nieprzeczytane"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Informacja dodatkowa"; diff --git a/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings index 778aa479..a116e9cf 100644 --- a/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "Texto alternativo da imagem: %@"; "accessibility.image.alt-text-more.label" = "Mais texto alternativo disponível"; "accessibility.tabs.messages.unread.label" = "Não lido"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Informação Adicional"; diff --git a/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings index 939b6d40..9249fcbf 100644 --- a/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Additional Info"; diff --git a/IceCubesApp/Resources/Localization/uk.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/uk.lproj/Localizable.strings index 71777cd2..2600ad16 100644 --- a/IceCubesApp/Resources/Localization/uk.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/uk.lproj/Localizable.strings @@ -565,6 +565,16 @@ "accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.tabs.messages.unread.label" = "Unread"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "Додаткова інформація"; diff --git a/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings index ac0e40f4..092e44b9 100644 --- a/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings @@ -564,6 +564,16 @@ "accessibility.image.alt-text-%@" = "图片描述文本:%@"; "accessibility.image.alt-text-more.label" = "更多描述文本可用"; "accessibility.tabs.messages.unread.label" = "未读"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "附加信息"; diff --git a/IceCubesApp/Resources/Localization/zh-Hant.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/zh-Hant.lproj/Localizable.strings index 35019891..452589c8 100644 --- a/IceCubesApp/Resources/Localization/zh-Hant.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/zh-Hant.lproj/Localizable.strings @@ -565,6 +565,16 @@ "accessibility.image.alt-text-%@" = "圖片描述文字:%@"; "accessibility.image.alt-text-more.label" = "更多圖片描述文字"; "accessibility.tabs.messages.unread.label" = "未讀"; +"accessibility.status.poll.option-prefix-%lld-of-%lld" = "Option %lld of %lld"; +"accessibility.status.poll.active.label" = "Active poll"; +"accessibility.status.poll.finished.label" = "Poll results"; +"accessibility.status.poll.selected.label" = "Selected"; +"accessibility.media.supported-type.image.label" = "Image"; +"accessibility.media.supported-type.gifv.label" = "Animated Gif"; +"accessibility.media.supported-type.video.label" = "Video"; +"accessibility.media.supported-type.audio.label" = "Audio"; +"accessibility.status.contains-media.label-%@" = "Contains %@"; +"accessibility.status.application.label" = "App"; // MARK: Report "report.comment.placeholder" = "附加資訊"; diff --git a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift index 73a0fc4e..571a893d 100644 --- a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift +++ b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift @@ -50,6 +50,7 @@ struct AccountDetailHeaderView: View { Rectangle() .foregroundColor(theme.secondaryBackgroundColor) .frame(height: Constants.headerHeight) + .accessibilityHidden(true) } else { LazyImage(url: account.header) { state in if let image = state.image { diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index b0063137..d3ac5069 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -299,6 +299,7 @@ public struct AccountDetailView: View { .frame(height: 12) .listRowInsets(.init()) .listRowSeparator(.hidden) + .accessibilityHidden(true) } } diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift index cf7b5483..dd44463a 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift @@ -67,6 +67,11 @@ public struct AppAccountsSelectorView: View { .onAppear { refreshAccounts() } + .accessibilityRepresentation { + Menu("accessibility.app-account.selector.accounts") {} + .accessibilityHint("accessibility.app-account.selector.accounts.hint") + .accessibilityRemoveTraits(.isButton) + } } @ViewBuilder @@ -85,8 +90,6 @@ public struct AppAccountsSelectorView: View { .frame(width: 9, height: 9) } } - .accessibilityLabel("accessibility.app-account.selector.accounts") - .accessibilityHint("accessibility.app-account.selector.accounts.hint") } private var accountBackgroundColor: Color { diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift index 4bbd28e1..cdb5b75b 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift @@ -110,6 +110,7 @@ public struct ConversationDetailView: View { .fill(Color.clear) .frame(height: 40) .id(Constants.bottomAnchor) + .accessibilityHidden(true) } private var inputTextView: some View { diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/ThemePreviewView.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/ThemePreviewView.swift index 1a85fb2e..61433928 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Views/ThemePreviewView.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/ThemePreviewView.swift @@ -38,6 +38,7 @@ struct ThemeBoxView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .cornerRadius(4) .shadow(radius: 2, x: 2, y: 4) + .accessibilityHidden(true) VStack(spacing: gutterSpace) { Text(color.name.rawValue) diff --git a/Packages/Models/Sources/Models/MediaAttachement.swift b/Packages/Models/Sources/Models/MediaAttachement.swift index b9065d26..fbc46dd1 100644 --- a/Packages/Models/Sources/Models/MediaAttachement.swift +++ b/Packages/Models/Sources/Models/MediaAttachement.swift @@ -24,6 +24,22 @@ public struct MediaAttachment: Codable, Identifiable, Hashable, Equatable { SupportedType(rawValue: type) } + public var localizedTypeDescription: String? { + if let supportedType { + switch supportedType { + case .image: + return NSLocalizedString("accessibility.media.supported-type.image.label", bundle: .main, comment: "A localized description of SupportedType.image") + case .gifv: + return NSLocalizedString("accessibility.media.supported-type.gifv.label", bundle: .main, comment: "A localized description of SupportedType.gifv") + case .video: + return NSLocalizedString("accessibility.media.supported-type.video.label", bundle: .main, comment: "A localized description of SupportedType.video") + case .audio: + return NSLocalizedString("accessibility.media.supported-type.audio.label", bundle: .main, comment: "A localized description of SupportedType.audio") + } + } + return nil + } + public let url: URL? public let previewUrl: URL? public let description: String? diff --git a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift index 235124db..e6ecf75e 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift @@ -28,8 +28,25 @@ public struct NotificationsListView: View { .id(account.account?.id) .environment(\.defaultMinListRowHeight, 1) .listStyle(.plain) - .navigationTitle(lockedType?.menuTitle() ?? viewModel.selectedType?.menuTitle() ?? "notifications.navigation-title") - .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + let title = lockedType?.menuTitle() ?? viewModel.selectedType?.menuTitle() ?? "notifications.navigation-title" + if lockedType == nil { + Text(title) + .font(.headline) + .accessibilityRepresentation { + Menu(title) {} + } + .accessibilityAddTraits(.isHeader) + .accessibilityRemoveTraits(.isButton) + .accessibilityRespondsToUserInteraction(true) + } else { + Text(title) + .font(.headline) + .accessibilityAddTraits(.isHeader) + } + } + } .toolbar { if lockedType == nil { ToolbarTitleMenu { @@ -53,6 +70,7 @@ public struct NotificationsListView: View { } } } + .navigationBarTitleDisplayMode(.inline) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) .task { diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift index 526bdef3..7452aeeb 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift @@ -11,8 +11,9 @@ public struct StatusDetailView: View { @EnvironmentObject private var watcher: StreamWatcher @EnvironmentObject private var client: Client @EnvironmentObject private var routerPath: RouterPath - @Environment(\.openURL) private var openURL + @StateObject private var viewModel: StatusDetailViewModel + @State private var isLoaded: Bool = false @State private var statusHeight: CGFloat = 0 @@ -54,6 +55,7 @@ public struct StatusDetailView: View { .listRowSeparator(.hidden) .listRowBackground(theme.secondaryBackgroundColor) .listRowInsets(.init()) + .accessibilityHidden(true) case .error: errorView @@ -119,6 +121,7 @@ public struct StatusDetailView: View { Rectangle() .fill(theme.tintColor) .frame(width: 2) + .accessibilityHidden(true) Spacer(minLength: 8) } if self.viewModel.statusId == status.id { diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift index 7bddbda2..89315f40 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift @@ -158,5 +158,6 @@ struct StatusEditorMediaView: View { Rectangle() .foregroundColor(theme.secondaryBackgroundColor) .frame(width: 150, height: 150) + .accessibilityHidden(true) } } diff --git a/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift b/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift index a2986bee..75beaecb 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/Coordinator.swift @@ -7,6 +7,7 @@ extension TextView.Representable { private var originalText: NSMutableAttributedString = .init() private var text: Binding + private var sizeCategory: ContentSizeCategory private var calculatedHeight: Binding var didBecomeFirstResponder = false @@ -15,6 +16,7 @@ extension TextView.Representable { init(text: Binding, calculatedHeight: Binding, + sizeCategory: ContentSizeCategory, getTextView: ((UITextView) -> Void)?) { textView = UIKitTextView() @@ -26,6 +28,7 @@ extension TextView.Representable { self.text = text self.calculatedHeight = calculatedHeight + self.sizeCategory = sizeCategory self.getTextView = getTextView super.init() diff --git a/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift b/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift index 106878d2..08c1e427 100644 --- a/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift +++ b/Packages/Status/Sources/Status/Editor/UITextView/Representable.swift @@ -4,6 +4,7 @@ extension TextView { struct Representable: UIViewRepresentable { @Binding var text: NSMutableAttributedString @Binding var calculatedHeight: CGFloat + @Environment(\.sizeCategory) var sizeCategory let keyboard: UIKeyboardType var getTextView: ((UITextView) -> Void)? @@ -24,6 +25,7 @@ extension TextView { Coordinator( text: $text, calculatedHeight: $calculatedHeight, + sizeCategory: sizeCategory, getTextView: getTextView ) } diff --git a/Packages/Status/Sources/Status/Media/VideoPlayerView.swift b/Packages/Status/Sources/Status/Media/VideoPlayerView.swift index add995ae..7489a211 100644 --- a/Packages/Status/Sources/Status/Media/VideoPlayerView.swift +++ b/Packages/Status/Sources/Status/Media/VideoPlayerView.swift @@ -55,6 +55,7 @@ struct VideoPlayerView: View { var body: some View { ZStack { VideoPlayer(player: viewModel.player) + .accessibilityAddTraits(.startsMediaSession) if !preferences.autoPlayVideo { Image(systemName: "play.fill") diff --git a/Packages/Status/Sources/Status/Poll/StatusPollView.swift b/Packages/Status/Sources/Status/Poll/StatusPollView.swift index 560ae436..20ba01dc 100644 --- a/Packages/Status/Sources/Status/Poll/StatusPollView.swift +++ b/Packages/Status/Sources/Status/Poll/StatusPollView.swift @@ -70,11 +70,12 @@ public struct StatusPollView: View { } public var body: some View { + let isInteractive = viewModel.poll.expired == false && (viewModel.poll.voted ?? true) == false VStack(alignment: .leading) { - ForEach(viewModel.poll.options) { option in + ForEach(Array(viewModel.poll.options.enumerated()), id: \.element.id) { index, option in HStack { makeBarView(for: option, buttonImage: buttonImage(option: option)) - .disabled(viewModel.poll.expired || (viewModel.poll.voted ?? false)) + .disabled(isInteractive == false) if viewModel.showResults || status.account.id == currentAccount.account?.id { Spacer() // Make sure they're all the same width using a ZStack with 100% hiding behind the @@ -88,6 +89,12 @@ public struct StatusPollView: View { } } } + .accessibilityElement(children: .combine) + .accessibilityLabel(combinedAccessibilityLabel(for: option, index: index)) + .accessibilityRespondsToUserInteraction(isInteractive) + .accessibilityAddTraits(isSelected(option: option) ? .isSelected : []) + .accessibilityAddTraits(isInteractive ? [] : .isStaticText) + .accessibilityRemoveTraits(isInteractive ? [] : .isButton) } if !viewModel.poll.expired, !(viewModel.poll.voted ?? false), !viewModel.votes.isEmpty { Button("status.poll.send") { @@ -108,6 +115,18 @@ public struct StatusPollView: View { await viewModel.fetchPoll() } } + .accessibilityElement(children: .contain) + .accessibilityLabel(viewModel.poll.expired ? "accessibility.status.poll.finished.label" : "accessibility.status.poll.active.label") + + } + + func combinedAccessibilityLabel(for option: Poll.Option, index: Int) -> Text { + let showPercentage = viewModel.poll.expired || viewModel.poll.voted ?? false + return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") + + Text(", ") + + Text(option.title) + + Text(showPercentage ? ", \(percentForOption(option: option))%" : "") + } private var footerView: some View { @@ -118,6 +137,7 @@ public struct StatusPollView: View { Text("status.poll.n-votes \(viewModel.poll.votesCount)") } Text(" ⸱ ") + .accessibilityHidden(true) if viewModel.poll.expired { Text("status.poll.closed") } else if let date = viewModel.poll.expiresAt.value?.asDate { @@ -127,6 +147,8 @@ public struct StatusPollView: View { } .font(.scaledFootnote) .foregroundColor(.gray) + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.updatesFrequently) } @ViewBuilder diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index e4598f55..51c75318 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -67,6 +67,11 @@ public struct StatusRowView: View { .onTapGesture { viewModel.navigateToDetail() } + .accessibilityActions { + if viewModel.isFocused, viewModel.showActions { + accessibilityActions + } + } } if viewModel.showActions, viewModel.isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode { StatusRowActionsView(viewModel: viewModel) @@ -118,7 +123,7 @@ public struct StatusRowView: View { viewModel.navigateToDetail() } .accessibilityActions { - if viewModel.showActions { + if viewModel.isFocused == false, viewModel.showActions { accessibilityActions } } @@ -272,8 +277,9 @@ private struct CombinedAccessibilityLabel { Text(hasSpoiler ? "status.editor.spoiler" : "" - ) + - imageAltText() + Text(", ") + + ) + Text(", ") + + pollText() + + imageAltText() + Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") + Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") + Text("status.summary.n-boosts \(viewModel.finalStatus.reblogsCount)") + Text(", ") + @@ -308,11 +314,40 @@ private struct CombinedAccessibilityLabel { .compactMap(\.description) if descriptions.count == 1 { - return Text("accessibility.image.alt-text-\(descriptions[0])") + return Text("accessibility.image.alt-text-\(descriptions[0])") + Text(", ") } else if descriptions.count > 1 { - return Text("accessibility.image.alt-text-\(descriptions[0])") + Text(", ") + Text("accessibility.image.alt-text-more.label") + return Text("accessibility.image.alt-text-\(descriptions[0])") + Text(", ") + Text("accessibility.image.alt-text-more.label") + Text(", ") + } else if viewModel.finalStatus.mediaAttachments.isEmpty == false { + let differentTypes = Set(viewModel.finalStatus.mediaAttachments.compactMap(\.localizedTypeDescription)).sorted() + return Text("accessibility.status.contains-media.label-\(ListFormatter.localizedString(byJoining: differentTypes))") + Text(", ") } else { return Text("") } } + + func pollText() -> Text { + if let poll = viewModel.finalStatus.poll { + let showPercentage = poll.expired || poll.voted ?? false + let title: LocalizedStringKey = poll.expired + ? "accessibility.status.poll.finished.label" + : "accessibility.status.poll.active.label" + + return poll.options.enumerated().reduce(into: Text(title)) { text, pair in + let (index, option) = pair + let selected = poll.ownVotes?.contains(index) ?? false + let percentage = poll.safeVotersCount > 0 + ? Int(round(Double(option.votesCount) / Double(poll.safeVotersCount) * 100)) + : 0 + + text = text + + Text(selected ? "accessibility.status.poll.selected.label" : "") + + Text(", ") + + Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(poll.options.count)") + + Text(", ") + + Text(option.title) + + Text(showPercentage ? ", \(percentage)%. " : ". ") + } + } + return Text("") + } } diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift index d8c78c9b..36fb2a5a 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowCardView.swift @@ -60,6 +60,8 @@ public struct StatusRowCardView: View { } } .processors(processors) + // This image is decorative + .accessibilityHidden(true) } .frame(height: imageHeight) } @@ -107,6 +109,9 @@ public struct StatusRowCardView: View { Label("status.card.copy", systemImage: "doc.on.doc") } } + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.isLink) + .accessibilityRemoveTraits(.isStaticText) } } } diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowDetailView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowDetailView.swift index b34bc802..8914666c 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowDetailView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowDetailView.swift @@ -14,19 +14,28 @@ struct StatusRowDetailView: View { Group { Divider() HStack { - Text(viewModel.status.createdAt.asDate, style: .date) + - Text("status.summary.at-time") + - Text(viewModel.status.createdAt.asDate, style: .time) + - Text(" ·") - Image(systemName: viewModel.status.visibility.iconName) + Group { + Text(viewModel.status.createdAt.asDate, style: .date) + + Text("status.summary.at-time") + + Text(viewModel.status.createdAt.asDate, style: .time) + + Text(" ·") + Image(systemName: viewModel.status.visibility.iconName) + .accessibilityHidden(true) + }.accessibilityElement(children: .combine) Spacer() - Text(viewModel.status.application?.name ?? "") - .underline() - .onTapGesture { - if let url = viewModel.status.application?.website { - openURL(url) - } + if let name = viewModel.status.application?.name, let url = viewModel.status.application?.website { + Button { + openURL(url) + } label: { + Text(name) + .underline() } + .buttonStyle(.plain) + .accessibilityLabel("accessibility.status.application.label") + .accessibilityValue(name) + .accessibilityAddTraits(.isLink) + .accessibilityRemoveTraits(.isButton) + } } .font(.scaledCaption) .foregroundColor(.gray) diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift index da4e106c..e365c83c 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift @@ -23,8 +23,16 @@ struct StatusRowHeaderView: View { contextMenuButton } } - .accessibilityElement() - .accessibilityLabel(Text("\(viewModel.finalStatus.account.safeDisplayName)")) + .accessibilityElement(children: .combine) + .accessibilityLabel(Text("\(viewModel.finalStatus.account.safeDisplayName)") + Text(", ") + Text(viewModel.finalStatus.createdAt.relativeFormatted)) + .accessibilityAction { + viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account) + } + .accessibilityActions { + if viewModel.isFocused { + StatusRowContextMenu(viewModel: viewModel) + } + } } @ViewBuilder diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift index e354d4a8..1abedd06 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift @@ -254,11 +254,13 @@ public struct StatusRowMediaPreviewView: View { .cornerRadius(4) } } + .accessibilityAddTraits(.isImage) case .gifv, .video, .audio: if let url = attachment.url { VideoPlayerView(viewModel: .init(url: url)) .frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width) .frame(height: imageMaxHeight) + .accessibilityAddTraits(.startsMediaSession) } } } @@ -274,7 +276,7 @@ public struct StatusRowMediaPreviewView: View { } .accessibilityElement(children: .combine) .modifier(ConditionalAccessibilityLabelAltTextModifier(attachment: attachment)) - .accessibilityAddTraits([.isButton, .isImage]) + .accessibilityAddTraits(.isButton) } } @@ -343,6 +345,9 @@ private struct ConditionalAccessibilityLabelAltTextModifier: ViewModifier { if let altText = attachment.description { content .accessibilityLabel("accessibility.image.alt-text-\(altText)") + } else if let typeDescription = attachment.localizedTypeDescription { + content + .accessibilityLabel(typeDescription) } else { content } diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index 07c5908d..5bf00dfd 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -101,14 +101,25 @@ public struct TimelineView: View { } } .accessibilityRepresentation { - if canFilterTimeline { - Menu(timeline.localizedTitle()) {} - } else { - Text(timeline.localizedTitle()) + switch timeline { + case let .remoteLocal(_, filter): + if canFilterTimeline { + Menu(filter.localizedTitle()) {} + } else { + Text(filter.localizedTitle()) + } + default: + if canFilterTimeline { + Menu(timeline.localizedTitle()) {} + } else { + Text(timeline.localizedTitle()) + } + } } .accessibilityAddTraits(.isHeader) .accessibilityRemoveTraits(.isButton) + .accessibilityRespondsToUserInteraction(canFilterTimeline) } } .navigationBarTitleDisplayMode(.inline) @@ -217,5 +228,6 @@ public struct TimelineView: View { .onDisappear { viewModel.scrollToTopVisible = false } + .accessibilityHidden(true) } }