diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/Components/Metadata/MetadataDuration.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/Components/Metadata/MetadataDuration.swift index 4e73e890..695e21d2 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/Components/Metadata/MetadataDuration.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/Components/Metadata/MetadataDuration.swift @@ -1,4 +1,5 @@ import SwiftUI +import UtilityLibrary public struct MetadataDuration: View { let text: String @@ -15,5 +16,6 @@ public struct MetadataDuration: View { .foregroundColor(.white) .lineLimit(1) .glassEffect(.clear, in: .rect(cornerRadius: 6)) + .accessibilityLabel(text.accessibilityTime) } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/GlassCardView.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/GlassCardView.swift index 7c6313b2..d99140a2 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/GlassCardView.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/GlassCardView.swift @@ -108,11 +108,13 @@ private extension GlassCardView { .cornerRadius(12) } .aspectRatio(ratio, contentMode: .fit) + .accessibilityHidden(true) } else { GeometryReader { geo in CachedAsyncImage(image: imageUrl, contentMode: .fill) .frame(width: geo.size.width, height: geo.size.height) } + .accessibilityHidden(true) } } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/LeadingImageCard.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/LeadingImageCard.swift index 2863e234..50bc1400 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/LeadingImageCard.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Cards/Views/LeadingImageCard.swift @@ -57,6 +57,7 @@ private extension LeadingImageCard { CachedAsyncImage(image: artworkUrl, contentMode: .fill) .frame(width: 100, height: 100) .clipShape(RoundedRectangle(cornerRadius: CardMetrics.innerRadius, style: .continuous)) + .accessibilityHidden(true) } } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/MenuView.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/MenuView.swift index 79e0f027..f4b7ff7f 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/MenuView.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/MenuView.swift @@ -26,6 +26,7 @@ public struct MenuView: View where T: RawRepresentable, T.RawValue: .padding(.horizontal, 20) .padding(.vertical, 10) .glassEffect(effect(selected: selected == option), in: .capsule) + .accessibilityAddTraits(selected == option ? .isSelected : []) } } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/PaginatedForEach.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/PaginatedForEach.swift index 123566e9..2c933eb9 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/PaginatedForEach.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/PaginatedForEach.swift @@ -42,6 +42,7 @@ public struct PaginatedForEach: View { } if hasMore { ProgressView() + .accessibilityLabel("Carregando mais conteúdo") .frame(maxWidth: .infinity) .padding() } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/Disqus/DisqusWebView.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/Disqus/DisqusWebView.swift index 88f374b2..f14a0184 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/Disqus/DisqusWebView.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/Disqus/DisqusWebView.swift @@ -32,6 +32,7 @@ struct DisqusSheet: View { Button(action: { onDismiss() }, label: { Image(systemName: "xmark") }) .tint(.primary) + .accessibilityLabel("Fechar") } } } @@ -70,6 +71,7 @@ struct DisqusSheet: View { Button(action: { self.loginURL = nil }, label: { Image(systemName: "xmark") }) .tint(.primary) + .accessibilityLabel("Fechar") } } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/MMWebView.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/MMWebView.swift index 1228222b..0437d4d8 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/MMWebView.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/MMWebView.swift @@ -50,6 +50,7 @@ public struct MMWebView: View { Image(systemName: "xmark") } .tint(.primary) + .accessibilityLabel("Fechar") } } } diff --git a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/WebViewStatus.swift b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/WebViewStatus.swift index 2ab7c80f..a6d4a5fb 100644 --- a/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/WebViewStatus.swift +++ b/MacMagazine/Features/MacMagazineUILibrary/Sources/MacMagazineUILibrary/Webview/WebViewStatus.swift @@ -14,6 +14,7 @@ struct WebViewStatusOverlay: View { switch status { case .loading: ProgressView() + .accessibilityLabel("Carregando conteúdo") case let .error(error): ContentUnavailableView( "Estamos com um problema", diff --git a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Modifiers/CardAccessibilityModifier.swift b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Modifiers/CardAccessibilityModifier.swift index 0f35d331..d91204d1 100644 --- a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Modifiers/CardAccessibilityModifier.swift +++ b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Modifiers/CardAccessibilityModifier.swift @@ -3,7 +3,7 @@ import MacMagazineUILibrary import SwiftUI import UtilityLibrary -enum CardLabel { +public enum CardLabel { case title case date case author @@ -26,7 +26,7 @@ private extension Array where Element == CardLabel { } } -enum CardButton { +public enum CardButton { case share case favorite } @@ -45,7 +45,7 @@ private extension Array where Element == CardButton { } } -extension View { +public extension View { func cardAccessibility( data: CardContent, labels: [CardLabel]?, diff --git a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/Components/FeedHighlightsCarouselView.swift b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/Components/FeedHighlightsCarouselView.swift index 830c565e..107189f4 100644 --- a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/Components/FeedHighlightsCarouselView.swift +++ b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/Components/FeedHighlightsCarouselView.swift @@ -92,6 +92,8 @@ public struct FeedHighlightsCarouselView: View { spacing: Layout.spacing ) } + .accessibilityLabel(post.title) + .accessibilityHint("Duplo toque para abrir a notícia.") .scrollTransition(.interactive) { content, phase in content .scaleEffect(phase.isIdentity ? 1.0 : 0.95) diff --git a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/NewsView.swift b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/NewsView.swift index 7eeeb14f..64148aef 100644 --- a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/NewsView.swift +++ b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Views/NewsView.swift @@ -216,6 +216,7 @@ extension NewsView { action: { readingNews = false }, label: { Image(systemName: "chevron.backward") }) .tint(.primary) + .accessibilityLabel("Voltar") } ToolbarItem(placement: .automatic) { favoriteView diff --git a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/FullPlayerView.swift b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/FullPlayerView.swift index 07a7facf..2acb44f9 100644 --- a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/FullPlayerView.swift +++ b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/FullPlayerView.swift @@ -514,7 +514,6 @@ private extension FullPlayerView { .accessibilityHidden(true) } .padding(.horizontal, 20) - .dynamicTypeSize(.medium) } } diff --git a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/Helpers/ScrollStylePicker.swift b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/Helpers/ScrollStylePicker.swift index 3d52131a..caea385d 100644 --- a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/Helpers/ScrollStylePicker.swift +++ b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/Helpers/ScrollStylePicker.swift @@ -99,7 +99,6 @@ struct SpeedWheelPicker: View { .buttonStyle(.plain) } .frame(width: width, height: 40) - .dynamicTypeSize(.medium) } // MARK: - SCROLL BINDING diff --git a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/MiniPlayerView.swift b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/MiniPlayerView.swift index 1ebf4d95..6c141caf 100644 --- a/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/MiniPlayerView.swift +++ b/MacMagazine/Features/PodcastLibrary/Sources/Podcast/Views/Player/MiniPlayerView.swift @@ -44,6 +44,16 @@ struct MiniPlayerView: View { .gesture(tapToOpen) .gesture(tapToDismiss) .matchedTransitionSource(id: "MINIPLAYER", in: animation) + .accessibilityAction(named: "Abrir player completo") { + playerManager.isFullscreen.toggle() + } + .accessibilityAction(named: "Fechar mini player") { + if playerManager.isPlaying { + playerManager.pause() + currentPodcast.save(current: playerManager.currentTime, using: modelContext) + } + playerManager.currentPodcast = nil + } } private var tapToOpen: some Gesture { @@ -83,6 +93,7 @@ private extension MiniPlayerView { Ticker(text: currentPodcast.title, speed: 30) .frame(height: 30) .id(currentPodcast.id) + .accessibilityLabel(currentPodcast.title) HStack(spacing: 20) { Button { @@ -96,6 +107,7 @@ private extension MiniPlayerView { .font(.system(size: 20)) } .buttonStyle(.plain) + .accessibilityLabel("Voltar 15 segundos") Button { playerManager.togglePlayPause() @@ -109,6 +121,7 @@ private extension MiniPlayerView { .font(.system(size: 24)) } .buttonStyle(.plain) + .accessibilityLabel(playerManager.isPlaying ? "Pausar" : "Reproduzir") Button { playerManager.skip(by: 15) @@ -121,6 +134,7 @@ private extension MiniPlayerView { .font(.system(size: 20)) } .buttonStyle(.plain) + .accessibilityLabel("Avançar 15 segundos") } } .foregroundStyle(controlsColor) diff --git a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/RecentSearchesView.swift b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/RecentSearchesView.swift index 41774239..489a67dc 100644 --- a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/RecentSearchesView.swift +++ b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/RecentSearchesView.swift @@ -12,9 +12,11 @@ struct RecentSearchesView: View { HStack { Text("Recentes") .font(.headline) + .accessibilityAddTraits(.isHeader) Spacer() Button("Limpar") { onClear() } .font(.subheadline) + .accessibilityLabel("Limpar buscas recentes") } .padding(.horizontal) diff --git a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchResultsList.swift b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchResultsList.swift index 95e4074e..0411aa9d 100644 --- a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchResultsList.swift +++ b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchResultsList.swift @@ -60,6 +60,11 @@ private extension SearchResultsList { NewsCard(data: cardContent) { onSelectNews(feedDB) } + .cardAccessibility( + data: cardContent, + labels: [NewsLibrary.CardLabel.title, NewsLibrary.CardLabel.date], + buttons: [NewsLibrary.CardButton.favorite, NewsLibrary.CardButton.share] + ) } } diff --git a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchView.swift b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchView.swift index 3399a18d..c13ea922 100644 --- a/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchView.swift +++ b/MacMagazine/Features/SearchLibrary/Sources/SearchLibrary/Views/SearchView.swift @@ -6,6 +6,7 @@ import PodcastLibrary import StorageLibrary import SwiftUI import UIComponentsLibrary +import UIKit import YouTubeLibrary public struct SearchView: View { @@ -50,6 +51,7 @@ public struct SearchView: View { } .onChange(of: viewModel.status) { _, newStatus in trackSearchCompletion(newStatus) + announceSearchStatus(newStatus) } .navigationDestination(isPresented: $showingWebView) { details @@ -248,6 +250,25 @@ private extension SearchView { break } } + + func announceSearchStatus(_ status: SearchStatus) { + let message: String + switch status { + case .searching: + message = "Buscando..." + case .done: + if viewModel.results.isEmpty { + message = "Nenhum resultado encontrado" + } else { + message = "\(viewModel.results.count) resultados encontrados" + } + case .error: + message = "Erro na busca" + case .idle, .localResults: + return + } + UIAccessibility.post(notification: .announcement, argument: message) + } } // MARK: - Layout diff --git a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/SettingsView.swift b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/SettingsView.swift index ead56a30..17c56206 100644 --- a/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/SettingsView.swift +++ b/MacMagazine/Features/SettingsLibrary/Sources/SettingsLibrary/Views/SettingsView.swift @@ -126,6 +126,7 @@ private struct ContentSheet: View { Button(action: onDismiss, label: { Image(systemName: "xmark") }) .tint(.primary) + .accessibilityLabel("Fechar") } } .onChange(of: colorScheme) { @@ -177,6 +178,7 @@ private struct PatronLoginSheet: View { Button(action: onDismiss, label: { Image(systemName: "xmark") }) .tint(.primary) + .accessibilityLabel("Fechar") } } } diff --git a/MacMagazine/MacMagazine/Features/News/DeepLinkNewsDetailView.swift b/MacMagazine/MacMagazine/Features/News/DeepLinkNewsDetailView.swift index 04c86ca2..81540757 100644 --- a/MacMagazine/MacMagazine/Features/News/DeepLinkNewsDetailView.swift +++ b/MacMagazine/MacMagazine/Features/News/DeepLinkNewsDetailView.swift @@ -32,6 +32,7 @@ struct DeepLinkNewsDetailView: View { Button(action: onDismiss, label: { Image(systemName: "xmark") }) .tint(.primary) + .accessibilityLabel("Fechar") } ToolbarItem(placement: .automatic) { favoriteView diff --git a/MacMagazine/MacMagazine/Features/Social/SocialView.swift b/MacMagazine/MacMagazine/Features/Social/SocialView.swift index f1b97ed8..f5364dd8 100644 --- a/MacMagazine/MacMagazine/Features/Social/SocialView.swift +++ b/MacMagazine/MacMagazine/Features/Social/SocialView.swift @@ -54,7 +54,7 @@ private extension SocialView { var optionsView: some View { @Bindable var bindableViewModel = viewModel - Picker("", selection: $bindableViewModel.social) { + Picker("Seção social", selection: $bindableViewModel.social) { ForEach(viewModel.settingsViewModel.social, id: \.self) { option in Text(option.rawValue).tag(option) } diff --git a/MacMagazine/Widget/Extensions/WidgetAccessibility+View.swift b/MacMagazine/Widget/Extensions/WidgetAccessibility+View.swift new file mode 100644 index 00000000..d90dc48c --- /dev/null +++ b/MacMagazine/Widget/Extensions/WidgetAccessibility+View.swift @@ -0,0 +1,48 @@ +import SwiftUI +import WidgetKit + +private enum WidgetAccessibility { + static let label = "MacMagazine" + static let hint = "Toque para abrir as notícias" +} + +// MARK: - Generic (inline / rectangular) + +extension View { + + func widgetAccessibility( + url: URL?, + lastPostTitle: String, + children: AccessibilityChildBehavior? = nil + ) -> some View { + Group { + if let children { + self + .accessibilityElement(children: children) + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue("Última notícia: \(lastPostTitle)") + .accessibilityHint(WidgetAccessibility.hint) + } else { + self + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue("Última notícia: \(lastPostTitle)") + .accessibilityHint(WidgetAccessibility.hint) + } + } + .widgetURL(url) + } + + // MARK: - Rectangular (multi-item) + + func widgetRectangularAccessibility( + url: URL?, + accessibilityValue: String + ) -> some View { + self + .accessibilityElement(children: .combine) + .accessibilityLabel(WidgetAccessibility.label) + .accessibilityValue(accessibilityValue) + .accessibilityHint(WidgetAccessibility.hint) + .widgetURL(url) + } +} diff --git a/MacMagazine/Widget/Views/WidgetElementView.swift b/MacMagazine/Widget/Views/WidgetElementView.swift index 30694aba..f11881d3 100644 --- a/MacMagazine/Widget/Views/WidgetElementView.swift +++ b/MacMagazine/Widget/Views/WidgetElementView.swift @@ -16,10 +16,18 @@ struct WidgetElementView: View { @ViewBuilder var body: some View { switch widgetFamily { - case .systemSmall: smallWidget.widgetURL(post.url) - case .accessoryInline: accessoryInlineWidget - case .accessoryRectangular: accessoryRectangularWidget - default: Link(destination: post.url) { content } + case .systemSmall: + smallWidget + .widgetAccessibility(url: post.url, lastPostTitle: post.title) + case .accessoryInline: + accessoryInlineWidget + .widgetAccessibility(url: post.url, lastPostTitle: post.title) + case .accessoryRectangular: + accessoryRectangularWidget + .widgetAccessibility(url: post.url, lastPostTitle: post.title) + default: + Link(destination: post.url) { content } + .widgetAccessibility(url: nil, lastPostTitle: post.title) } } } diff --git a/MacMagazine/Widget/Views/WidgetView.swift b/MacMagazine/Widget/Views/WidgetView.swift index 6fcc4a9f..a0854c96 100644 --- a/MacMagazine/Widget/Views/WidgetView.swift +++ b/MacMagazine/Widget/Views/WidgetView.swift @@ -40,6 +40,10 @@ struct WidgetView: View { id: \.self) { index in WidgetElementView(post: content[index]) }.header(title: "Últimas notícias", spacing: widgetFamily.spacing) + .widgetRectangularAccessibility( + url: content.first?.url, + accessibilityValue: content.prefix(quantity).map(\.title).joined(separator: "; ") + ) .trackScreen(AnalyticsConstants.Screen.widget(widgetFamily.description).name, analytics: analytics) } }