133 lines
4.7 KiB
Swift
133 lines
4.7 KiB
Swift
import SwiftUI
|
|
|
|
/// 手表主页:一键录音、今日数、最近录音;录完传 iPhone,整理在手机端完成。
|
|
struct ContentView: View {
|
|
@EnvironmentObject private var sessionManager: WatchSessionManager
|
|
@StateObject private var recorder = WatchRecorder()
|
|
@State private var phase = Phase.idle
|
|
@State private var notes = WatchStore.load()
|
|
@State private var lastURL: URL?
|
|
|
|
enum Phase {
|
|
case idle, recording, sending, sent, error(String)
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 12) {
|
|
switch phase {
|
|
case .idle:
|
|
recordButton
|
|
Text("今日速记 \(sessionManager.todayCount) 条")
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
case .recording:
|
|
recordingView
|
|
case .sending:
|
|
ProgressView("正在传到 iPhone…")
|
|
case .sent:
|
|
Label("已传到 iPhone", systemImage: "checkmark.circle.fill")
|
|
.font(.caption)
|
|
.foregroundStyle(.green)
|
|
Button("再录一条") { phase = .idle }
|
|
.font(.caption)
|
|
case .error(let message):
|
|
Text(message)
|
|
.font(.caption)
|
|
.foregroundStyle(.red)
|
|
.multilineTextAlignment(.center)
|
|
if let url = lastURL {
|
|
Button("重试传输") { send(url: url) }
|
|
.font(.caption)
|
|
}
|
|
Button("返回") { phase = .idle }
|
|
.font(.caption)
|
|
}
|
|
|
|
if !notes.isEmpty {
|
|
Divider()
|
|
ForEach(notes.prefix(5)) { note in
|
|
HStack {
|
|
Text(note.createdAt, format: .dateTime.hour().minute())
|
|
.font(.caption2)
|
|
Spacer()
|
|
Text(formatTime(note.duration))
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
Image(systemName: note.sent ? "checkmark.icloud.fill" : "paperplane")
|
|
.font(.caption2)
|
|
.foregroundStyle(note.sent ? .green : .secondary)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
private var recordButton: some View {
|
|
Button {
|
|
Task {
|
|
guard await recorder.requestPermission() else {
|
|
phase = .error("没有麦克风权限,请在手表设置里开启。")
|
|
return
|
|
}
|
|
do {
|
|
try recorder.start()
|
|
phase = .recording
|
|
} catch {
|
|
phase = .error("录音启动失败:\(error.localizedDescription)")
|
|
}
|
|
}
|
|
} label: {
|
|
ZStack {
|
|
Circle().fill(Color.red)
|
|
Image(systemName: "mic.fill")
|
|
.font(.title2)
|
|
.foregroundStyle(.white)
|
|
}
|
|
.frame(width: 72, height: 72)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
|
|
private var recordingView: some View {
|
|
VStack(spacing: 10) {
|
|
HStack(spacing: 4) {
|
|
Circle().fill(Color.red).frame(width: 8, height: 8)
|
|
Text("录音中 \(formatTime(recorder.elapsed))")
|
|
.font(.caption)
|
|
}
|
|
Button("停止并发送") {
|
|
guard let url = recorder.stop() else {
|
|
phase = .error("录音文件没生成,请再试一次。")
|
|
return
|
|
}
|
|
lastURL = url
|
|
send(url: url)
|
|
}
|
|
.font(.caption)
|
|
.tint(.red)
|
|
}
|
|
}
|
|
|
|
private func send(url: URL) {
|
|
phase = .sending
|
|
sessionManager.transfer(url: url) { result in
|
|
switch result {
|
|
case .success:
|
|
notes.insert(WatchNote(createdAt: Date(), duration: recorder.elapsed, sent: true), at: 0)
|
|
WatchStore.append(notes[0])
|
|
phase = .sent
|
|
case .failure(let error):
|
|
phase = .error(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func formatTime(_ time: TimeInterval) -> String {
|
|
let total = Int(time)
|
|
return String(format: "%d:%02d", total / 60, total % 60)
|
|
}
|
|
}
|