Added accessibility support to in-app notifications on the ContentViewModel view model in the Sample app.

This commit is contained in:
2026-07-05 13:06:43 +02:00
parent 6373fad0cd
commit 6bcb88c944
4 changed files with 50 additions and 33 deletions
@@ -47,6 +47,18 @@
}
}
},
"view.recording.picker.locale.hint" : {
"comment" : "The accessibility hint of the toolbar menu that picks the locale to transcribe.",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Picks the language of the spoken audio to transcribe."
}
}
}
},
"view.recording.picker.locale.title" : {
"localizations" : {
"en" : {
+24
View File
@@ -47,3 +47,27 @@ extension AppNotification {
case started
}
}
// MARK: - Properties
extension AppNotification {
/// The system symbol of the icon matching the notification's event.
var imageSymbol: String {
switch event {
case .cancelled: "xmark.circle"
case .failed: "exclamationmark.triangle"
case .started: "arrow.down.circle"
}
}
/// The localized message of the notification, naming the locale of the speech model, from the app's string catalog.
var textMessage: LocalizedStringResource {
switch event {
case .cancelled: .viewRecordingNotificationDownloadCancelled(localeName)
case .failed: .viewRecordingNotificationDownloadFailed(localeName)
case .started: .viewRecordingNotificationDownloadStarted(localeName)
}
}
}
@@ -1,5 +1,5 @@
import Accessibility
import Foundation
import Notifying
import Observation
import OSLog
import Recording
@@ -153,8 +153,8 @@ private extension ContentView.Model {
await SpeechTranscriber.supportedLocale(equivalentTo: locale)
}
/// Posts a notification for a download event of the given locale, scheduling its automatic dismissal a few seconds later
/// the only way a notification is dismissed.
/// Posts a notification for a download event of the given locale, announcing it to assistive technologies and scheduling its
/// automatic dismissal a few seconds later the only way a notification is dismissed.
///
/// - Parameters:
/// - event: The download event to notify.
@@ -170,6 +170,10 @@ private extension ContentView.Model {
notifications.append(notification)
AccessibilityNotification.Announcement(
String(localized: notification.textMessage)
).post()
Task {
try? await Task.sleep(for: Constant.Delay.dismissal)
+7 -30
View File
@@ -25,7 +25,8 @@ struct ContentView: View {
/// transcribed text of every processed recording or a content unavailable message when the transcription is empty closable
/// through its toolbar button or a swipe. The model's in-app notifications overlay the feature, stacking downward just below the
/// navigation bar; each one slides in from the leading edge when posted and out again when its scheduled dismissal arrives, without
/// displacing the recording controls underneath.
/// displacing the recording controls underneath. Every posted notification is also announced to assistive technologies, since the
/// banners themselves are transient.
var body: some View {
NavigationStack {
RecordingView(
@@ -100,6 +101,7 @@ private extension ContentView {
)
}
.disabled(model.locales.isEmpty)
.accessibilityHint(Text("view.recording.picker.locale.hint"))
}
/// The stack of the model's in-app notifications, each one sliding in from the leading edge when posted and out again when
@@ -116,10 +118,14 @@ private extension ContentView {
Text(notification.textMessage)
} icon: {
Image(systemName: notification.imageSymbol)
.accessibilityHidden(true)
}
.labelStyle(.notification(
event: notification.event
))
.accessibilityElement(
children: .combine
)
.transition(.move(
edge: .leading
).combined(
@@ -181,35 +187,6 @@ private extension ContentView {
}
// MARK: - AppNotification+Extensions
private extension AppNotification {
// MARK: Properties
/// The system symbol of the icon matching the notification's event.
var imageSymbol: String {
switch event {
case .cancelled: "xmark.circle"
case .failed: "exclamationmark.triangle"
case .started: "arrow.down.circle"
}
}
// MARK: Methods
/// The localized message of the notification, naming the locale of the speech model, from the app's string catalog.
var textMessage: LocalizedStringResource {
switch event {
case .cancelled: .viewRecordingNotificationDownloadCancelled(localeName)
case .failed: .viewRecordingNotificationDownloadFailed(localeName)
case .started: .viewRecordingNotificationDownloadStarted(localeName)
}
}
}
// MARK: - Constants
/// The constant values used across the view.