mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 05:02:53 +00:00
Offer a delayed Cancel while tvOS external video extraction loads
A screen showing only a spinner has nothing focusable, so a slow or hung extraction stranded the tvOS remote. Add TVStalledLoadCancelButton, a Cancel that appears under a loading spinner after three seconds so normal loads never flash it, and use it for external video extraction.
This commit is contained in:
@@ -13,6 +13,8 @@ import SwiftUI
|
||||
struct LoadingView: View {
|
||||
var message: String? = nil
|
||||
var subtext: String? = nil
|
||||
/// tvOS: offered as a Cancel button once loading has taken a while.
|
||||
var onCancel: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
@@ -32,11 +34,59 @@ struct LoadingView: View {
|
||||
.truncationMode(.middle)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
if let onCancel {
|
||||
TVStalledLoadCancelButton(action: onCancel)
|
||||
.padding(.top, 24)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
// MARK: - Stalled Load Cancel
|
||||
|
||||
/// A Cancel button that appears under a tvOS loading spinner once the load has
|
||||
/// taken a while.
|
||||
///
|
||||
/// A screen showing only a spinner has nothing focusable: the focus engine has
|
||||
/// nowhere to land, the remote cannot reach the sidebar and Menu cannot pop the
|
||||
/// screen, so a hung request strands the user until it times out. Delayed so
|
||||
/// a normal load never flashes the button.
|
||||
struct TVStalledLoadCancelButton: View {
|
||||
var delay: Duration = .seconds(3)
|
||||
let action: () -> Void
|
||||
|
||||
@State private var isShown = false
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if isShown {
|
||||
Button(String(localized: "common.cancel"), action: action)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
guard (try? await Task.sleep(for: delay)) != nil else { return }
|
||||
isShown = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Puts a delayed Cancel under a tvOS loading spinner. See
|
||||
/// `TVStalledLoadCancelButton`.
|
||||
func tvStalledLoadCancel(action: @escaping () -> Void) -> some View {
|
||||
VStack(spacing: 40) {
|
||||
self
|
||||
TVStalledLoadCancelButton(action: action)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: - Error State View
|
||||
|
||||
/// An error view with icon, message, and action buttons.
|
||||
|
||||
@@ -24,7 +24,8 @@ struct ExternalVideoView: View {
|
||||
if isLoading {
|
||||
LoadingView(
|
||||
message: String(localized: "externalVideo.extracting"),
|
||||
subtext: url.host ?? url.absoluteString
|
||||
subtext: url.host ?? url.absoluteString,
|
||||
onCancel: { dismiss() }
|
||||
)
|
||||
} else if let error = errorMessage {
|
||||
ErrorStateView(
|
||||
|
||||
Reference in New Issue
Block a user