- Add default values to all SwiftData attributes (CloudKit requirement) - Make memos relationship optional (CloudKit requirement) - Add allMemos computed property for safe unwrapping - Add remote-notification background mode for CloudKit push sync Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
2.2 KiB
Swift
94 lines
2.2 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
@Observable
|
|
@MainActor
|
|
final class RecordingViewModel {
|
|
var isRecording = false
|
|
var recordingDuration: TimeInterval = 0
|
|
var error: Error?
|
|
|
|
private let recorder = AudioRecorderService()
|
|
private let transcriptionService = TranscriptionService()
|
|
private var currentRecordingURL: URL?
|
|
|
|
var formattedDuration: String {
|
|
let minutes = Int(recorder.recordingDuration) / 60
|
|
let seconds = Int(recorder.recordingDuration) % 60
|
|
return String(format: "%d:%02d", minutes, seconds)
|
|
}
|
|
|
|
func startRecording() {
|
|
do {
|
|
currentRecordingURL = try recorder.startRecording()
|
|
isRecording = true
|
|
error = nil
|
|
} catch {
|
|
self.error = error
|
|
}
|
|
}
|
|
|
|
func stopRecording(context: ModelContext) {
|
|
guard let result = recorder.stopRecording() else { return }
|
|
isRecording = false
|
|
|
|
let today = Calendar.current.startOfDay(for: .now)
|
|
let entry = fetchOrCreateEntry(for: today, context: context)
|
|
|
|
let memo = VoiceMemo(
|
|
audioFileName: result.url.lastPathComponent,
|
|
duration: result.duration
|
|
)
|
|
context.insert(memo)
|
|
memo.entry = entry
|
|
if entry.memos == nil { entry.memos = [] }
|
|
entry.memos?.append(memo)
|
|
entry.updatedAt = .now
|
|
|
|
try? context.save()
|
|
|
|
let audioURL = memo.audioURL
|
|
Task {
|
|
await transcribeMemo(memo, audioURL: audioURL)
|
|
}
|
|
}
|
|
|
|
private func fetchOrCreateEntry(for date: Date, context: ModelContext) -> DiaryEntry {
|
|
let descriptor = FetchDescriptor<DiaryEntry>()
|
|
|
|
if let entries = try? context.fetch(descriptor) {
|
|
let match = entries.first { entry in
|
|
Calendar.current.isDate(entry.date, inSameDayAs: date)
|
|
}
|
|
if let match { return match }
|
|
}
|
|
|
|
let entry = DiaryEntry(date: date)
|
|
context.insert(entry)
|
|
return entry
|
|
}
|
|
|
|
private func transcribeMemo(_ memo: VoiceMemo, audioURL: URL) async {
|
|
if transcriptionService.authorizationStatus == .notDetermined {
|
|
await transcriptionService.requestAuthorization()
|
|
}
|
|
|
|
guard transcriptionService.authorizationStatus == .authorized else {
|
|
return
|
|
}
|
|
|
|
memo.isTranscribing = true
|
|
|
|
do {
|
|
let transcript = try await transcriptionService.transcribe(audioURL: audioURL)
|
|
memo.transcript = transcript
|
|
memo.isTranscribing = false
|
|
memo.entry?.updatedAt = .now
|
|
} catch {
|
|
memo.isTranscribing = false
|
|
self.error = error
|
|
}
|
|
}
|
|
}
|