Propagated the Transcription type and locale binding through the RecordingView view and its view model in the Recording package target.

This commit is contained in:
2026-07-04 12:11:24 +02:00
parent 68c070948e
commit 20cbda7b27
6 changed files with 47 additions and 30 deletions
@@ -1,7 +1,7 @@
import Foundation
/// A transcription of a processed recording.
public struct Transcription: Identifiable, Sendable {
public struct Transcription: Equatable, Identifiable, Sendable {
// MARK: Properties
@@ -21,7 +21,11 @@ extension RecordingView {
private(set) var elapsedSeconds: Int = 0
/// The transcription of the last processed recording, or `nil` when none has been processed yet.
private(set) var textTranscription: String?
private(set) var transcription: Transcription?
/// The binding to the locale of the spoken language to transcribe.
@ObservationIgnored
private let locale: Binding<Locale>
/// The service that captures the audio from a microphone.
@ObservationIgnored
@@ -42,12 +46,15 @@ extension RecordingView {
/// - Parameters:
/// - recorder: The service that captures the audio from a microphone. Defaults to ``DummyRecordingService``.
/// - transcribe: The callable service that transcribes the recorded audio into text. Defaults to ``DummyTranscribing``.
/// - locale: The binding to the locale of the spoken language to transcribe. Defaults to a constant binding to the user's current locale.
init(
recorder: any RecordingService = DummyRecordingService(),
transcribe: any Transcribing = DummyTranscribing()
transcribe: any Transcribing = DummyTranscribing(),
locale: Binding<Locale> = .constant(.current)
) {
self.recorder = recorder
self.transcribe = transcribe
self.locale = locale
}
// MARK: Computed
@@ -175,7 +182,7 @@ private extension RecordingView.Model {
// MARK: Methods
/// Processes the recorded input: it stops the audio capture, transcribes the captured audio, and publishes the result in ``textTranscription``;
/// Processes the recorded input: it stops the audio capture, transcribes the captured audio, and publishes the result in ``transcription``;
/// when finished or when either service fails it resets ``elapsedSeconds`` and returns the model to the not-recording state.
func processInput() async {
guard state == .processing else {
@@ -185,12 +192,12 @@ private extension RecordingView.Model {
do {
let audio = try await recorder.stop()
textTranscription = try await transcribe(
transcription = try await transcribe(
audio,
locale: .current
).text
locale: locale.wrappedValue
)
} catch {
textTranscription = nil
transcription = nil
}
elapsedSeconds = 0
@@ -218,14 +225,14 @@ private extension RecordingView.Model {
/// Starts the timer task, which increments ``elapsedSeconds`` once per second until it is cancelled. Any previously running timer task is cancelled first.
///
/// - Parameter shouldRestartTimer: Whether ``elapsedSeconds`` and ``textTranscription`` should be cleared before the timer starts,
/// - Parameter shouldRestartTimer: Whether ``elapsedSeconds`` and ``transcription`` should be cleared before the timer starts,
/// which is the case for a new recording as opposed to one resuming from a pause.
func startTimer(
_ shouldRestartTimer: Bool
) {
if shouldRestartTimer {
elapsedSeconds = 0
textTranscription = nil
transcription = nil
}
taskTimer?.cancel()
@@ -13,8 +13,8 @@ public struct RecordingView: View {
/// The model that owns the recording state and drives the view's controls.
@State private var model: Model
/// The closure invoked with the transcribed text of every processed recording.
private let onTranscription: (String) -> Void
/// The closure invoked with the transcription of every processed recording.
private let onTranscription: (Transcription) -> Void
// MARK: Initializers
@@ -23,15 +23,18 @@ public struct RecordingView: View {
/// - Parameters:
/// - recorder: The service that captures the audio from a microphone.
/// - transcriber: The service that transcribes the recorded audio into text.
/// - onTranscription: The closure invoked with the transcribed text of every processed recording. Defaults to a closure that does nothing.
/// - locale: The binding to the locale of the spoken language to transcribe.
/// - onTranscription: The closure invoked with the transcription of every processed recording. Defaults to a closure that does nothing.
public init(
recorder: any RecordingService,
transcriber: any Transcribing,
onTranscription: @escaping (String) -> Void = { _ in }
locale: Binding<Locale>,
onTranscription: @escaping (Transcription) -> Void
) {
self.model = .init(
recorder: recorder,
transcribe: transcriber
transcribe: transcriber,
locale: locale
)
self.onTranscription = onTranscription
}
@@ -101,7 +104,7 @@ public struct RecordingView: View {
)
}
.onChange(
of: model.textTranscription,
of: model.transcription,
initial: false
) { _, newValue in
if let newValue {
@@ -130,6 +133,9 @@ private enum Constant {
) {
RecordingView(
recorder: DummyRecordingService(),
transcriber: DummyTranscribing()
)
transcriber: DummyTranscribing(),
locale: .constant(.current)
) { _ in
// On transcription closure.
}
}