Added accessibility support to in-app notifications on the ContentViewModel view model in the Sample app.
This commit is contained in:
@@ -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" : {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user