Surfaced recording flow errors to the user through an alert in the Recording package target.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import SwiftUI
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// A view that drives a recording session.
|
||||
///
|
||||
/// The view shows a main button that starts, pauses, and resumes a recording, with a label above it displaying the elapsed recording time.
|
||||
@@ -10,6 +14,9 @@ public struct RecordingView: View {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The action that opens a URL, used to open the app's settings from the error alert.
|
||||
@Environment(\.openURL) private var openURL
|
||||
|
||||
/// The model that owns the recording state and drives the view's controls.
|
||||
@State private var model: Model
|
||||
|
||||
@@ -45,7 +52,8 @@ 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.
|
||||
/// 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
|
||||
@@ -106,6 +114,53 @@ public struct RecordingView: View {
|
||||
onTranscription(newValue)
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
Constant.Text.titleError,
|
||||
isPresented: .init(
|
||||
get: { model.error != nil },
|
||||
set: { isPresented in
|
||||
if !isPresented {
|
||||
model.dismissedError()
|
||||
}
|
||||
}
|
||||
),
|
||||
presenting: model.error
|
||||
) { error in
|
||||
if error == .permissionDenied, let url = Constant.URLs.settings {
|
||||
Button(Constant.Text.buttonSettings) {
|
||||
openURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
Constant.Text.buttonOK,
|
||||
role: .cancel
|
||||
) {
|
||||
// Dismissal is handled by the presentation binding.
|
||||
}
|
||||
} message: { error in
|
||||
Text(message(for: error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private extension RecordingView {
|
||||
|
||||
/// Returns the message describing the given error for the alert.
|
||||
///
|
||||
/// - Parameter error: The error to describe.
|
||||
/// - Returns: The message describing the error.
|
||||
func message(
|
||||
for error: RecordingError
|
||||
) -> String {
|
||||
switch error {
|
||||
case .captureFailed: Constant.Text.messageCaptureFailed
|
||||
case .permissionDenied: Constant.Text.messagePermissionDenied
|
||||
case .transcriptionFailed: Constant.Text.messageTranscriptionFailed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -124,6 +179,52 @@ private enum Constant {
|
||||
/// The spacing between the elements of a stack.
|
||||
static let stack: CGFloat = 8
|
||||
}
|
||||
|
||||
/// The text constants, localized through the package's string catalog.
|
||||
enum Text {
|
||||
/// The title of the alert's dismissal button.
|
||||
static let buttonOK = String(
|
||||
localized: "view.recording.alert.error.button.ok",
|
||||
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 a failure of the audio capture.
|
||||
static let messageCaptureFailed = String(
|
||||
localized: "view.recording.alert.error.message.capture",
|
||||
bundle: .module
|
||||
)
|
||||
/// The message describing a denied microphone permission.
|
||||
static let messagePermissionDenied = String(
|
||||
localized: "view.recording.alert.error.message.permission",
|
||||
bundle: .module
|
||||
)
|
||||
/// The message describing a failure of the transcription.
|
||||
static let messageTranscriptionFailed = String(
|
||||
localized: "view.recording.alert.error.message.transcription",
|
||||
bundle: .module
|
||||
)
|
||||
/// The title of the error alert.
|
||||
static let titleError = String(
|
||||
localized: "view.recording.alert.error.title",
|
||||
bundle: .module
|
||||
)
|
||||
}
|
||||
|
||||
/// The URL constants.
|
||||
enum URLs {
|
||||
/// The location that opens the app's settings, where the microphone permission can be granted.
|
||||
static let settings: URL? = {
|
||||
#if os(macOS)
|
||||
URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone")
|
||||
#else
|
||||
URL(string: UIApplication.openSettingsURLString)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
Reference in New Issue
Block a user