mirror of
https://github.com/yattee/yattee.git
synced 2026-10-24 13:42:54 +00:00
Surface video details load failures in the info view
A failed details fetch for a video opened with list metadata set the error state but never rendered it - the spinner cleared and the description section silently disappeared. Show an inline error section with a retry button and an error details sheet (copy/share, reusing the player's ErrorDetailsSheet). Also fill in the empty video.error.title localization.
This commit is contained in:
@@ -19135,7 +19135,15 @@
|
||||
}
|
||||
},
|
||||
"video.error.title" : {
|
||||
|
||||
"comment" : "Title shown when a video fails to load",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Failed to Load Video"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"video.navigation.next" : {
|
||||
"comment" : "Accessibility label for next video button",
|
||||
|
||||
@@ -41,6 +41,7 @@ struct VideoInfoView: View {
|
||||
@State private var loadMoreError: String?
|
||||
|
||||
@State private var isBookmarked = false
|
||||
@State private var showingErrorDetailsSheet = false
|
||||
@State private var showingPlaylistSheet = false
|
||||
@State private var showingCommentsSheet = false
|
||||
@State private var showingRemoveBookmarkAlert = false
|
||||
@@ -183,6 +184,15 @@ struct VideoInfoView: View {
|
||||
return !description.isEmpty
|
||||
}
|
||||
|
||||
/// Whether to surface a failed details fetch inline. The card still
|
||||
/// renders from list metadata, but description/stats/related never
|
||||
/// arrived — without this the sections just silently disappear.
|
||||
private var shouldShowDetailsErrorSection: Bool {
|
||||
guard !isLoadingVideoDetails, videoDetailsLoadError != nil,
|
||||
let base = baseVideo else { return false }
|
||||
return loadedVideoDetails[base.id.videoID] == nil
|
||||
}
|
||||
|
||||
/// Whether the stats section should be visible.
|
||||
/// Duration alone does not warrant showing stats.
|
||||
private var shouldShowStatsSection: Bool {
|
||||
@@ -382,6 +392,11 @@ struct VideoInfoView: View {
|
||||
PlaylistSelectorSheet(video: video)
|
||||
}
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.sheet(isPresented: $showingErrorDetailsSheet) {
|
||||
ErrorDetailsSheet(errorMessage: videoDetailsLoadError ?? initialVideoLoadError ?? "Unknown error")
|
||||
}
|
||||
#endif
|
||||
#if os(tvOS)
|
||||
.fullScreenCover(isPresented: $showingCommentsSheet) {
|
||||
commentsSheetContent
|
||||
@@ -496,6 +511,15 @@ struct VideoInfoView: View {
|
||||
bookmarkDetailsSection(bookmark)
|
||||
}
|
||||
|
||||
// Details fetch failed - show error with retry instead of
|
||||
// silently hiding the description/stats sections
|
||||
if shouldShowDetailsErrorSection {
|
||||
Divider()
|
||||
.padding(.horizontal)
|
||||
|
||||
detailsErrorSection
|
||||
}
|
||||
|
||||
// Description section (collapsible) - only show if description available
|
||||
if shouldShowDescriptionSection {
|
||||
Divider()
|
||||
@@ -739,6 +763,12 @@ struct VideoInfoView: View {
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 16)
|
||||
}
|
||||
} else if shouldShowDetailsErrorSection {
|
||||
detailsErrorSection
|
||||
|
||||
Divider()
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 16)
|
||||
}
|
||||
|
||||
if !isDescriptionScrollLocked {
|
||||
@@ -803,6 +833,14 @@ struct VideoInfoView: View {
|
||||
await loadVideoDetails()
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
Button {
|
||||
showingErrorDetailsSheet = true
|
||||
} label: {
|
||||
Label(String(localized: "player.error.button"), systemImage: "info.circle")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
@@ -1756,6 +1794,46 @@ struct VideoInfoView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
// MARK: - Details Error Section
|
||||
|
||||
/// Inline error shown when the details fetch failed but the video card
|
||||
/// still renders from list metadata (e.g. deleted/unavailable video).
|
||||
private var detailsErrorSection: some View {
|
||||
VStack(spacing: 12) {
|
||||
Label(String(localized: "video.error.title"), systemImage: "exclamationmark.triangle")
|
||||
.font(.headline)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
if let error = videoDetailsLoadError {
|
||||
Text(error)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button(String(localized: "common.retry")) {
|
||||
Task {
|
||||
await loadVideoDetails()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
||||
#if !os(tvOS)
|
||||
Button {
|
||||
showingErrorDetailsSheet = true
|
||||
} label: {
|
||||
Label(String(localized: "player.error.button"), systemImage: "info.circle")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 16)
|
||||
}
|
||||
|
||||
// MARK: - Description Section
|
||||
|
||||
private var descriptionSection: some View {
|
||||
|
||||
Reference in New Issue
Block a user