Răsfoiți Sursa

feat(recording): 增加录音暂停/继续控制、优化状态指示器与背景双击结束快捷手势

bob.yuxinyang 1 lună în urmă
părinte
comite
3969b5fea9

+ 1 - 0
CelestiaTrace/Services/Audio/RealAudioRecorder.swift

@@ -112,6 +112,7 @@ final class RealAudioRecorder: AudioRecorderProtocol {
         guard isRecording else { return }
         audioRecorder?.pause()
         isPaused = true
+        currentAmplitude = 0
         invalidateTimer()
     }
     

+ 72 - 26
CelestiaTrace/Views/Recording/ActiveRecordingView.swift

@@ -45,6 +45,10 @@ struct ActiveRecordingView: View {
             // Dynamic clean background
             Color.spaceBlack
                 .ignoresSafeArea()
+                .contentShape(Rectangle())
+                .onTapGesture(count: 2) {
+                    endRecording()
+                }
 
             VStack(spacing: 0) {
                 recordNameSection
@@ -76,8 +80,8 @@ struct ActiveRecordingView: View {
                     .padding(.horizontal, 48)
                     .padding(.bottom, 32)
 
-                // End Recording
-                endRecordingButton
+                // Recording Controls
+                recordingControlButtons
                     .padding(.bottom, 24)
             }
             .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -238,16 +242,16 @@ struct ActiveRecordingView: View {
                 TimelineView(.periodic(from: .now, by: 1.0)) { context in
                     let isEven = Int(context.date.timeIntervalSince1970) % 2 == 0
                     Circle()
-                        .fill(recordingVM.isRecording ? Color.recordingRed : Color.secondary)
+                        .fill(recordingVM.isRecording && !recordingVM.isPaused ? Color.recordingRed : Color.secondary)
                         .frame(width: 8, height: 8)
-                        .opacity(recordingVM.isRecording ? (isEven ? 1.0 : 0.3) : 0.6)
+                        .opacity(recordingVM.isRecording && !recordingVM.isPaused ? (isEven ? 1.0 : 0.3) : 0.6)
                         .animation(.easeInOut(duration: 0.5), value: isEven)
                 }
                 .frame(width: 10, height: 10)
 
                 Text(recordingVM.isPaused ? "已暂停" : recordingVM.statusMessage)
                     .font(.system(size: 11, weight: .semibold, design: .monospaced))
-                    .foregroundStyle(recordingVM.isRecording ? Color.recordingRed : Color.secondary)
+                    .foregroundStyle(recordingVM.isRecording && !recordingVM.isPaused ? Color.recordingRed : Color.secondary)
                     .tracking(1.2)
             }
 
@@ -387,33 +391,64 @@ struct ActiveRecordingView: View {
         .buttonStyle(.plain)
     }
 
-    // MARK: - End Recording Button
+    // MARK: - Recording Controls
 
-    private var endRecordingButton: some View {
-        Button {
-            endRecording()
-        } label: {
-            VStack(spacing: 6) {
-                Text("双击结束录制")
-                    .font(.system(size: 13, weight: .medium))
-                    .foregroundStyle(Color.recordingRed)
-                    .padding(.horizontal, 24)
-                    .padding(.vertical, 10)
-                    .businessBorder(cornerRadius: 8)
-                
-                Text("双击即可完成本次现场录音")
-                    .font(.system(size: 8, weight: .medium, design: .monospaced))
-                    .foregroundStyle(Color.secondary.opacity(0.5))
-                    .tracking(1.5)
+    private var recordingControlButtons: some View {
+        HStack(spacing: 52) {
+            recordingControlButton(
+                icon: recordingVM.isPaused ? "play.fill" : "pause.fill",
+                label: recordingVM.isPaused ? "继续记录" : "暂停",
+                tint: .primary
+            ) {
+                toggleRecordingPause()
             }
-        }
-        .highPriorityGesture(
-            TapGesture(count: 2).onEnded {
+
+            recordingControlButton(
+                icon: "stop.fill",
+                label: isEndingRecording ? "正在结束" : "结束记录",
+                tint: .recordingRed,
+                isProminent: true
+            ) {
                 endRecording()
             }
-        )
+        }
+    }
+
+    private func recordingControlButton(
+        icon: String,
+        label: String,
+        tint: Color,
+        isProminent: Bool = false,
+        action: @escaping () -> Void
+    ) -> some View {
+        Button(action: action) {
+            VStack(spacing: 9) {
+                ZStack {
+                    Circle()
+                        .fill(isProminent ? tint : Color.primary.opacity(0.02))
+                        .frame(width: 64, height: 64)
+
+                    if !isProminent {
+                        Circle()
+                            .stroke(Color.primary.opacity(0.18), lineWidth: 1)
+                            .frame(width: 64, height: 64)
+                    }
+
+                    Image(systemName: icon)
+                        .font(.system(size: 21, weight: .semibold))
+                        .foregroundStyle(isProminent ? Color.white : tint)
+                }
+
+                Text(label)
+                    .font(.system(size: 11, weight: .medium))
+                    .foregroundStyle(tint)
+            }
+            .frame(width: 88)
+        }
         .buttonStyle(.plain)
         .disabled(isEndingRecording)
+        .opacity(isEndingRecording && !isProminent ? 0.4 : 1)
+        .accessibilityLabel(label)
     }
 
     // MARK: - Note Input Sheet
@@ -501,6 +536,17 @@ struct ActiveRecordingView: View {
         showNoteSheet = false
     }
 
+    private func toggleRecordingPause() {
+        guard !isEndingRecording else { return }
+        HapticManager.trigger(.tapFeedback)
+
+        if recordingVM.isPaused {
+            recordingVM.resumeRecording()
+        } else {
+            recordingVM.pauseRecording()
+        }
+    }
+
     private func endRecording() {
         guard !isEndingRecording else { return }
         commitRecordName()