Timeline & Timeline detail accessibility uplift (#1323)
* 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 <ricouard77@gmail.com>
This commit is contained in:
parent
59af600945
commit
9e347c75b9
|
@ -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" = "Дадатковая інфармацыя";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "追加情報";
|
||||
|
|
|
@ -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" = "추가 정보";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "Додаткова інформація";
|
||||
|
|
|
@ -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" = "附加信息";
|
||||
|
|
|
@ -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" = "附加資訊";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -299,6 +299,7 @@ public struct AccountDetailView: View {
|
|||
.frame(height: 12)
|
||||
.listRowInsets(.init())
|
||||
.listRowSeparator(.hidden)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -110,6 +110,7 @@ public struct ConversationDetailView: View {
|
|||
.fill(Color.clear)
|
||||
.frame(height: 40)
|
||||
.id(Constants.bottomAnchor)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
|
||||
private var inputTextView: some View {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -158,5 +158,6 @@ struct StatusEditorMediaView: View {
|
|||
Rectangle()
|
||||
.foregroundColor(theme.secondaryBackgroundColor)
|
||||
.frame(width: 150, height: 150)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ extension TextView.Representable {
|
|||
|
||||
private var originalText: NSMutableAttributedString = .init()
|
||||
private var text: Binding<NSMutableAttributedString>
|
||||
private var sizeCategory: ContentSizeCategory
|
||||
private var calculatedHeight: Binding<CGFloat>
|
||||
|
||||
var didBecomeFirstResponder = false
|
||||
|
@ -15,6 +16,7 @@ extension TextView.Representable {
|
|||
|
||||
init(text: Binding<NSMutableAttributedString>,
|
||||
calculatedHeight: Binding<CGFloat>,
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,18 +14,27 @@ struct StatusRowDetailView: View {
|
|||
Group {
|
||||
Divider()
|
||||
HStack {
|
||||
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 {
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -101,14 +101,25 @@ public struct TimelineView: View {
|
|||
}
|
||||
}
|
||||
.accessibilityRepresentation {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue