Chapters: allow user to disable thumbnails

Chapters have their own section is the settings. The users can decide if chapter thumbnails should be shown or not. Also they can decide to only show thumbnails if they are not the same.

The VideoDetailsView had to be modified, to avoid compiler type-checking errors.
This commit is contained in:
Toni Förster 2024-05-11 20:18:56 +02:00
parent d1cf45c6a1
commit 7c50db426f
No known key found for this signature in database
GPG Key ID: 292F3E5086C83FC7
5 changed files with 114 additions and 73 deletions

View File

@ -77,6 +77,8 @@ extension Defaults.Keys {
static let collapsedLinesDescription = Key<Int>("collapsedLinesDescription", default: 5) static let collapsedLinesDescription = Key<Int>("collapsedLinesDescription", default: 5)
static let showChapters = Key<Bool>("showChapters", default: true) static let showChapters = Key<Bool>("showChapters", default: true)
static let showChapterThumbnails = Key<Bool>("showChapterThumbnails", default: true)
static let showChapterThumbnailsOnlyWhenDifferent = Key<Bool>("showChapterThumbnailsOnlyWhenDifferent", default: true)
static let expandChapters = Key<Bool>("expandChapters", default: true) static let expandChapters = Key<Bool>("expandChapters", default: true)
static let showRelated = Key<Bool>("showRelated", default: true) static let showRelated = Key<Bool>("showRelated", default: true)
static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal) static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)

View File

@ -9,6 +9,8 @@ import SwiftUI
var chapterIndex: Int var chapterIndex: Int
@ObservedObject private var player = PlayerModel.shared @ObservedObject private var player = PlayerModel.shared
var showThumbnail: Bool
var isCurrentChapter: Bool { var isCurrentChapter: Bool {
player.currentChapterIndex == chapterIndex player.currentChapterIndex == chapterIndex
} }
@ -27,7 +29,7 @@ import SwiftUI
var verticalChapter: some View { var verticalChapter: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
if !chapter.image.isNil { if !chapter.image.isNil, showThumbnail {
smallImage(chapter) smallImage(chapter)
} }
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
@ -40,7 +42,7 @@ import SwiftUI
.font(.system(.subheadline).monospacedDigit()) .font(.system(.subheadline).monospacedDigit())
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.frame(maxWidth: !chapter.image.isNil ? Self.thumbnailWidth : nil, alignment: .leading) .frame(maxWidth: !chapter.image.isNil && showThumbnail ? Self.thumbnailWidth : nil, alignment: .leading)
} }
} }
@ -126,7 +128,7 @@ struct ChapterView_Preview: PreviewProvider {
ChapterViewTVOS(chapter: .init(title: "Chapter", start: 30)) ChapterViewTVOS(chapter: .init(title: "Chapter", start: 30))
.injectFixtureEnvironmentObjects() .injectFixtureEnvironmentObjects()
#else #else
ChapterView(chapter: .init(title: "Chapter", start: 30), chapterIndex: 0) ChapterView(chapter: .init(title: "Chapter", start: 30), chapterIndex: 0, showThumbnail: true)
.injectFixtureEnvironmentObjects() .injectFixtureEnvironmentObjects()
#endif #endif
} }

View File

@ -5,18 +5,16 @@ import SwiftUI
struct ChaptersView: View { struct ChaptersView: View {
@ObservedObject private var player = PlayerModel.shared @ObservedObject private var player = PlayerModel.shared
@Binding var expand: Bool @Binding var expand: Bool
let chaptersHaveImages: Bool
let showThumbnails: Bool
var chapters: [Chapter] { var chapters: [Chapter] {
player.videoForDisplay?.chapters ?? [] player.videoForDisplay?.chapters ?? []
} }
var chaptersHaveImages: Bool {
chapters.allSatisfy { $0.image != nil }
}
var body: some View { var body: some View {
if !chapters.isEmpty { if !chapters.isEmpty {
if chaptersHaveImages { if chaptersHaveImages, showThumbnails {
#if os(tvOS) #if os(tvOS)
List { List {
Section { Section {
@ -70,7 +68,7 @@ struct ChaptersView: View {
private func chapterViews(for chaptersToShow: ArraySlice<Chapter>, opacity: Double = 1.0, clickable: Bool = true) -> some View { private func chapterViews(for chaptersToShow: ArraySlice<Chapter>, opacity: Double = 1.0, clickable: Bool = true) -> some View {
ForEach(Array(chaptersToShow.indices), id: \.self) { index in ForEach(Array(chaptersToShow.indices), id: \.self) { index in
let chapter = chaptersToShow[index] let chapter = chaptersToShow[index]
ChapterView(chapter: chapter, chapterIndex: index) ChapterView(chapter: chapter, chapterIndex: index, showThumbnail: showThumbnails)
.opacity(index == 0 ? 1.0 : opacity) .opacity(index == 0 ? 1.0 : opacity)
.allowsHitTesting(clickable) .allowsHitTesting(clickable)
} }
@ -80,7 +78,7 @@ struct ChaptersView: View {
struct ChaptersView_Previews: PreviewProvider { struct ChaptersView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ChaptersView(expand: .constant(false)) ChaptersView(expand: .constant(false), chaptersHaveImages: false, showThumbnails: true)
.injectFixtureEnvironmentObjects() .injectFixtureEnvironmentObjects()
} }
} }

View File

@ -186,6 +186,8 @@ struct VideoDetails: View {
@Default(.playerSidebar) private var playerSidebar @Default(.playerSidebar) private var playerSidebar
@Default(.showInspector) private var showInspector @Default(.showInspector) private var showInspector
@Default(.showChapters) private var showChapters @Default(.showChapters) private var showChapters
@Default(.showChapterThumbnails) private var showChapterThumbnails
@Default(.showChapterThumbnailsOnlyWhenDifferent) private var showChapterThumbnailsOnlyWhenDifferent
@Default(.showRelated) private var showRelated @Default(.showRelated) private var showRelated
#if !os(tvOS) #if !os(tvOS)
@Default(.showScrollToTopInComments) private var showScrollToTopInComments @Default(.showScrollToTopInComments) private var showScrollToTopInComments
@ -287,6 +289,63 @@ struct VideoDetails: View {
} }
} }
func infoView(video: Video) -> some View {
VStack(alignment: .leading, spacing: 10) {
if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) {
VStack {
ProgressView()
.progressViewStyle(.circular)
}
.frame(maxWidth: .infinity)
} else if let description = video.description, !description.isEmpty {
Section(header: descriptionHeader) {
VideoDescription(video: video, detailsSize: detailsSize, expand: $descriptionExpanded)
.padding(.horizontal)
}
} else if !video.isLocal {
Text("No description")
.font(.caption)
.foregroundColor(.secondary)
.padding(.horizontal)
}
if player.videoBeingOpened.isNil {
if showChapters,
!video.isLocal,
!video.chapters.isEmpty
{
Section(header: chaptersHeader) {
ChaptersView(expand: $chaptersExpanded, chaptersHaveImages: chaptersHaveImages, showThumbnails: showThumbnails)
}
}
if showInspector == .always || video.isLocal {
InspectorView(video: player.videoForDisplay)
.padding(.horizontal)
}
if showRelated,
!sidebarQueue,
!(player.videoForDisplay?.related.isEmpty ?? true)
{
RelatedView()
.padding(.horizontal)
.padding(.top, 20)
}
}
}
.onAppear {
if !pageAvailable(page) {
page = .info
}
}
.transition(.opacity)
.animation(nil, value: player.currentItem)
#if os(iOS)
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
#endif
}
var pageView: some View { var pageView: some View {
ScrollView(.vertical) { ScrollView(.vertical) {
LazyVStack { LazyVStack {
@ -296,69 +355,12 @@ struct VideoDetails: View {
switch page { switch page {
case .info: case .info:
Group { if let video = self.video {
if let video { infoView(video: video)
VStack(alignment: .leading, spacing: 10) {
if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) {
VStack {
ProgressView()
.progressViewStyle(.circular)
}
.frame(maxWidth: .infinity)
} else if let description = video.description, !description.isEmpty {
Section(header: descriptionHeader) {
VideoDescription(video: video, detailsSize: detailsSize, expand: $descriptionExpanded)
.padding(.horizontal)
}
} else if !video.isLocal {
Text("No description")
.font(.caption)
.foregroundColor(.secondary)
.padding(.horizontal)
}
if player.videoBeingOpened.isNil {
if showChapters,
!video.isLocal,
!video.chapters.isEmpty
{
Section(header: chaptersHeader) {
ChaptersView(expand: $chaptersExpanded)
}
}
if showInspector == .always || video.isLocal {
InspectorView(video: player.videoForDisplay)
.padding(.horizontal)
}
if showRelated,
!sidebarQueue,
!(player.videoForDisplay?.related.isEmpty ?? true)
{
RelatedView()
.padding(.horizontal)
.padding(.top, 20)
}
}
}
}
} }
.onAppear {
if video != nil, !pageAvailable(page) {
page = .info
}
}
.transition(.opacity)
.animation(nil, value: player.currentItem)
#if os(iOS)
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
#endif
case .queue: case .queue:
PlayerQueueView(sidebarQueue: false) PlayerQueueView(sidebarQueue: false)
.padding(.horizontal) .padding(.horizontal)
case .comments: case .comments:
CommentsView() CommentsView()
.onAppear { .onAppear {
@ -447,9 +449,27 @@ struct VideoDetails: View {
player.videoForDisplay?.chapters.allSatisfy { $0.image != nil } ?? false player.videoForDisplay?.chapters.allSatisfy { $0.image != nil } ?? false
} }
var chapterImagesTheSame: Bool {
guard let firstChapterURL = player.videoForDisplay?.chapters.first?.image else {
return false
}
return player.videoForDisplay?.chapters.allSatisfy { $0.image == firstChapterURL } ?? false
}
var showThumbnails: Bool {
if !chaptersHaveImages || !showChapterThumbnails {
return false
}
if showChapterThumbnailsOnlyWhenDifferent {
return !chapterImagesTheSame
}
return true
}
var chaptersHeader: some View { var chaptersHeader: some View {
Group { Group {
if !chaptersHaveImages { if !chaptersHaveImages || !showThumbnails {
#if canImport(UIKit) #if canImport(UIKit)
Button(action: { Button(action: {
chaptersExpanded.toggle() chaptersExpanded.toggle()

View File

@ -32,6 +32,8 @@ struct PlayerSettings: View {
@Default(.showInspector) private var showInspector @Default(.showInspector) private var showInspector
@Default(.showChapters) private var showChapters @Default(.showChapters) private var showChapters
@Default(.showChapterThumbnails) private var showThumbnails
@Default(.showChapterThumbnailsOnlyWhenDifferent) private var showThumbnailsOnlyWhenDifferent
@Default(.expandChapters) private var expandChapters @Default(.expandChapters) private var expandChapters
@Default(.showRelated) private var showRelated @Default(.showRelated) private var showRelated
@ -80,8 +82,6 @@ struct PlayerSettings: View {
Section(header: SettingsHeader(text: "Info".localized())) { Section(header: SettingsHeader(text: "Info".localized())) {
expandVideoDescriptionToggle expandVideoDescriptionToggle
collapsedLineDescriptionStepper collapsedLineDescriptionStepper
showChaptersToggle
expandChaptersToggle
showRelatedToggle showRelatedToggle
#if os(macOS) #if os(macOS)
HStack { HStack {
@ -93,6 +93,13 @@ struct PlayerSettings: View {
inspectorVisibilityPicker inspectorVisibilityPicker
#endif #endif
} }
Section(header: SettingsHeader(text: "Chapters".localized())) {
showChaptersToggle
showThumbnailsToggle
showThumbnailsWhenDifferentToggle
expandChaptersToggle
}
#endif #endif
let interface = Section(header: SettingsHeader(text: "Interface".localized())) { let interface = Section(header: SettingsHeader(text: "Interface".localized())) {
@ -284,7 +291,19 @@ struct PlayerSettings: View {
} }
private var showChaptersToggle: some View { private var showChaptersToggle: some View {
Toggle("Chapters (if available)", isOn: $showChapters) Toggle("Show chapters", isOn: $showChapters)
}
private var showThumbnailsToggle: some View {
Toggle("Show thumbnails", isOn: $showThumbnails)
.disabled(!showChapters)
.foregroundColor(showChapters ? .primary : .secondary)
}
private var showThumbnailsWhenDifferentToggle: some View {
Toggle("Show thumbnails only when unique", isOn: $showThumbnailsOnlyWhenDifferent)
.disabled(!showChapters || !showThumbnails)
.foregroundColor(showChapters && showThumbnails ? .primary : .secondary)
} }
private var expandChaptersToggle: some View { private var expandChaptersToggle: some View {