Accessibility tweaks + Notifications and Messages tab uplift (#1292)

* Improve StatusRowView accessibility actions

Previously, there was no way to interact with links and hashtags.

Now, these are added to the Actions rotor

* Hide `topPaddingView`s from accessibility

* Fix accessible header rendering in non-filterable TimelineViews

Previously, all navigation title views were assumed to be popup buttons.

Now, we only change the representation for timelines that are filterable.

* Combine tagHeaderView text elements

Previously, these were two separate items

* Prefer shorter Quote action label

* Improve accessibility of StatusEmbeddedView

Previously, this element would be three different ones, and include all the actions on the `StatusRowView` proper. Now, it presents as one element with no actions.

* Add haptics to StatusRowView accessibility actions

* Improve accessibility of ConversationsListRow

This commit adds:
- A combined representation of the component views
- “Unread” as the first part of the label (if this is the case)
- All relevant actions as custom actions
- Reply as magic tap

* Remove StatusRowView accessibilityActions if viewModel.showActions is false

* Hide media attachments from accessibility if the view is not focused

* Combine NotificationRowView accessibility elements; add user actions

Previously, there was no real way to interact with these notifications.

Now, the notifications that show the actions row have the appropriate StatusRowView-derived actions, and new followers notifications have more actions that let you see each user’s profile.

* Prefer @Environment’s `accessibilityEnabled` over `isVoiceOverRunning`

This way we can cater for Voice Control, Full Keyboard Access and Switch Control as well.

---------

Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
Chris Kolbu 2023-03-24 17:52:29 +11:00 committed by GitHub
parent 9746eb7674
commit b2f594f174
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 323 additions and 70 deletions

View file

@ -31,9 +31,9 @@ extension View {
case let .conversationDetail(conversation): case let .conversationDetail(conversation):
ConversationDetailView(conversation: conversation) ConversationDetailView(conversation: conversation)
case let .hashTag(tag, accountId): case let .hashTag(tag, accountId):
TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)), scrollToTopSignal: .constant(0)) TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)), scrollToTopSignal: .constant(0), canFilterTimeline: false)
case let .list(list): case let .list(list):
TimelineView(timeline: .constant(.list(list: list)), scrollToTopSignal: .constant(0)) TimelineView(timeline: .constant(.list(list: list)), scrollToTopSignal: .constant(0), canFilterTimeline: false)
case let .following(id): case let .following(id):
AccountsListView(mode: .following(accountId: id)) AccountsListView(mode: .following(accountId: id))
case let .followers(id): case let .followers(id):
@ -45,7 +45,7 @@ extension View {
case let .accountsList(accounts): case let .accountsList(accounts):
AccountsListView(mode: .accountsList(accounts: accounts)) AccountsListView(mode: .accountsList(accounts: accounts))
case .trendingTimeline: case .trendingTimeline:
TimelineView(timeline: .constant(.trending), scrollToTopSignal: .constant(0)) TimelineView(timeline: .constant(.trending), scrollToTopSignal: .constant(0), canFilterTimeline: false)
case let .tagsList(tags): case let .tagsList(tags):
TagsListView(tags: tags) TagsListView(tags: tags)
} }

View file

@ -32,7 +32,7 @@ struct TimelineTab: View {
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
TimelineView(timeline: $timeline, scrollToTopSignal: $scrollToTopSignal) TimelineView(timeline: $timeline, scrollToTopSignal: $scrollToTopSignal, canFilterTimeline: canFilterTimeline)
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar { .toolbar {

View file

@ -529,6 +529,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Уліковыя запісы"; "accessibility.app-account.selector.accounts" = "Уліковыя запісы";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -562,6 +564,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Дадатковая інфармацыя"; "report.comment.placeholder" = "Дадатковая інфармацыя";

View file

@ -523,6 +523,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Accounts"; "accessibility.app-account.selector.accounts" = "Accounts";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -556,6 +558,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Additional Info"; "report.comment.placeholder" = "Additional Info";

View file

@ -519,6 +519,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Erzeugen"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Erzeugen";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld neue Beiträge"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld neue Beiträge";
"accessibility.tabs.timeline.unread-posts.hint" = "Bewegt durch die Timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Bewegt durch die Timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Konten"; "accessibility.app-account.selector.accounts" = "Konten";
"accessibility.app-account.selector.accounts.hint" = "Öffnet die Optionen."; "accessibility.app-account.selector.accounts.hint" = "Öffnet die Optionen.";
"accessibility.tabs.profile.options.label" = "Optionen"; "accessibility.tabs.profile.options.label" = "Optionen";
@ -552,6 +554,7 @@
"accessibility.status.a-replied-to-%@" = "%@ antwortete auf"; "accessibility.status.a-replied-to-%@" = "%@ antwortete auf";
"accessibility.image.alt-text-%@" = "Alternativer Bildtext: %@"; "accessibility.image.alt-text-%@" = "Alternativer Bildtext: %@";
"accessibility.image.alt-text-more.label" = "Weiterer Alt.-Text verfügbar"; "accessibility.image.alt-text-more.label" = "Weiterer Alt.-Text verfügbar";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Zusätzliche Informationen"; "report.comment.placeholder" = "Zusätzliche Informationen";

View file

@ -523,6 +523,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.editor.button.characters-remaining" = "Characters remaining"; "accessibility.editor.button.characters-remaining" = "Characters remaining";
"accessibility.editor.privacy.label" = "Visibility"; "accessibility.editor.privacy.label" = "Visibility";
"accessibility.editor.privacy.hint" = "Changes post audience."; "accessibility.editor.privacy.hint" = "Changes post audience.";
@ -559,6 +561,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Additional Info"; "report.comment.placeholder" = "Additional Info";

View file

@ -525,6 +525,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Accounts"; "accessibility.app-account.selector.accounts" = "Accounts";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -558,6 +560,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Additional Info"; "report.comment.placeholder" = "Additional Info";

View file

@ -525,6 +525,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Crear"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Crear";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld publicaciones nuevas"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld publicaciones nuevas";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Cuentas"; "accessibility.app-account.selector.accounts" = "Cuentas";
"accessibility.app-account.selector.accounts.hint" = "Abrir opciones."; "accessibility.app-account.selector.accounts.hint" = "Abrir opciones.";
"accessibility.tabs.profile.options.label" = "Opciones"; "accessibility.tabs.profile.options.label" = "Opciones";
@ -558,6 +560,7 @@
"accessibility.status.a-replied-to-%@" = "%@ respondió a"; "accessibility.status.a-replied-to-%@" = "%@ respondió a";
"accessibility.image.alt-text-%@" = "Texto alt de la imagen: %@"; "accessibility.image.alt-text-%@" = "Texto alt de la imagen: %@";
"accessibility.image.alt-text-more.label" = "Hay más text alt disponible"; "accessibility.image.alt-text-more.label" = "Hay más text alt disponible";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Información adicional"; "report.comment.placeholder" = "Información adicional";

View file

@ -513,6 +513,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Sortu"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Sortu";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld bidalketa berri"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld bidalketa berri";
"accessibility.tabs.timeline.unread-posts.hint" = "Denbora-lerroa korritzen du."; "accessibility.tabs.timeline.unread-posts.hint" = "Denbora-lerroa korritzen du.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Kontuak"; "accessibility.app-account.selector.accounts" = "Kontuak";
"accessibility.app-account.selector.accounts.hint" = "Aukeren orria irekitzen du."; "accessibility.app-account.selector.accounts.hint" = "Aukeren orria irekitzen du.";
"accessibility.tabs.profile.options.label" = "Aukerak"; "accessibility.tabs.profile.options.label" = "Aukerak";
@ -546,6 +548,7 @@
"accessibility.status.a-replied-to-%@" = "%@(e)k honi erantzun dio:"; "accessibility.status.a-replied-to-%@" = "%@(e)k honi erantzun dio:";
"accessibility.image.alt-text-%@" = "Irudiaren deskribapena: %@"; "accessibility.image.alt-text-%@" = "Irudiaren deskribapena: %@";
"accessibility.image.alt-text-more.label" = "Deskribapen testu gehiago dago"; "accessibility.image.alt-text-more.label" = "Deskribapen testu gehiago dago";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Informazio gehigarria"; "report.comment.placeholder" = "Informazio gehigarria";

View file

@ -520,6 +520,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Comptes"; "accessibility.app-account.selector.accounts" = "Comptes";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -553,6 +555,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Information supplémentaire"; "report.comment.placeholder" = "Information supplémentaire";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Account"; "accessibility.app-account.selector.accounts" = "Account";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Informazioni aggiuntive"; "report.comment.placeholder" = "Informazioni aggiuntive";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "作成"; "accessibility.tabs.timeline.new-post.inputLabel2" = "作成";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld 件の新しい投稿"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld 件の新しい投稿";
"accessibility.tabs.timeline.unread-posts.hint" = "タイムラインをスクロールします。"; "accessibility.tabs.timeline.unread-posts.hint" = "タイムラインをスクロールします。";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "アカウント"; "accessibility.app-account.selector.accounts" = "アカウント";
"accessibility.app-account.selector.accounts.hint" = "オプション シートを開きます。"; "accessibility.app-account.selector.accounts.hint" = "オプション シートを開きます。";
"accessibility.tabs.profile.options.label" = "オプション"; "accessibility.tabs.profile.options.label" = "オプション";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ に返信"; "accessibility.status.a-replied-to-%@" = "%@ に返信";
"accessibility.image.alt-text-%@" = "画像の代替テキスト: %@"; "accessibility.image.alt-text-%@" = "画像の代替テキスト: %@";
"accessibility.image.alt-text-more.label" = "より多くの代替テキストを利用できます"; "accessibility.image.alt-text-more.label" = "より多くの代替テキストを利用できます";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "追加情報"; "report.comment.placeholder" = "追加情報";

View file

@ -526,6 +526,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "계정"; "accessibility.app-account.selector.accounts" = "계정";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -559,6 +561,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "추가 정보"; "report.comment.placeholder" = "추가 정보";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Accounts"; "accessibility.app-account.selector.accounts" = "Accounts";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Additional Info"; "report.comment.placeholder" = "Additional Info";

View file

@ -521,6 +521,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Maak aan"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Maak aan";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld nieuwe posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld nieuwe posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrollt de tijdlijn."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrollt de tijdlijn.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Accounts"; "accessibility.app-account.selector.accounts" = "Accounts";
"accessibility.app-account.selector.accounts.hint" = "Opent paneel voor opties."; "accessibility.app-account.selector.accounts.hint" = "Opent paneel voor opties.";
"accessibility.tabs.profile.options.label" = "Opties"; "accessibility.tabs.profile.options.label" = "Opties";
@ -554,6 +556,7 @@
"accessibility.status.a-replied-to-%@" = "%@ heeft geantwoord op"; "accessibility.status.a-replied-to-%@" = "%@ heeft geantwoord op";
"accessibility.image.alt-text-%@" = "Tekst voor afbeedling: %@"; "accessibility.image.alt-text-%@" = "Tekst voor afbeedling: %@";
"accessibility.image.alt-text-more.label" = "Meer tekst beschikbaar"; "accessibility.image.alt-text-more.label" = "Meer tekst beschikbaar";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Aanvullende informatie"; "report.comment.placeholder" = "Aanvullende informatie";

View file

@ -515,6 +515,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Konta"; "accessibility.app-account.selector.accounts" = "Konta";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Opcje"; "accessibility.tabs.profile.options.label" = "Opcje";
@ -548,6 +550,7 @@
"accessibility.status.a-replied-to-%@" = "%@ odpowiedział do"; "accessibility.status.a-replied-to-%@" = "%@ odpowiedział do";
"accessibility.image.alt-text-%@" = "Tekst alternatywny obrazka: %@"; "accessibility.image.alt-text-%@" = "Tekst alternatywny obrazka: %@";
"accessibility.image.alt-text-more.label" = "Dostępna jest większa ilość tekstu alternatywnego"; "accessibility.image.alt-text-more.label" = "Dostępna jest większa ilość tekstu alternatywnego";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Informacja dodatkowa"; "report.comment.placeholder" = "Informacja dodatkowa";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Contas"; "accessibility.app-account.selector.accounts" = "Contas";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Informação Adicional"; "report.comment.placeholder" = "Informação Adicional";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Accounts"; "accessibility.app-account.selector.accounts" = "Accounts";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Additional Info"; "report.comment.placeholder" = "Additional Info";

View file

@ -525,6 +525,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "Профілі"; "accessibility.app-account.selector.accounts" = "Профілі";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -558,6 +560,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "Додаткова інформація"; "report.comment.placeholder" = "Додаткова інформація";

View file

@ -559,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ 回复给"; "accessibility.status.a-replied-to-%@" = "%@ 回复给";
"accessibility.image.alt-text-%@" = "图片描述文本:%@"; "accessibility.image.alt-text-%@" = "图片描述文本:%@";
"accessibility.image.alt-text-more.label" = "更多描述文本可用"; "accessibility.image.alt-text-more.label" = "更多描述文本可用";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "附加信息"; "report.comment.placeholder" = "附加信息";

View file

@ -524,6 +524,8 @@
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create"; "accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts"; "accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
"accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline."; "accessibility.tabs.timeline.unread-posts.hint" = "Scrolls the timeline.";
"accessibility.tabs.timeline.content-link-%@" = "Visit %@";
"accessibility.tabs.timeline.content-hashtag-%@" = "Hashtag %@";
"accessibility.app-account.selector.accounts" = "帳號"; "accessibility.app-account.selector.accounts" = "帳號";
"accessibility.app-account.selector.accounts.hint" = "Opens options sheet."; "accessibility.app-account.selector.accounts.hint" = "Opens options sheet.";
"accessibility.tabs.profile.options.label" = "Options"; "accessibility.tabs.profile.options.label" = "Options";
@ -557,6 +559,7 @@
"accessibility.status.a-replied-to-%@" = "%@ replied to"; "accessibility.status.a-replied-to-%@" = "%@ replied to";
"accessibility.image.alt-text-%@" = "Image alt text: %@"; "accessibility.image.alt-text-%@" = "Image alt text: %@";
"accessibility.image.alt-text-more.label" = "More alt text available"; "accessibility.image.alt-text-more.label" = "More alt text available";
"accessibility.tabs.messages.unread.label" = "Unread";
// MARK: Report // MARK: Report
"report.comment.placeholder" = "附加資訊"; "report.comment.placeholder" = "附加資訊";

View file

@ -15,55 +15,75 @@ struct ConversationsListRow: View {
@ObservedObject var viewModel: ConversationsListViewModel @ObservedObject var viewModel: ConversationsListViewModel
var body: some View { var body: some View {
VStack(alignment: .leading) { Button {
HStack(alignment: .top, spacing: 8) { Task {
AvatarView(url: conversation.accounts.first!.avatar) await viewModel.markAsRead(conversation: conversation)
VStack(alignment: .leading, spacing: 4) { }
HStack { routerPath.navigate(to: .conversationDetail(conversation: conversation))
EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")), } label: {
emojis: conversation.accounts.flatMap { $0.emojis }) VStack(alignment: .leading) {
.font(.scaledSubheadline) HStack(alignment: .top, spacing: 8) {
.foregroundColor(theme.labelColor) AvatarView(url: conversation.accounts.first!.avatar)
.emojiSize(Font.scaledSubheadlineFont.emojiSize) .accessibilityHidden(true)
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset) VStack(alignment: .leading, spacing: 4) {
.fontWeight(.semibold) HStack {
.foregroundColor(theme.labelColor) EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")),
emojis: conversation.accounts.flatMap { $0.emojis })
.font(.scaledSubheadline)
.foregroundColor(theme.labelColor)
.emojiSize(Font.scaledSubheadlineFont.emojiSize)
.emojiBaselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
.fontWeight(.semibold)
.foregroundColor(theme.labelColor)
.multilineTextAlignment(.leading)
Spacer()
if conversation.unread {
Circle()
.foregroundColor(theme.tintColor)
.frame(width: 10, height: 10)
.accessibilityRepresentation {
Text("accessibility.tabs.messages.unread.label")
}
.accessibilitySortPriority(1)
}
if let message = conversation.lastStatus {
Text(message.createdAt.relativeFormatted)
.font(.scaledFootnote)
}
}
EmojiTextApp(conversation.lastStatus?.content ?? HTMLString(stringValue: ""), emojis: conversation.lastStatus?.emojis ?? [])
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
Spacer() .font(.scaledBody)
if conversation.unread { .foregroundColor(theme.labelColor)
Circle() .emojiSize(Font.scaledBodyFont.emojiSize)
.foregroundColor(theme.tintColor) .emojiBaselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
.frame(width: 10, height: 10) .accessibilityLabel(conversation.lastStatus?.content.asRawText ?? "")
}
if let message = conversation.lastStatus {
Text(message.createdAt.relativeFormatted)
.font(.scaledFootnote)
}
} }
EmojiTextApp(conversation.lastStatus?.content ?? HTMLString(stringValue: ""), emojis: conversation.lastStatus?.emojis ?? []) Spacer()
.multilineTextAlignment(.leading)
.font(.scaledBody)
.foregroundColor(theme.labelColor)
.emojiSize(Font.scaledBodyFont.emojiSize)
.emojiBaselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
} }
Spacer() .padding(.top, 4)
} if conversation.lastStatus != nil {
.contentShape(Rectangle()) actionsView
.onTapGesture { .padding(.bottom, 4)
Task { .accessibilityHidden(true)
await viewModel.markAsRead(conversation: conversation)
} }
routerPath.navigate(to: .conversationDetail(conversation: conversation))
} }
.padding(.top, 4) .contextMenu {
if conversation.lastStatus != nil { contextMenu
actionsView .accessibilityHidden(true)
.padding(.bottom, 4) }
.accessibilityElement(children: .combine)
.accessibilityActions {
replyAction
contextMenu
accessibilityActions
}
.accessibilityAction(.magicTap) {
if let lastStatus = conversation.lastStatus {
HapticManager.shared.fireHaptic(of: .notification(.success))
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
}
} }
}
.contextMenu {
contextMenu
} }
} }
@ -88,12 +108,14 @@ struct ConversationsListRow: View {
@ViewBuilder @ViewBuilder
private var contextMenu: some View { private var contextMenu: some View {
Button { if conversation.unread {
Task { Button {
await viewModel.markAsRead(conversation: conversation) Task {
await viewModel.markAsRead(conversation: conversation)
}
} label: {
Label("conversations.action.mark-read", systemImage: "eye")
} }
} label: {
Label("conversations.action.mark-read", systemImage: "eye")
} }
if let message = conversation.lastStatus { if let message = conversation.lastStatus {
@ -152,4 +174,53 @@ struct ConversationsListRow: View {
systemImage: conversation.lastStatus?.bookmarked ?? false ? "bookmark.fill" : "bookmark") systemImage: conversation.lastStatus?.bookmarked ?? false ? "bookmark.fill" : "bookmark")
} }
} }
// MARK: - Accessibility actions
@ViewBuilder
var replyAction: some View {
if let lastStatus = conversation.lastStatus {
Button("status.action.reply") {
HapticManager.shared.fireHaptic(of: .notification(.success))
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
}
} else {
EmptyView()
}
}
@ViewBuilder
private var accessibilityActions: some View {
if let lastStatus = conversation.lastStatus {
if lastStatus.account.id != currentAccount.account?.id {
Button("@\(lastStatus.account.username)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
routerPath.navigate(to: .accountDetail(id: lastStatus.account.id))
}
}
// Add in each detected link in the content
ForEach(lastStatus.content.links) { link in
switch link.type {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = routerPath.handle(url: link.url)
}
}
}
}
}
} }

View file

@ -11,6 +11,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
public var asMarkdown: String = "" public var asMarkdown: String = ""
public var asRawText: String = "" public var asRawText: String = ""
public var statusesURLs = [URL]() public var statusesURLs = [URL]()
public var links = [Link]()
public var asSafeMarkdownAttributedString: AttributedString = .init() public var asSafeMarkdownAttributedString: AttributedString = .init()
private var main_regex: NSRegularExpression? private var main_regex: NSRegularExpression?
@ -75,6 +76,15 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
} catch { } catch {
asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue) asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue)
} }
links = asSafeMarkdownAttributedString.runs
.compactMap { run in
guard let link = run.link else {
return nil
}
return Link(link, displayString: String(self.asSafeMarkdownAttributedString[run.range].characters))
}
} }
public init(stringValue: String, parseMarkdown: Bool = false) { public init(stringValue: String, parseMarkdown: Bool = false) {
@ -94,6 +104,15 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
} else { } else {
asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue) asSafeMarkdownAttributedString = AttributedString(stringLiteral: htmlValue)
} }
links = asSafeMarkdownAttributedString.runs
.compactMap { run in
guard let link = run.link else {
return nil
}
return Link(link, displayString: String(self.asSafeMarkdownAttributedString[run.range].characters))
}
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -167,4 +186,39 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
} }
} catch {} } catch {}
} }
public struct Link: Hashable, Identifiable {
public var id: Int { hashValue }
public let url: AttributeScopes.FoundationAttributes.LinkAttribute.Value
public let displayString: String
public let type: LinkType
public let title: String
init(_ url: AttributeScopes.FoundationAttributes.LinkAttribute.Value, displayString: String) {
self.url = url
self.displayString = displayString
switch displayString.first {
case "@":
self.type = .mention
self.title = displayString
case "#":
self.type = .hashtag
self.title = String(displayString.dropFirst())
default:
self.type = .url
var hostNameUrl = url.host ?? url.absoluteString
if hostNameUrl.hasPrefix("www.") {
hostNameUrl = String(hostNameUrl.dropFirst(4))
}
self.title = hostNameUrl
}
}
public enum LinkType {
case url
case mention
case hashtag
}
}
} }

View file

@ -19,13 +19,17 @@ struct NotificationRowView: View {
HStack(alignment: .top, spacing: 8) { HStack(alignment: .top, spacing: 8) {
if notification.accounts.count == 1 { if notification.accounts.count == 1 {
makeAvatarView(type: notification.type) makeAvatarView(type: notification.type)
.accessibilityHidden(true)
} else { } else {
makeNotificationIconView(type: notification.type) makeNotificationIconView(type: notification.type)
.frame(width: AvatarView.Size.status.size.width, .frame(width: AvatarView.Size.status.size.width,
height: AvatarView.Size.status.size.height) height: AvatarView.Size.status.size.height)
.accessibilityHidden(true)
} }
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
makeMainLabel(type: notification.type) makeMainLabel(type: notification.type)
// The main label is redundant for mentions
.accessibilityHidden(notification.type == .mention)
makeContent(type: notification.type) makeContent(type: notification.type)
if notification.type == .follow_request, if notification.type == .follow_request,
followRequests.map(\.id).contains(notification.accounts[0].id) followRequests.map(\.id).contains(notification.accounts[0].id)
@ -34,6 +38,12 @@ struct NotificationRowView: View {
} }
} }
} }
.accessibilityElement(children: .combine)
.accessibilityActions {
if notification.type == .follow {
accessibilityUserActions
}
}
.alignmentGuide(.listRowSeparatorLeading) { _ in .alignmentGuide(.listRowSeparatorLeading) { _ in
-100 -100
} }
@ -115,6 +125,7 @@ struct NotificationRowView: View {
Text("") Text("")
Text(Image(systemName: status.visibility.iconName)) Text(Image(systemName: status.visibility.iconName))
} }
.accessibilityHidden(true)
.font(.scaledFootnote) .font(.scaledFootnote)
.fontWeight(.regular) .fontWeight(.regular)
.foregroundColor(.gray) .foregroundColor(.gray)
@ -130,6 +141,7 @@ struct NotificationRowView: View {
routerPath.navigate(to: .accountsList(accounts: notification.accounts)) routerPath.navigate(to: .accountsList(accounts: notification.accounts))
} }
} }
.accessibilityElement(children: .combine)
} }
@ViewBuilder @ViewBuilder
@ -161,6 +173,7 @@ struct NotificationRowView: View {
if type == .follow { if type == .follow {
EmojiTextApp(notification.accounts[0].note, EmojiTextApp(notification.accounts[0].note,
emojis: notification.accounts[0].emojis) emojis: notification.accounts[0].emojis)
.accessibilityLabel(notification.accounts[0].note.asRawText)
.lineLimit(3) .lineLimit(3)
.font(.scaledCallout) .font(.scaledCallout)
.emojiSize(Font.scaledCalloutFont.emojiSize) .emojiSize(Font.scaledCalloutFont.emojiSize)
@ -169,6 +182,7 @@ struct NotificationRowView: View {
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url) routerPath.handle(url: url)
}) })
.accessibilityAddTraits(.isButton)
} }
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
@ -181,4 +195,16 @@ struct NotificationRowView: View {
} }
} }
} }
// MARK: - Accessibility actions
@ViewBuilder
private var accessibilityUserActions: some View {
ForEach(notification.accounts) { account in
Button("@\(account.username)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
routerPath.navigate(to: .accountDetail(id: account.id))
}
}
}
} }

View file

@ -175,5 +175,6 @@ public struct NotificationsListView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowInsets(.init()) .listRowInsets(.init())
.frame(height: .layoutPadding) .frame(height: .layoutPadding)
.accessibilityHidden(true)
} }
} }

View file

@ -191,5 +191,6 @@ public struct StatusDetailView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowInsets(.init()) .listRowInsets(.init())
.frame(height: .layoutPadding) .frame(height: .layoutPadding)
.accessibilityHidden(true)
} }
} }

View file

@ -27,6 +27,7 @@ public struct StatusEmbeddedView: View {
client: client, client: client,
routerPath: routerPath, routerPath: routerPath,
showActions: false) }) showActions: false) })
.accessibilityLabel(status.content.asRawText)
.environment(\.isCompact, true) .environment(\.isCompact, true)
} }
Spacer() Spacer()
@ -39,6 +40,7 @@ public struct StatusEmbeddedView: View {
.stroke(.gray.opacity(0.35), lineWidth: 1) .stroke(.gray.opacity(0.35), lineWidth: 1)
) )
.padding(.top, 8) .padding(.top, 8)
.accessibilityElement(children: .combine)
} }
private func makeAccountView(account: Account) -> some View { private func makeAccountView(account: Account) -> some View {

View file

@ -11,6 +11,7 @@ public struct StatusRowView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
@Environment(\.isCompact) private var isCompact: Bool @Environment(\.isCompact) private var isCompact: Bool
@Environment(\.accessibilityEnabled) private var accessibilityEnabled
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -95,13 +96,13 @@ public struct StatusRowView: View {
} }
.swipeActions(edge: .trailing) { .swipeActions(edge: .trailing) {
// The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them. // The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them.
if !isCompact, UIAccessibility.isVoiceOverRunning == false { if !isCompact, accessibilityEnabled == false {
StatusRowSwipeView(viewModel: viewModel, mode: .trailing) StatusRowSwipeView(viewModel: viewModel, mode: .trailing)
} }
} }
.swipeActions(edge: .leading) { .swipeActions(edge: .leading) {
// The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them. // The actions associated with the swipes are exposed as custom accessibility actions and there is no way to remove them.
if !isCompact, UIAccessibility.isVoiceOverRunning == false { if !isCompact, accessibilityEnabled == false {
StatusRowSwipeView(viewModel: viewModel, mode: .leading) StatusRowSwipeView(viewModel: viewModel, mode: .leading)
} }
} }
@ -111,18 +112,15 @@ public struct StatusRowView: View {
bottom: 12, bottom: 12,
trailing: .layoutPadding)) trailing: .layoutPadding))
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine) .accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
.accessibilityLabel(viewModel.isFocused == false && UIAccessibility.isVoiceOverRunning .accessibilityLabel(viewModel.isFocused == false && accessibilityEnabled
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text("")) ? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
.accessibilityCustomContent(
LocalizedStringKey("accessibility.status.spoiler-full-content"),
viewModel.finalStatus.content.asRawText,
importance: .high
)
.accessibilityAction { .accessibilityAction {
viewModel.navigateToDetail() viewModel.navigateToDetail()
} }
.accessibilityActions { .accessibilityActions {
accessibilityActions if viewModel.showActions {
accessibilityActions
}
} }
.background { .background {
Color.clear Color.clear
@ -161,11 +159,15 @@ public struct StatusRowView: View {
@ViewBuilder @ViewBuilder
private var accessibilityActions: some View { private var accessibilityActions: some View {
// Add the individual mentions as accessibility actions // Add reply and quote, which are lost when the swipe actions are removed
ForEach(viewModel.status.mentions, id: \.id) { mention in Button("status.action.reply") {
Button("@\(mention.username)") { HapticManager.shared.fireHaptic(of: .notification(.success))
viewModel.routerPath.navigate(to: .accountDetail(id: mention.id)) viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
} }
Button("settings.swipeactions.status.action.quote") {
HapticManager.shared.fireHaptic(of: .notification(.success))
viewModel.routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
} }
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") { Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
@ -175,8 +177,40 @@ public struct StatusRowView: View {
} }
Button("@\(viewModel.status.account.username)") { Button("@\(viewModel.status.account.username)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id)) viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
} }
// Add a reference to the post creator
if viewModel.status.account != viewModel.finalStatus.account {
Button("@\(viewModel.finalStatus.account.username)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.finalStatus.account.id))
}
}
// Add in each detected link in the content
ForEach(viewModel.finalStatus.content.links) { link in
switch link.type {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
HapticManager.shared.fireHaptic(of: .notification(.success))
_ = viewModel.routerPath.handle(url: link.url)
}
}
}
} }
private func makeFilterView(filter: Filter) -> some View { private func makeFilterView(filter: Filter) -> some View {
@ -234,11 +268,11 @@ private struct CombinedAccessibilityLabel {
Text(hasSpoiler Text(hasSpoiler
? viewModel.finalStatus.spoilerText.asRawText ? viewModel.finalStatus.spoilerText.asRawText
: viewModel.finalStatus.content.asRawText : viewModel.finalStatus.content.asRawText
) + Text(", ") + ) +
Text(hasSpoiler Text(hasSpoiler
? "status.editor.spoiler" ? "status.editor.spoiler"
: "" : ""
) + Text(", ") + ) +
imageAltText() + Text(", ") + imageAltText() + Text(", ") +
Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") + Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") +
Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") + Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") +

View file

@ -44,6 +44,7 @@ struct StatusRowContentView: View {
Spacer() Spacer()
} }
} }
.accessibilityHidden(viewModel.isFocused == false)
.padding(.vertical, 4) .padding(.vertical, 4)
} }

View file

@ -27,10 +27,12 @@ public struct TimelineView: View {
@Binding var timeline: TimelineFilter @Binding var timeline: TimelineFilter
@Binding var scrollToTopSignal: Int @Binding var scrollToTopSignal: Int
private let canFilterTimeline: Bool
public init(timeline: Binding<TimelineFilter>, scrollToTopSignal: Binding<Int>) { public init(timeline: Binding<TimelineFilter>, scrollToTopSignal: Binding<Int>, canFilterTimeline: Bool) {
_timeline = timeline _timeline = timeline
_scrollToTopSignal = scrollToTopSignal _scrollToTopSignal = scrollToTopSignal
self.canFilterTimeline = canFilterTimeline
} }
public var body: some View { public var body: some View {
@ -99,9 +101,14 @@ public struct TimelineView: View {
} }
} }
.accessibilityRepresentation { .accessibilityRepresentation {
Menu(timeline.localizedTitle()) {} if canFilterTimeline {
Menu(timeline.localizedTitle()) {}
} else {
Text(timeline.localizedTitle())
}
} }
.accessibilityAddTraits(.isHeader) .accessibilityAddTraits(.isHeader)
.accessibilityRemoveTraits(.isButton)
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@ -172,6 +179,7 @@ public struct TimelineView: View {
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
.accessibilityElement(children: .combine)
Spacer() Spacer()
Button { Button {
Task { Task {