Fixes #29 — Delete downloaded video inline on the guide detail view

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) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-23 06:46:51 -05:00
parent 5777a210cd
commit 57f945a4d3
@@ -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 {