From 48378d7ca8d5b7aa634be7f7e604243bef717d45 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Mon, 6 Jul 2026 08:19:56 +0200 Subject: [PATCH] Add download row context menu for pause, resume and cancel Download row actions were wired only as swipe actions, and SwipeActionModifier has #if !os(iOS) overloads that return the view unmodified, so macOS had no way to pause, resume or cancel a download. The video context menu could not cover it either, since the .downloads context deliberately hides the built-in cancel item. Give active download rows their own context menu with pause when queued or downloading, resume when paused, retry when failed, and a destructive cancel. Right-click on macOS, long-press on iOS. Completed rows had a related bug: the delete item called an onDelete callback that DownloadsView never passed, so it rendered but did nothing on macOS. Drop the callback and delete via the download manager directly, which is what already made the same menu work in HomeView. Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o --- Yattee/Localizable.xcstrings | 33 +++++++++++ Yattee/Views/Downloads/DownloadRowView.swift | 58 +++++++++++++++----- Yattee/Views/Home/HomeView.swift | 5 -- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 8fa92680..264b131c 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -2287,6 +2287,17 @@ } } }, + "downloads.cancel" : { + "comment" : "Cancel in-progress download context menu item", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel Download" + } + } + } + }, "downloads.delete" : { "localizations" : { "en" : { @@ -2341,6 +2352,17 @@ } } }, + "downloads.pause" : { + "comment" : "Pause download context menu item", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + } + } + }, "downloads.retry" : { "comment" : "Retry failed download button", "localizations" : { @@ -2352,6 +2374,17 @@ } } }, + "downloads.resume" : { + "comment" : "Resume paused download context menu item", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume" + } + } + } + }, "downloads.search.placeholder" : { "localizations" : { "en" : { diff --git a/Yattee/Views/Downloads/DownloadRowView.swift b/Yattee/Views/Downloads/DownloadRowView.swift index 3f1a148c..70ab8c59 100644 --- a/Yattee/Views/Downloads/DownloadRowView.swift +++ b/Yattee/Views/Downloads/DownloadRowView.swift @@ -17,7 +17,6 @@ struct DownloadRowView: View { let download: Download let isActive: Bool - var onDelete: (() -> Void)? = nil // Queue context (optional, enables auto-play when provided) var queueSource: QueueSource? = nil @@ -82,19 +81,8 @@ struct DownloadRowView: View { if isActive { // Active downloads: custom row with progress indicators, no tap actions activeDownloadContent - .if(onDelete != nil) { view in - view.videoContextMenu( - video: video, - customActions: [ - VideoContextAction( - String(localized: "downloads.delete"), - systemImage: "trash", - role: .destructive, - action: { onDelete?() } - ) - ], - context: .downloads - ) + .contextMenu { + activeDownloadMenuItems } } else { // Completed downloads: use VideoRowView with tap zones (thumbnail plays, text opens info) @@ -120,7 +108,9 @@ struct DownloadRowView: View { String(localized: "downloads.delete"), systemImage: "trash", role: .destructive, - action: { onDelete?() } + action: { + Task { await appEnvironment?.downloadManager.delete(download) } + } ) ], context: .downloads @@ -131,6 +121,44 @@ struct DownloadRowView: View { } } + /// Menu items for an in-progress download: pause/resume/retry plus cancel. + /// The `.downloads` video context menu hides the built-in cancel item, and swipe + /// actions are iOS-only, so this is the only cancel affordance on macOS. + @ViewBuilder + private var activeDownloadMenuItems: some View { + switch download.status { + case .queued, .downloading: + Button { + Task { await appEnvironment?.downloadManager.pause(download) } + } label: { + Label(String(localized: "downloads.pause"), systemImage: "pause.fill") + } + + case .paused: + Button { + Task { await appEnvironment?.downloadManager.resume(download) } + } label: { + Label(String(localized: "downloads.resume"), systemImage: "play.fill") + } + + case .failed: + Button { + Task { await appEnvironment?.downloadManager.resume(download) } + } label: { + Label(String(localized: "downloads.retry"), systemImage: "arrow.clockwise") + } + + case .completed: + EmptyView() + } + + Button(role: .destructive) { + Task { await appEnvironment?.downloadManager.cancel(download) } + } label: { + Label(String(localized: "downloads.cancel"), systemImage: "xmark.circle") + } + } + private var activeDownloadContent: some View { HStack(spacing: 12) { // Thumbnail (download status shown automatically) diff --git a/Yattee/Views/Home/HomeView.swift b/Yattee/Views/Home/HomeView.swift index 3d6750a9..1cb1c7b5 100644 --- a/Yattee/Views/Home/HomeView.swift +++ b/Yattee/Views/Home/HomeView.swift @@ -1315,11 +1315,6 @@ struct HomeView: View { DownloadRowView( download: download, isActive: false, - onDelete: { - Task { - await downloadManager?.delete(download) - } - }, queueSource: .manual, sourceLabel: String(localized: "queue.source.downloads"), videoList: videoList,