Преглед изворни кода

feat(detail): implement session deletion logic and confirmation modal

bob.yuxinyang пре 1 месец
родитељ
комит
690862613a
1 измењених фајлова са 96 додато и 3 уклоњено
  1. 96 3
      CelestiaTrace/Views/Detail/SessionDetailView.swift

+ 96 - 3
CelestiaTrace/Views/Detail/SessionDetailView.swift

@@ -9,6 +9,7 @@ import UIKit
 /// Redesigned to use a minimalist wireframe business style.
 struct SessionDetailView: View {
     @Environment(\.modelContext) private var modelContext
+    @Environment(\.dismiss) private var dismiss
     @ObservedObject private var bleManager: BLEManager = .shared
     @ObservedObject private var authManager: AuthManager = .shared
     @ObservedObject private var syncManager: SyncManager = .shared
@@ -42,6 +43,9 @@ struct SessionDetailView: View {
     @State private var showLargeSyncConfirmation = false
     @State private var showSyncInfo = false
     @State private var hasApprovedLargeTransfer = false
+    @State private var showDeleteConfirmation = false
+    @State private var deletionError: String?
+    @State private var isDeletingSession = false
 
     var body: some View {
         ZStack {
@@ -62,9 +66,11 @@ struct SessionDetailView: View {
                         .padding(.horizontal, 16)
 
                     // Section 3: Chrono Feed
-                    chronoFeedSection
-                        .padding(.horizontal, 16)
-                        .padding(.bottom, 32)
+                    if !chronoFeedItems.isEmpty {
+                        chronoFeedSection
+                            .padding(.horizontal, 16)
+                            .padding(.bottom, 32)
+                    }
                 }
             }
         }
@@ -104,6 +110,29 @@ struct SessionDetailView: View {
         } message: {
             Text("当前记录没有可用的本地录音文件,请先完成录制或同步录音。")
         }
+        .alert("确认删除?", isPresented: $showDeleteConfirmation) {
+            Button("取消", role: .cancel) {}
+            Button(session.cloudSessionId == nil ? "删除" : "同时删除", role: .destructive) {
+                Task {
+                    await deleteSession()
+                }
+            }
+            .disabled(isDeletingSession)
+        } message: {
+            if session.cloudSessionId != nil {
+                Text("删除「\(session.title)」后,本地录音、图片以及已经同步到云端的记录都会一并删除,且无法恢复。")
+            } else {
+                Text("删除「\(session.title)」后,相关录音和图片也会被删除,且无法恢复。")
+            }
+        }
+        .alert("删除失败", isPresented: Binding(
+            get: { deletionError != nil },
+            set: { if !$0 { deletionError = nil } }
+        )) {
+            Button("好", role: .cancel) {}
+        } message: {
+            Text(deletionError ?? "请稍后重试。")
+        }
         .alert("无法完成编辑", isPresented: Binding(
             get: { timelineEditError != nil },
             set: { if !$0 { timelineEditError = nil } }
@@ -452,12 +481,76 @@ struct SessionDetailView: View {
             }
             .buttonStyle(.plain)
             .accessibilityLabel("继续现场记录")
+
+            Button(role: .destructive) {
+                showSessionInfo = false
+                DispatchQueue.main.async {
+                    showDeleteConfirmation = true
+                }
+            } label: {
+                HStack(spacing: 8) {
+                    Image(systemName: "trash")
+                        .font(.system(size: 14, weight: .medium))
+
+                    Text("删除")
+                        .font(.system(size: 13, weight: .semibold))
+                }
+                .foregroundStyle(Color.red)
+                .frame(maxWidth: .infinity)
+                .padding(.vertical, 10)
+                .background(Color.red.opacity(0.06))
+                .overlay {
+                    RoundedRectangle(cornerRadius: 8)
+                        .stroke(Color.red.opacity(0.45), lineWidth: 1)
+                }
+            }
+            .buttonStyle(.plain)
+            .disabled(isDeletingSession)
+            .accessibilityLabel("删除现场记录")
         }
         .padding(14)
         .background(Color.cardBackground.opacity(0.15))
         .businessBorder(cornerRadius: 10)
     }
 
+    @MainActor
+    private func deleteSession() async {
+        guard !isDeletingSession else { return }
+        isDeletingSession = true
+        defer { isDeletingSession = false }
+
+        let storedPaths =
+            [session.localAudioPath].compactMap { $0 } +
+            session.audioChunks.map(\.localFilePath) +
+            session.events.compactMap(\.localFilePath)
+        let fileURLs = Set(storedPaths.compactMap { AudioPathHelper.resolveURL(for: $0) })
+
+        if let cloudSessionID = session.cloudSessionId {
+            do {
+                try await RemoteNetworkService().deleteSession(sessionID: cloudSessionID)
+            } catch APIError.server(let code, _) where code == 404 {
+                // The remote record is already gone, so local cleanup can continue.
+            } catch {
+                deletionError = "云端记录删除失败,本地记录未删除:\(error.localizedDescription)"
+                return
+            }
+        }
+
+        playbackVM.pause()
+        modelContext.delete(session)
+
+        do {
+            try modelContext.save()
+            for fileURL in fileURLs {
+                try? FileManager.default.removeItem(at: fileURL)
+            }
+            dismiss()
+        } catch {
+            modelContext.rollback()
+            deletionError = error.localizedDescription
+        }
+    }
+
     private static let chineseDateFormatter: DateFormatter = {
         let formatter = DateFormatter()
         formatter.locale = Locale(identifier: "zh_CN")