diff --git a/Packages/Features/Sources/Recording/RecordingService.swift b/Packages/Features/Sources/Recording/RecordingService.swift new file mode 100644 index 0000000..8db1ab8 --- /dev/null +++ b/Packages/Features/Sources/Recording/RecordingService.swift @@ -0,0 +1,53 @@ +import Foundation + +/// A service that captures audio from a microphone for the Recording feature. +/// +/// ``RecordingView`` attaches the service to its model at initialization, so the audio capture can be swapped without touching the feature's +/// state machine — for example, with a real microphone backend in the app, or with a mock in unit tests. +public protocol RecordingService: Sendable { + + /// Starts a new audio recording from the microphone. + func start() async throws + + /// Pauses the ongoing recording. + func pause() async throws + + /// Resumes a paused recording. + func resume() async throws + + /// Stops the recording. + /// + /// - Returns: The audio captured since the recording started. + func stop() async throws -> Data + +} + +// MARK: - Services + +/// The recording service used by default, which simulates the audio capture without touching a microphone. +public struct SimulatedRecordingService: RecordingService { + + // MARK: Initializers + + /// Creates a simulated recording service. + public init() {} + + // MARK: Methods + + /// Simulates the start of an audio recording. + public func start() async throws {} + + /// Simulates the pause of an ongoing recording. + public func pause() async throws {} + + /// Simulates the resumption of a paused recording. + public func resume() async throws {} + + /// Simulates the stop of a recording. + /// + /// - Returns: An empty audio payload. + public func stop() async throws -> Data { + .init() + } + +} diff --git a/Packages/Features/Sources/Recording/TranscribingService.swift b/Packages/Features/Sources/Recording/TranscribingService.swift new file mode 100644 index 0000000..d71ef7e --- /dev/null +++ b/Packages/Features/Sources/Recording/TranscribingService.swift @@ -0,0 +1,56 @@ +import Foundation + +/// A service that transcribes recorded audio into text for the Recording feature. +/// +/// ``RecordingView`` attaches the service to its model at initialization, so the transcription can be swapped without touching the feature's +/// state machine — for example, with a real transcription backend in the app, or with a fast mock in unit tests. +public protocol TranscribingService: Sendable { + + /// Transcribes the given recorded audio into text. + /// + /// - Parameter audio: The recorded audio to transcribe. + /// - Returns: The transcription of the recorded audio. + func transcribe(_ audio: Data) async throws -> String + +} + +// MARK: - Services + +/// The transcribing service used by default, which simulates the transcription work. +public struct SimulatedTranscribingService: TranscribingService { + + // MARK: Initializers + + /// Creates a simulated transcribing service. + public init() {} + + // MARK: Methods + + /// Simulates the transcription of the given recorded audio with a two-second delay. + /// + /// - Parameter audio: The recorded audio to transcribe. + /// - Returns: A dummy transcription. + public func transcribe(_ audio: Data) async throws -> String { + try await Task.sleep(for: Constant.Delay.transcribing) + + return Constant.Text.transcription + } + +} + +// MARK: - Constants + +/// The constant values used across the transcribing services. +private enum Constant { + /// The delay constants. + enum Delay { + /// The duration of the simulated transcription work. + static let transcribing: Duration = .seconds(2) + } + + /// The text constants. + enum Text { + /// The dummy transcription of a recorded audio. + static let transcription = "This is a dummy transcription of the recorded audio." + } +} diff --git a/README.md b/README.md index 1595aac..053b00f 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Attendi/ │ ├── RecordingView.swift │ ├── RecordingViewModel.swift │ ├── RecordingButtonStyle.swift + │ ├── RecordingService.swift + │ ├── TranscribingService.swift │ └── Images.xcassets # Record, pause, and send icons └── Tests/ ├── Recording/ @@ -81,6 +83,21 @@ text content transition. background, shrinks while pressed, and dims while disabled. The label and its padding scale with Dynamic Type via `@ScaledMetric`, and an `invertStyle` flag controls whether the label's color scheme is inverted for contrast. +### Services + +The actual recording work is abstracted behind two protocols, injected into `RecordingView` at initialization and +attached to its view model: + +- **`RecordingService`** — captures the audio from a microphone, with throwing async `start`, `pause`, `resume`, and + `stop` methods; `stop` returns the captured audio. +- **`TranscribingService`** — transcribes the captured audio into text through a throwing async `transcribe` method; + the view model stores the result once processing finishes. + +The default `SimulatedRecordingService` and `SimulatedTranscribingService` fake the work (the latter with a two-second +delay and a dummy transcription); the unit tests inject fast mocks, and real backends can be plugged in the same way +without touching the feature's state machine. When either service fails, the view model falls back to the +not-recording state. + ## Testing The view model is covered by Swift Testing suites in `RecordingViewModelTests.swift`, grouped with nested `@Suite`