Added a discard button and error handling to the RecordingView view in the Recording package target.

This commit is contained in:
2026-07-04 15:59:30 +02:00
parent 7ec49264e6
commit b0c6e25639
7 changed files with 339 additions and 14 deletions
@@ -51,9 +51,9 @@ public struct RecordingView: View {
// MARK: Body
/// The content of the view: the timer label above the main and send buttons, with the transcription of every processed recording
/// forwarded to the closure given at initialization, and an alert surfacing any error of the recording flow offering to open the
/// app's settings when the microphone permission was denied.
/// The content of the view: the timer label above the main, discard, and send buttons, with the transcription of every processed
/// recording forwarded to the closure given at initialization, and an alert surfacing any error of the recording flow offering to
/// open the app's settings when the microphone permission was denied.
public var body: some View {
VStack(
spacing: Constant.Spacing.stack
@@ -69,6 +69,8 @@ public struct RecordingView: View {
.easeInOut,
value: model.textTimer
)
.accessibilityLabel(Constant.Text.labelTimer)
.accessibilityValue(model.textTimerAccessible)
}
GlassEffectContainer {
@@ -86,6 +88,21 @@ public struct RecordingView: View {
invertStyle: true
))
.disabled(model.shouldDisableMain)
.accessibilityLabel(labelMain)
if model.shouldShowDiscard {
Button {
model.pressedDiscard()
} label: {
Image(systemName: Constant.Symbol.discard)
.resizable()
.scaledToFit()
}
.buttonStyle(.recording(
invertStyle: true
))
.accessibilityLabel(Constant.Text.labelDiscard)
}
if model.shouldShowSend {
Button {
@@ -102,6 +119,7 @@ public struct RecordingView: View {
invertStyle: !model.isProcessing
))
.disabled(model.isProcessing)
.accessibilityLabel(labelSend)
}
}
}
@@ -149,6 +167,26 @@ public struct RecordingView: View {
private extension RecordingView {
// MARK: Computed
/// The accessibility label for the main button, matching its action in the current state.
var labelMain: String {
switch model.state {
case .notRecording, .processing: Constant.Text.labelRecord
case .recording: Constant.Text.labelPause
case .paused: Constant.Text.labelResume
}
}
/// The accessibility label for the send button: the sending action while paused, the ongoing processing otherwise.
var labelSend: String {
model.isProcessing
? Constant.Text.labelProcessing
: Constant.Text.labelSend
}
// MARK: Methods
/// Returns the message describing the given error for the alert.
///
/// - Parameter error: The error to describe.
@@ -157,6 +195,7 @@ private extension RecordingView {
for error: RecordingError
) -> String {
switch error {
case .assetsUnavailable: Constant.Text.messageAssetsUnavailable
case .captureFailed: Constant.Text.messageCaptureFailed
case .permissionDenied: Constant.Text.messagePermissionDenied
case .transcriptionFailed: Constant.Text.messageTranscriptionFailed
@@ -180,6 +219,12 @@ private enum Constant {
static let stack: CGFloat = 8
}
/// The symbol constants.
enum Symbol {
/// The system symbol of the discard button's image.
static let discard = "trash"
}
/// The text constants, localized through the package's string catalog.
enum Text {
/// The title of the alert's dismissal button.
@@ -187,11 +232,51 @@ private enum Constant {
localized: "view.recording.alert.error.button.ok",
bundle: .module
)
/// The accessibility label of the discard button.
static let labelDiscard = String(
localized: "view.recording.button.discard.label",
bundle: .module
)
/// The accessibility label of the main button while recording.
static let labelPause = String(
localized: "view.recording.button.main.label.pause",
bundle: .module
)
/// The accessibility label of the send button while processing.
static let labelProcessing = String(
localized: "view.recording.button.send.label.processing",
bundle: .module
)
/// The accessibility label of the main button while not recording.
static let labelRecord = String(
localized: "view.recording.button.main.label.record",
bundle: .module
)
/// The accessibility label of the main button while paused.
static let labelResume = String(
localized: "view.recording.button.main.label.resume",
bundle: .module
)
/// The accessibility label of the send button while paused.
static let labelSend = String(
localized: "view.recording.button.send.label.send",
bundle: .module
)
/// The accessibility label of the timer.
static let labelTimer = String(
localized: "view.recording.timer.label",
bundle: .module
)
/// The title of the alert's button that opens the app's settings.
static let buttonSettings = String(
localized: "view.recording.alert.error.button.settings",
bundle: .module
)
/// The message describing unavailable speech model assets.
static let messageAssetsUnavailable = String(
localized: "view.recording.alert.error.message.assets",
bundle: .module
)
/// The message describing a failure of the audio capture.
static let messageCaptureFailed = String(
localized: "view.recording.alert.error.message.capture",