From 57f945a4d3852371e58852ccf36dc82c965735c4 Mon Sep 17 00:00:00 2001 From: Trey t Date: Thu, 23 Apr 2026 06:46:51 -0500 Subject: [PATCH] =?UTF-8?q?Fixes=20#29=20=E2=80=94=20Delete=20downloaded?= =?UTF-8?q?=20video=20inline=20on=20the=20guide=20detail=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The middle button in the Stream/Download/Play row now cycles through the full download lifecycle instead of ending at a disabled "Downloaded" checkmark. Once a video is on disk the button becomes a red, destructive "Delete" with a trash icon; tapping presents a confirmation dialog, and confirming removes the file + SwiftData row, flipping the button back to "Download" and disabling Play. Settings → Downloaded Videos retains the swipe-delete and "Delete all" affordances for bulk management. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Views/Guide/VideoActionsView.swift | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/Conjuga/Conjuga/Views/Guide/VideoActionsView.swift b/Conjuga/Conjuga/Views/Guide/VideoActionsView.swift index 2efd25a..628243d 100644 --- a/Conjuga/Conjuga/Views/Guide/VideoActionsView.swift +++ b/Conjuga/Conjuga/Views/Guide/VideoActionsView.swift @@ -20,6 +20,7 @@ struct VideoActionsButtonRow: View { @State private var isDownloaded: Bool @State private var playerVideoId: String? @State private var downloadError: String? + @State private var confirmDelete: Bool = false init(video: YouTubeVideoStore.VideoEntry) { self.video = video @@ -64,6 +65,19 @@ struct VideoActionsButtonRow: View { } message: { Text(downloadError ?? "") } + .confirmationDialog( + "Delete this downloaded video?", + isPresented: $confirmDelete, + titleVisibility: .visible + ) { + Button("Delete", role: .destructive) { + downloadService.delete(videoId: video.videoId, modelContext: modelContext) + isDownloaded = false + } + Button("Cancel", role: .cancel) {} + } message: { + Text("You can re-download it at any time.") + } .onAppear { // Refresh on appear in case the user deleted the file via Settings. isDownloaded = VideoDownloadService.isDownloaded(videoId: video.videoId) @@ -88,29 +102,42 @@ struct VideoActionsButtonRow: View { @ViewBuilder private var downloadButton: some View { - Button { - Task { await startDownload() } - } label: { - Group { - if let progress = activeProgress { - HStack(spacing: 6) { - ProgressView(value: progress) - .frame(width: 40) - Text("\(Int(progress * 100))%") - .font(.caption.monospacedDigit()) - } - } else if isDownloaded { - Label("Downloaded", systemImage: "checkmark.circle.fill") - } else { - Label("Download", systemImage: "arrow.down.to.line") + // Single slot whose role flips through the download lifecycle: + // Download → progress (disabled) → Delete. + if isDownloading, let progress = activeProgress { + Button {} label: { + HStack(spacing: 6) { + ProgressView(value: progress).frame(width: 40) + Text("\(Int(progress * 100))%") + .font(.caption.monospacedDigit()) } + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) + .buttonStyle(.bordered) + .tint(.blue) + .controlSize(.large) + .disabled(true) + } else if isDownloaded { + Button(role: .destructive) { + confirmDelete = true + } label: { + Label("Delete", systemImage: "trash") + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .tint(.red) + .controlSize(.large) + } else { + Button { + Task { await startDownload() } + } label: { + Label("Download", systemImage: "arrow.down.to.line") + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .tint(.blue) + .controlSize(.large) } - .buttonStyle(.bordered) - .tint(.blue) - .controlSize(.large) - .disabled(isDownloaded || isDownloading) } private var playButton: some View {