mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 16:31:00 +00:00
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:
parent
9746eb7674
commit
b2f594f174
30 changed files with 323 additions and 70 deletions
|
@ -31,9 +31,9 @@ extension View {
|
|||
case let .conversationDetail(conversation):
|
||||
ConversationDetailView(conversation: conversation)
|
||||
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):
|
||||
TimelineView(timeline: .constant(.list(list: list)), scrollToTopSignal: .constant(0))
|
||||
TimelineView(timeline: .constant(.list(list: list)), scrollToTopSignal: .constant(0), canFilterTimeline: false)
|
||||
case let .following(id):
|
||||
AccountsListView(mode: .following(accountId: id))
|
||||
case let .followers(id):
|
||||
|
@ -45,7 +45,7 @@ extension View {
|
|||
case let .accountsList(accounts):
|
||||
AccountsListView(mode: .accountsList(accounts: accounts))
|
||||
case .trendingTimeline:
|
||||
TimelineView(timeline: .constant(.trending), scrollToTopSignal: .constant(0))
|
||||
TimelineView(timeline: .constant(.trending), scrollToTopSignal: .constant(0), canFilterTimeline: false)
|
||||
case let .tagsList(tags):
|
||||
TagsListView(tags: tags)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ struct TimelineTab: View {
|
|||
|
||||
var body: some View {
|
||||
NavigationStack(path: $routerPath.path) {
|
||||
TimelineView(timeline: $timeline, scrollToTopSignal: $scrollToTopSignal)
|
||||
TimelineView(timeline: $timeline, scrollToTopSignal: $scrollToTopSignal, canFilterTimeline: canFilterTimeline)
|
||||
.withAppRouter()
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
.toolbar {
|
||||
|
|
|
@ -529,6 +529,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -562,6 +564,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Дадатковая інфармацыя";
|
||||
|
|
|
@ -523,6 +523,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -556,6 +558,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Additional Info";
|
||||
|
|
|
@ -519,6 +519,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Erzeugen";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld neue Beiträge";
|
||||
"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.hint" = "Öffnet die Optionen.";
|
||||
"accessibility.tabs.profile.options.label" = "Optionen";
|
||||
|
@ -552,6 +554,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ antwortete auf";
|
||||
"accessibility.image.alt-text-%@" = "Alternativer Bildtext: %@";
|
||||
"accessibility.image.alt-text-more.label" = "Weiterer Alt.-Text verfügbar";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Zusätzliche Informationen";
|
||||
|
|
|
@ -523,6 +523,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.privacy.label" = "Visibility";
|
||||
"accessibility.editor.privacy.hint" = "Changes post audience.";
|
||||
|
@ -559,6 +561,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Additional Info";
|
||||
|
|
|
@ -525,6 +525,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -558,6 +560,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Additional Info";
|
||||
|
|
|
@ -525,6 +525,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Crear";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld publicaciones nuevas";
|
||||
"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.hint" = "Abrir opciones.";
|
||||
"accessibility.tabs.profile.options.label" = "Opciones";
|
||||
|
@ -558,6 +560,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ respondió a";
|
||||
"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";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Información adicional";
|
||||
|
|
|
@ -513,6 +513,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Sortu";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld bidalketa berri";
|
||||
"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.hint" = "Aukeren orria irekitzen du.";
|
||||
"accessibility.tabs.profile.options.label" = "Aukerak";
|
||||
|
@ -546,6 +548,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@(e)k honi erantzun dio:";
|
||||
"accessibility.image.alt-text-%@" = "Irudiaren deskribapena: %@";
|
||||
"accessibility.image.alt-text-more.label" = "Deskribapen testu gehiago dago";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Informazio gehigarria";
|
||||
|
|
|
@ -520,6 +520,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -553,6 +555,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Information supplémentaire";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Informazioni aggiuntive";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "作成";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld 件の新しい投稿";
|
||||
"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.hint" = "オプション シートを開きます。";
|
||||
"accessibility.tabs.profile.options.label" = "オプション";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ に返信";
|
||||
"accessibility.image.alt-text-%@" = "画像の代替テキスト: %@";
|
||||
"accessibility.image.alt-text-more.label" = "より多くの代替テキストを利用できます";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "追加情報";
|
||||
|
|
|
@ -526,6 +526,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -559,6 +561,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "추가 정보";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Additional Info";
|
||||
|
|
|
@ -521,6 +521,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Maak aan";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld nieuwe posts";
|
||||
"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.hint" = "Opent paneel voor opties.";
|
||||
"accessibility.tabs.profile.options.label" = "Opties";
|
||||
|
@ -554,6 +556,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ heeft geantwoord op";
|
||||
"accessibility.image.alt-text-%@" = "Tekst voor afbeedling: %@";
|
||||
"accessibility.image.alt-text-more.label" = "Meer tekst beschikbaar";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Aanvullende informatie";
|
||||
|
|
|
@ -515,6 +515,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Opcje";
|
||||
|
@ -548,6 +550,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ odpowiedział do";
|
||||
"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" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Informacja dodatkowa";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Informação Adicional";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Additional Info";
|
||||
|
|
|
@ -525,6 +525,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -558,6 +560,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "Додаткова інформація";
|
||||
|
|
|
@ -559,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ 回复给";
|
||||
"accessibility.image.alt-text-%@" = "图片描述文本:%@";
|
||||
"accessibility.image.alt-text-more.label" = "更多描述文本可用";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "附加信息";
|
||||
|
|
|
@ -524,6 +524,8 @@
|
|||
"accessibility.tabs.timeline.new-post.inputLabel2" = "Create";
|
||||
"accessibility.tabs.timeline.unread-posts.label-%lld" = "%lld new posts";
|
||||
"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.hint" = "Opens options sheet.";
|
||||
"accessibility.tabs.profile.options.label" = "Options";
|
||||
|
@ -557,6 +559,7 @@
|
|||
"accessibility.status.a-replied-to-%@" = "%@ replied to";
|
||||
"accessibility.image.alt-text-%@" = "Image alt text: %@";
|
||||
"accessibility.image.alt-text-more.label" = "More alt text available";
|
||||
"accessibility.tabs.messages.unread.label" = "Unread";
|
||||
|
||||
// MARK: Report
|
||||
"report.comment.placeholder" = "附加資訊";
|
||||
|
|
|
@ -15,9 +15,16 @@ struct ConversationsListRow: View {
|
|||
@ObservedObject var viewModel: ConversationsListViewModel
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.markAsRead(conversation: conversation)
|
||||
}
|
||||
routerPath.navigate(to: .conversationDetail(conversation: conversation))
|
||||
} label: {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
AvatarView(url: conversation.accounts.first!.avatar)
|
||||
.accessibilityHidden(true)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
EmojiTextApp(.init(stringValue: conversation.accounts.map { $0.safeDisplayName }.joined(separator: ", ")),
|
||||
|
@ -34,6 +41,10 @@ struct ConversationsListRow: View {
|
|||
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)
|
||||
|
@ -46,24 +57,33 @@ struct ConversationsListRow: View {
|
|||
.foregroundColor(theme.labelColor)
|
||||
.emojiSize(Font.scaledBodyFont.emojiSize)
|
||||
.emojiBaselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
|
||||
.accessibilityLabel(conversation.lastStatus?.content.asRawText ?? "")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
Task {
|
||||
await viewModel.markAsRead(conversation: conversation)
|
||||
}
|
||||
routerPath.navigate(to: .conversationDetail(conversation: conversation))
|
||||
}
|
||||
.padding(.top, 4)
|
||||
if conversation.lastStatus != nil {
|
||||
actionsView
|
||||
.padding(.bottom, 4)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
contextMenu
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +108,7 @@ struct ConversationsListRow: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var contextMenu: some View {
|
||||
if conversation.unread {
|
||||
Button {
|
||||
Task {
|
||||
await viewModel.markAsRead(conversation: conversation)
|
||||
|
@ -95,6 +116,7 @@ struct ConversationsListRow: View {
|
|||
} label: {
|
||||
Label("conversations.action.mark-read", systemImage: "eye")
|
||||
}
|
||||
}
|
||||
|
||||
if let message = conversation.lastStatus {
|
||||
Section("conversations.latest.message") {
|
||||
|
@ -152,4 +174,53 @@ struct ConversationsListRow: View {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
|||
public var asMarkdown: String = ""
|
||||
public var asRawText: String = ""
|
||||
public var statusesURLs = [URL]()
|
||||
public var links = [Link]()
|
||||
|
||||
public var asSafeMarkdownAttributedString: AttributedString = .init()
|
||||
private var main_regex: NSRegularExpression?
|
||||
|
@ -75,6 +76,15 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
|||
} catch {
|
||||
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) {
|
||||
|
@ -94,6 +104,15 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
|||
} else {
|
||||
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 {
|
||||
|
@ -167,4 +186,39 @@ public struct HTMLString: Codable, Equatable, Hashable, @unchecked Sendable {
|
|||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,17 @@ struct NotificationRowView: View {
|
|||
HStack(alignment: .top, spacing: 8) {
|
||||
if notification.accounts.count == 1 {
|
||||
makeAvatarView(type: notification.type)
|
||||
.accessibilityHidden(true)
|
||||
} else {
|
||||
makeNotificationIconView(type: notification.type)
|
||||
.frame(width: AvatarView.Size.status.size.width,
|
||||
height: AvatarView.Size.status.size.height)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
makeMainLabel(type: notification.type)
|
||||
// The main label is redundant for mentions
|
||||
.accessibilityHidden(notification.type == .mention)
|
||||
makeContent(type: notification.type)
|
||||
if notification.type == .follow_request,
|
||||
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
|
||||
-100
|
||||
}
|
||||
|
@ -115,6 +125,7 @@ struct NotificationRowView: View {
|
|||
Text(" ⸱ ")
|
||||
Text(Image(systemName: status.visibility.iconName))
|
||||
}
|
||||
.accessibilityHidden(true)
|
||||
.font(.scaledFootnote)
|
||||
.fontWeight(.regular)
|
||||
.foregroundColor(.gray)
|
||||
|
@ -130,6 +141,7 @@ struct NotificationRowView: View {
|
|||
routerPath.navigate(to: .accountsList(accounts: notification.accounts))
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -161,6 +173,7 @@ struct NotificationRowView: View {
|
|||
if type == .follow {
|
||||
EmojiTextApp(notification.accounts[0].note,
|
||||
emojis: notification.accounts[0].emojis)
|
||||
.accessibilityLabel(notification.accounts[0].note.asRawText)
|
||||
.lineLimit(3)
|
||||
.font(.scaledCallout)
|
||||
.emojiSize(Font.scaledCalloutFont.emojiSize)
|
||||
|
@ -169,6 +182,7 @@ struct NotificationRowView: View {
|
|||
.environment(\.openURL, OpenURLAction { url in
|
||||
routerPath.handle(url: url)
|
||||
})
|
||||
.accessibilityAddTraits(.isButton)
|
||||
}
|
||||
}
|
||||
.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,5 +175,6 @@ public struct NotificationsListView: View {
|
|||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(.init())
|
||||
.frame(height: .layoutPadding)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,5 +191,6 @@ public struct StatusDetailView: View {
|
|||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(.init())
|
||||
.frame(height: .layoutPadding)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ public struct StatusEmbeddedView: View {
|
|||
client: client,
|
||||
routerPath: routerPath,
|
||||
showActions: false) })
|
||||
.accessibilityLabel(status.content.asRawText)
|
||||
.environment(\.isCompact, true)
|
||||
}
|
||||
Spacer()
|
||||
|
@ -39,6 +40,7 @@ public struct StatusEmbeddedView: View {
|
|||
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
||||
)
|
||||
.padding(.top, 8)
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
private func makeAccountView(account: Account) -> some View {
|
|
@ -11,6 +11,7 @@ public struct StatusRowView: View {
|
|||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||
@Environment(\.redactionReasons) private var reasons
|
||||
@Environment(\.isCompact) private var isCompact: Bool
|
||||
@Environment(\.accessibilityEnabled) private var accessibilityEnabled
|
||||
|
||||
@EnvironmentObject private var theme: Theme
|
||||
|
||||
|
@ -95,13 +96,13 @@ public struct StatusRowView: View {
|
|||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .leading) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
@ -111,19 +112,16 @@ public struct StatusRowView: View {
|
|||
bottom: 12,
|
||||
trailing: .layoutPadding))
|
||||
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
|
||||
.accessibilityLabel(viewModel.isFocused == false && UIAccessibility.isVoiceOverRunning
|
||||
.accessibilityLabel(viewModel.isFocused == false && accessibilityEnabled
|
||||
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
|
||||
.accessibilityCustomContent(
|
||||
LocalizedStringKey("accessibility.status.spoiler-full-content"),
|
||||
viewModel.finalStatus.content.asRawText,
|
||||
importance: .high
|
||||
)
|
||||
.accessibilityAction {
|
||||
viewModel.navigateToDetail()
|
||||
}
|
||||
.accessibilityActions {
|
||||
if viewModel.showActions {
|
||||
accessibilityActions
|
||||
}
|
||||
}
|
||||
.background {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
|
@ -161,11 +159,15 @@ public struct StatusRowView: View {
|
|||
|
||||
@ViewBuilder
|
||||
private var accessibilityActions: some View {
|
||||
// Add the individual mentions as accessibility actions
|
||||
ForEach(viewModel.status.mentions, id: \.id) { mention in
|
||||
Button("@\(mention.username)") {
|
||||
viewModel.routerPath.navigate(to: .accountDetail(id: mention.id))
|
||||
// Add reply and quote, which are lost when the swipe actions are removed
|
||||
Button("status.action.reply") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
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") {
|
||||
|
@ -175,8 +177,40 @@ public struct StatusRowView: View {
|
|||
}
|
||||
|
||||
Button("@\(viewModel.status.account.username)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
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 {
|
||||
|
@ -234,11 +268,11 @@ private struct CombinedAccessibilityLabel {
|
|||
Text(hasSpoiler
|
||||
? viewModel.finalStatus.spoilerText.asRawText
|
||||
: viewModel.finalStatus.content.asRawText
|
||||
) + Text(", ") +
|
||||
) +
|
||||
Text(hasSpoiler
|
||||
? "status.editor.spoiler"
|
||||
: ""
|
||||
) + Text(", ") +
|
||||
) +
|
||||
imageAltText() + Text(", ") +
|
||||
Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") +
|
||||
Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") +
|
||||
|
|
|
@ -44,6 +44,7 @@ struct StatusRowContentView: View {
|
|||
Spacer()
|
||||
}
|
||||
}
|
||||
.accessibilityHidden(viewModel.isFocused == false)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,12 @@ public struct TimelineView: View {
|
|||
|
||||
@Binding var timeline: TimelineFilter
|
||||
@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
|
||||
_scrollToTopSignal = scrollToTopSignal
|
||||
self.canFilterTimeline = canFilterTimeline
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
@ -99,9 +101,14 @@ public struct TimelineView: View {
|
|||
}
|
||||
}
|
||||
.accessibilityRepresentation {
|
||||
if canFilterTimeline {
|
||||
Menu(timeline.localizedTitle()) {}
|
||||
} else {
|
||||
Text(timeline.localizedTitle())
|
||||
}
|
||||
}
|
||||
.accessibilityAddTraits(.isHeader)
|
||||
.accessibilityRemoveTraits(.isButton)
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
@ -172,6 +179,7 @@ public struct TimelineView: View {
|
|||
.font(.scaledFootnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
Spacer()
|
||||
Button {
|
||||
Task {
|
||||
|
|
Loading…
Reference in a new issue