Files
notes/Notes Watch App/Views/RecordView.swift
T
rzen 86d74806c9 Add voice notes: on-device transcription, multi-language, watch capture
- Schema v2: Note.AudioInfo, .m4a sidecar next to the note JSON in iCloud
- AudioRecorderService (iOS/macOS) + SpeechAnalyzer transcription pipeline
  with enabled-language set and confidence-based auto-pick, re-transcribe menu
- Settings > Transcription > Languages with asset download/reserve handling
- watchOS app + accessory complication: one-button recorder, WCSession
  transferFile to phone, WatchInbox staging, direct-upsert ingestion
- Player UI, transcription status chips, quick-capture mic in notes list
- Version 0.2, changelog + README updated
2026-07-15 13:30:43 -04:00

85 lines
2.9 KiB
Swift

import SwiftUI
/// The watch app's single screen: a big record/stop button, an elapsed timer
/// while recording, and a footer counting recordings still waiting to reach the
/// iPhone. Shows a permission-denied state when the mic is refused.
struct RecordView: View {
@Environment(WatchAppServices.self) private var services
var body: some View {
let recorder = services.recorder
VStack(spacing: 10) {
if recorder.permissionDenied {
permissionDenied
} else {
recordButton(isRecording: recorder.isRecording)
if recorder.isRecording {
Text(Self.elapsedString(recorder.elapsed))
.font(.system(.title3, design: .rounded).monospacedDigit())
.foregroundStyle(.secondary)
} else {
Text("Tap to record")
.font(.footnote)
.foregroundStyle(.secondary)
}
pendingFooter
}
}
.padding()
.containerBackground(.red.gradient, for: .navigation)
.navigationTitle("Contextful")
}
private func recordButton(isRecording: Bool) -> some View {
Button {
services.toggleRecording()
} label: {
ZStack {
Circle()
.fill(.red)
.frame(width: 96, height: 96)
Image(systemName: isRecording ? "stop.fill" : "mic.fill")
.font(.system(size: 40))
.foregroundStyle(.white)
.contentTransition(.symbolEffect(.replace))
}
}
.buttonStyle(.plain)
.accessibilityLabel(isRecording ? "Stop recording" : "Start recording")
}
@ViewBuilder
private var pendingFooter: some View {
let count = services.transfer.pendingCount
if count > 0 {
Label(
count == 1 ? "1 waiting for iPhone" : "\(count) waiting for iPhone",
systemImage: "arrow.up.circle"
)
.font(.caption2)
.foregroundStyle(.secondary)
}
}
private var permissionDenied: some View {
VStack(spacing: 8) {
Image(systemName: "mic.slash.fill")
.font(.title)
.foregroundStyle(.secondary)
Text("Microphone access is off")
.font(.headline)
.multilineTextAlignment(.center)
Text("Enable it in the Watch app on iPhone under Contextful.")
.font(.caption2)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
}
/// mm:ss for the elapsed readout.
static func elapsedString(_ interval: TimeInterval) -> String {
let total = Int(interval)
return String(format: "%02d:%02d", total / 60, total % 60)
}
}