|
|
@@ -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")
|