From 88551a13662f420cbb6f5e94fed9237bd93ec7fa Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sun, 18 Oct 2026 19:51:09 +0200 Subject: [PATCH] Offer a delayed Cancel while a tvOS remote playlist loads A remote playlist this account does not own showed only a spinner while loading, with or without a cached header, so the tvOS remote had nothing to focus. Cancel stops at the error view and its Retry, since a sidebar playlist is a tab root that cannot be popped. --- .../Playlist/UnifiedPlaylistDetailView.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift index 9b25f4cb..b9395489 100644 --- a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift +++ b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift @@ -247,7 +247,7 @@ struct UnifiedPlaylistDetailView: View { // Show header with cached data + spinner for video list loadingContent(cachedHeader) } else if case .remote = source, isLoading { - ProgressView() + loadingSpinner .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = errorMessage { errorView(error) @@ -404,7 +404,7 @@ struct UnifiedPlaylistDetailView: View { private var playlistBody: some View { if videos.isEmpty { if isLoading { - ProgressView() + loadingSpinner .frame(maxWidth: .infinity) .padding(.top, 40) } else if let errorMessage { @@ -420,6 +420,19 @@ struct UnifiedPlaylistDetailView: View { } } + /// On tvOS a remote playlist this account does not own has no other + /// focusable element while it loads, and as a sidebar playlist it is a tab + /// root that cannot be popped — so Cancel lands on the error and its Retry. + private var loadingSpinner: some View { + ProgressView() + #if os(tvOS) + .tvStalledLoadCancel { + isLoading = false + errorMessage = String(localized: "common.loadingStopped") + } + #endif + } + /// Shows cached header with a spinner below while loading full playlist data. private func loadingContent(_ cached: CachedPlaylistHeader) -> some View { ScrollView { @@ -433,7 +446,7 @@ struct UnifiedPlaylistDetailView: View { .padding(.horizontal) // Centered spinner for content area - ProgressView() + loadingSpinner .frame(maxWidth: .infinity) .padding(.top, 40) }