mirror of
				https://github.com/yattee/yattee.git
				synced 2025-11-03 22:22:02 +00:00 
			
		
		
		
	Merge pull request #564 from stonerl/collapsible-chapters
make chapters collapsible and highlight current chapter
This commit is contained in:
		@@ -596,6 +596,8 @@ final class AVPlayerBackend: PlayerBackend {
 | 
			
		||||
            if self.controlsUpdates {
 | 
			
		||||
                self.updateControls()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.model.updateTime(self.currentTime!)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -182,13 +182,21 @@ final class MPVBackend: PlayerBackend {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init() {
 | 
			
		||||
        // swiftlint:disable shorthand_optional_binding
 | 
			
		||||
        clientTimer = .init(interval: .seconds(Self.timeUpdateInterval), mode: .infinite) { [weak self] _ in
 | 
			
		||||
            self?.getTimeUpdates()
 | 
			
		||||
            guard let self = self, self.model.activeBackend == .mpv else {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            self.getTimeUpdates()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        networkStateTimer = .init(interval: .seconds(Self.networkStateUpdateInterval), mode: .infinite) { [weak self] _ in
 | 
			
		||||
            self?.updateNetworkState()
 | 
			
		||||
            guard let self = self, self.model.activeBackend == .mpv else {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            self.updateNetworkState()
 | 
			
		||||
        }
 | 
			
		||||
        // swiftlint:enable shorthand_optional_binding
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    typealias AreInIncreasingOrder = (Stream, Stream) -> Bool
 | 
			
		||||
@@ -432,6 +440,8 @@ final class MPVBackend: PlayerBackend {
 | 
			
		||||
        timeObserverThrottle.execute {
 | 
			
		||||
            self.model.updateWatch(time: self.currentTime)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.model.updateTime(self.currentTime!)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func stopClientUpdates() {
 | 
			
		||||
 
 | 
			
		||||
@@ -131,6 +131,8 @@ final class PlayerModel: ObservableObject {
 | 
			
		||||
        @Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
    @Published var currentChapterIndex: Int?
 | 
			
		||||
 | 
			
		||||
    var accounts: AccountsModel { .shared }
 | 
			
		||||
    var comments: CommentsModel { .shared }
 | 
			
		||||
    var controls: PlayerControlsModel { .shared }
 | 
			
		||||
@@ -1112,4 +1114,36 @@ final class PlayerModel: ObservableObject {
 | 
			
		||||
        onPlayStream.forEach { $0(stream) }
 | 
			
		||||
        onPlayStream.removeAll()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func updateTime(_ cmTime: CMTime) {
 | 
			
		||||
        let time = CMTimeGetSeconds(cmTime)
 | 
			
		||||
        let newChapterIndex = chapterForTime(time)
 | 
			
		||||
        if currentChapterIndex != newChapterIndex {
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                self.currentChapterIndex = newChapterIndex
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func chapterForTime(_ time: Double) -> Int? {
 | 
			
		||||
        guard let chapters = self.videoForDisplay?.chapters else {
 | 
			
		||||
            return nil
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (index, chapter) in chapters.enumerated() {
 | 
			
		||||
            let nextChapterStartTime = index < (chapters.count - 1) ? chapters[index + 1].start : nil
 | 
			
		||||
 | 
			
		||||
            if let nextChapterStart = nextChapterStartTime {
 | 
			
		||||
                if time >= chapter.start, time < nextChapterStart {
 | 
			
		||||
                    return index
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if time >= chapter.start {
 | 
			
		||||
                    return index
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return nil
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -265,6 +265,7 @@ extension Defaults.Keys {
 | 
			
		||||
    static let hideWatched = Key<Bool>("hideWatched", default: false)
 | 
			
		||||
    static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)
 | 
			
		||||
    static let showChapters = Key<Bool>("showChapters", default: true)
 | 
			
		||||
    static let expandChapters = Key<Bool>("expandChapters", default: true)
 | 
			
		||||
    static let showRelated = Key<Bool>("showRelated", default: true)
 | 
			
		||||
    static let widgetsSettings = Key<[WidgetSettings]>("widgetsSettings", default: [])
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,28 +2,85 @@ import Foundation
 | 
			
		||||
import SDWebImageSwiftUI
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChapterView: View {
 | 
			
		||||
    var chapter: Chapter
 | 
			
		||||
#if !os(tvOS)
 | 
			
		||||
    struct ChapterView: View {
 | 
			
		||||
        var chapter: Chapter
 | 
			
		||||
 | 
			
		||||
    var player = PlayerModel.shared
 | 
			
		||||
        var chapterIndex: Int
 | 
			
		||||
        @ObservedObject private var player = PlayerModel.shared
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Button {
 | 
			
		||||
            player.backend.seek(to: chapter.start, seekType: .userInteracted)
 | 
			
		||||
        } label: {
 | 
			
		||||
            Group {
 | 
			
		||||
                #if os(tvOS)
 | 
			
		||||
                    horizontalChapter
 | 
			
		||||
                #else
 | 
			
		||||
                    verticalChapter
 | 
			
		||||
                #endif
 | 
			
		||||
            }
 | 
			
		||||
            .contentShape(Rectangle())
 | 
			
		||||
        var isCurrentChapter: Bool {
 | 
			
		||||
            player.currentChapterIndex == chapterIndex
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var body: some View {
 | 
			
		||||
            Button(action: {
 | 
			
		||||
                player.backend.seek(to: chapter.start, seekType: .userInteracted)
 | 
			
		||||
            }) {
 | 
			
		||||
                Group {
 | 
			
		||||
                    verticalChapter
 | 
			
		||||
                }
 | 
			
		||||
                .contentShape(Rectangle())
 | 
			
		||||
            }
 | 
			
		||||
            .buttonStyle(.plain)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var verticalChapter: some View {
 | 
			
		||||
            VStack(spacing: 12) {
 | 
			
		||||
                if !chapter.image.isNil {
 | 
			
		||||
                    smallImage(chapter)
 | 
			
		||||
                }
 | 
			
		||||
                VStack(alignment: .leading, spacing: 4) {
 | 
			
		||||
                    Text(chapter.title)
 | 
			
		||||
                        .lineLimit(3)
 | 
			
		||||
                        .multilineTextAlignment(.leading)
 | 
			
		||||
                        .font(.headline)
 | 
			
		||||
                        .foregroundColor(isCurrentChapter ? Color("AppRedColor") : .primary)
 | 
			
		||||
                    Text(chapter.start.formattedAsPlaybackTime(allowZero: true) ?? "")
 | 
			
		||||
                        .font(.system(.subheadline).monospacedDigit())
 | 
			
		||||
                        .foregroundColor(.secondary)
 | 
			
		||||
                }
 | 
			
		||||
                .frame(maxWidth: !chapter.image.isNil ? Self.thumbnailWidth : nil, alignment: .leading)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
 | 
			
		||||
            WebImage(url: chapter.image, options: [.lowPriority])
 | 
			
		||||
                .resizable()
 | 
			
		||||
                .placeholder {
 | 
			
		||||
                    ProgressView()
 | 
			
		||||
                }
 | 
			
		||||
                .indicator(.activity)
 | 
			
		||||
                .frame(width: Self.thumbnailWidth, height: Self.thumbnailHeight)
 | 
			
		||||
 | 
			
		||||
                .mask(RoundedRectangle(cornerRadius: 6))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static var thumbnailWidth: Double {
 | 
			
		||||
            250
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static var thumbnailHeight: Double {
 | 
			
		||||
            thumbnailWidth / 1.7777
 | 
			
		||||
        }
 | 
			
		||||
        .buttonStyle(.plain)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #if os(tvOS)
 | 
			
		||||
#else
 | 
			
		||||
    struct ChapterViewTVOS: View {
 | 
			
		||||
        var chapter: Chapter
 | 
			
		||||
        var player = PlayerModel.shared
 | 
			
		||||
 | 
			
		||||
        var body: some View {
 | 
			
		||||
            Button {
 | 
			
		||||
                player.backend.seek(to: chapter.start, seekType: .userInteracted)
 | 
			
		||||
            } label: {
 | 
			
		||||
                Group {
 | 
			
		||||
                    horizontalChapter
 | 
			
		||||
                }
 | 
			
		||||
                .contentShape(Rectangle())
 | 
			
		||||
            }
 | 
			
		||||
            .buttonStyle(.plain)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var horizontalChapter: some View {
 | 
			
		||||
            HStack(spacing: 12) {
 | 
			
		||||
@@ -41,53 +98,36 @@ struct ChapterView: View {
 | 
			
		||||
            }
 | 
			
		||||
            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
        }
 | 
			
		||||
    #else
 | 
			
		||||
        var verticalChapter: some View {
 | 
			
		||||
            VStack(spacing: 12) {
 | 
			
		||||
                if !chapter.image.isNil {
 | 
			
		||||
                    smallImage(chapter)
 | 
			
		||||
 | 
			
		||||
        @ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
 | 
			
		||||
            WebImage(url: chapter.image, options: [.lowPriority])
 | 
			
		||||
                .resizable()
 | 
			
		||||
                .placeholder {
 | 
			
		||||
                    ProgressView()
 | 
			
		||||
                }
 | 
			
		||||
                VStack(alignment: .leading, spacing: 4) {
 | 
			
		||||
                    Text(chapter.title)
 | 
			
		||||
                        .lineLimit(2)
 | 
			
		||||
                        .multilineTextAlignment(.leading)
 | 
			
		||||
                        .font(.headline)
 | 
			
		||||
                    Text(chapter.start.formattedAsPlaybackTime(allowZero: true) ?? "")
 | 
			
		||||
                        .font(.system(.subheadline).monospacedDigit())
 | 
			
		||||
                        .foregroundColor(.secondary)
 | 
			
		||||
                }
 | 
			
		||||
                .frame(maxWidth: Self.thumbnailWidth, alignment: .leading)
 | 
			
		||||
            }
 | 
			
		||||
                .indicator(.activity)
 | 
			
		||||
                .frame(width: Self.thumbnailWidth, height: Self.thumbnailHeight)
 | 
			
		||||
                .mask(RoundedRectangle(cornerRadius: 12))
 | 
			
		||||
        }
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
    @ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
 | 
			
		||||
        WebImage(url: chapter.image, options: [.lowPriority])
 | 
			
		||||
            .resizable()
 | 
			
		||||
            .placeholder {
 | 
			
		||||
                ProgressView()
 | 
			
		||||
            }
 | 
			
		||||
            .indicator(.activity)
 | 
			
		||||
            .frame(width: Self.thumbnailWidth, height: Self.thumbnailHeight)
 | 
			
		||||
        #if os(tvOS)
 | 
			
		||||
            .mask(RoundedRectangle(cornerRadius: 12))
 | 
			
		||||
        #else
 | 
			
		||||
            .mask(RoundedRectangle(cornerRadius: 6))
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
        static var thumbnailWidth: Double {
 | 
			
		||||
            250
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    static var thumbnailWidth: Double {
 | 
			
		||||
        250
 | 
			
		||||
        static var thumbnailHeight: Double {
 | 
			
		||||
            thumbnailWidth / 1.7777
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static var thumbnailHeight: Double {
 | 
			
		||||
        thumbnailWidth / 1.7777
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct ChapterView_Preview: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        ChapterView(chapter: .init(title: "Chapter", start: 30))
 | 
			
		||||
            .injectFixtureEnvironmentObjects()
 | 
			
		||||
        #if os(tvOS)
 | 
			
		||||
            ChapterViewTVOS(chapter: .init(title: "Chapter", start: 30))
 | 
			
		||||
                .injectFixtureEnvironmentObjects()
 | 
			
		||||
        #else
 | 
			
		||||
            ChapterView(chapter: .init(title: "Chapter", start: 30), chapterIndex: 0)
 | 
			
		||||
                .injectFixtureEnvironmentObjects()
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChaptersView: View {
 | 
			
		||||
    @ObservedObject private var player = PlayerModel.shared
 | 
			
		||||
    @Binding var expand: Bool
 | 
			
		||||
 | 
			
		||||
    var chapters: [Chapter] {
 | 
			
		||||
        player.videoForDisplay?.chapters ?? []
 | 
			
		||||
@@ -15,45 +16,71 @@ struct ChaptersView: View {
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        if !chapters.isEmpty {
 | 
			
		||||
            #if os(tvOS)
 | 
			
		||||
                List {
 | 
			
		||||
                    Section {
 | 
			
		||||
                        ForEach(chapters) { chapter in
 | 
			
		||||
                            ChapterView(chapter: chapter)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .listRowBackground(Color.clear)
 | 
			
		||||
                }
 | 
			
		||||
                .listStyle(.plain)
 | 
			
		||||
            #else
 | 
			
		||||
                if chaptersHaveImages {
 | 
			
		||||
                    ScrollView(.horizontal) {
 | 
			
		||||
                        LazyHStack(spacing: 20) {
 | 
			
		||||
            if chaptersHaveImages {
 | 
			
		||||
                #if os(tvOS)
 | 
			
		||||
                    List {
 | 
			
		||||
                        Section {
 | 
			
		||||
                            ForEach(chapters) { chapter in
 | 
			
		||||
                                ChapterView(chapter: chapter)
 | 
			
		||||
                                ChapterViewTVOS(chapter: chapter)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        .padding(.horizontal, 15)
 | 
			
		||||
                        .listRowBackground(Color.clear)
 | 
			
		||||
                    }
 | 
			
		||||
                    .frame(minHeight: ChapterView.thumbnailHeight + 100)
 | 
			
		||||
                } else {
 | 
			
		||||
                    .listStyle(.plain)
 | 
			
		||||
                #else
 | 
			
		||||
                    ScrollView(.horizontal) {
 | 
			
		||||
                        LazyHStack(spacing: 20) { chapterViews(for: chapters[...]) }.padding(.horizontal, 15)
 | 
			
		||||
                    }
 | 
			
		||||
                #endif
 | 
			
		||||
            } else if expand {
 | 
			
		||||
                #if os(tvOS)
 | 
			
		||||
                    Section {
 | 
			
		||||
                        ForEach(chapters) { chapter in
 | 
			
		||||
                            ChapterView(chapter: chapter)
 | 
			
		||||
                            ChapterViewTVOS(chapter: chapter)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal)
 | 
			
		||||
                }
 | 
			
		||||
            #endif
 | 
			
		||||
        } else {
 | 
			
		||||
            NoCommentsView(text: "No chapters information available".localized(), systemImage: "xmark.circle.fill")
 | 
			
		||||
                #else
 | 
			
		||||
                    Section { chapterViews(for: chapters[...]) }.padding(.horizontal)
 | 
			
		||||
                #endif
 | 
			
		||||
            } else {
 | 
			
		||||
                #if os(iOS)
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        self.expand.toggle()
 | 
			
		||||
                    }) {
 | 
			
		||||
                        Section {
 | 
			
		||||
                            chapterViews(for: chapters.prefix(3), opacity: 0.3, clickable: false)
 | 
			
		||||
                        }.padding(.horizontal)
 | 
			
		||||
                    }
 | 
			
		||||
                #elseif os(macOS)
 | 
			
		||||
                    Section {
 | 
			
		||||
                        chapterViews(for: chapters.prefix(3), opacity: 0.3, clickable: false)
 | 
			
		||||
                    }.padding(.horizontal)
 | 
			
		||||
                #else
 | 
			
		||||
                    Section {
 | 
			
		||||
                        ForEach(chapters) { chapter in
 | 
			
		||||
                            ChapterViewTVOS(chapter: chapter)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                #endif
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #if !os(tvOS)
 | 
			
		||||
        private func chapterViews(for chaptersToShow: ArraySlice<Chapter>, opacity: Double = 1.0, clickable: Bool = true) -> some View {
 | 
			
		||||
            ForEach(Array(chaptersToShow.indices), id: \.self) { index in
 | 
			
		||||
                let chapter = chaptersToShow[index]
 | 
			
		||||
                ChapterView(chapter: chapter, chapterIndex: index)
 | 
			
		||||
                    .opacity(index == 0 ? 1.0 : opacity)
 | 
			
		||||
                    .allowsHitTesting(clickable)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    #endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ChaptersView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        ChaptersView()
 | 
			
		||||
        ChaptersView(expand: .constant(false))
 | 
			
		||||
            .injectFixtureEnvironmentObjects()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,7 @@ struct VideoDetails: View {
 | 
			
		||||
    @State private var subscriptionToggleButtonDisabled = false
 | 
			
		||||
    @State private var page = DetailsPage.info
 | 
			
		||||
    @State private var descriptionExpanded = false
 | 
			
		||||
    @State private var chaptersExpanded = false
 | 
			
		||||
 | 
			
		||||
    @Environment(\.navigationStyle) private var navigationStyle
 | 
			
		||||
    #if os(iOS)
 | 
			
		||||
@@ -190,6 +191,7 @@ struct VideoDetails: View {
 | 
			
		||||
        @Default(.showScrollToTopInComments) private var showScrollToTopInComments
 | 
			
		||||
    #endif
 | 
			
		||||
    @Default(.expandVideoDescription) private var expandVideoDescription
 | 
			
		||||
    @Default(.expandChapters) private var expandChapters
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
@@ -245,6 +247,7 @@ struct VideoDetails: View {
 | 
			
		||||
        .background(colorScheme == .dark ? Color.black : .white)
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            descriptionExpanded = expandVideoDescription
 | 
			
		||||
            chaptersExpanded = expandChapters
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -320,7 +323,7 @@ struct VideoDetails: View {
 | 
			
		||||
                                       !video.chapters.isEmpty
 | 
			
		||||
                                    {
 | 
			
		||||
                                        Section(header: chaptersHeader) {
 | 
			
		||||
                                            ChaptersView()
 | 
			
		||||
                                            ChaptersView(expand: $chaptersExpanded)
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
 | 
			
		||||
@@ -440,11 +443,48 @@ struct VideoDetails: View {
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var chaptersHaveImages: Bool {
 | 
			
		||||
        player.videoForDisplay?.chapters.allSatisfy { $0.image != nil } ?? false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var chaptersHeader: some View {
 | 
			
		||||
        Text("Chapters".localized())
 | 
			
		||||
            .padding(.horizontal)
 | 
			
		||||
            .font(.caption)
 | 
			
		||||
            .foregroundColor(.secondary)
 | 
			
		||||
        Group {
 | 
			
		||||
            if !chaptersHaveImages {
 | 
			
		||||
                #if canImport(UIKit)
 | 
			
		||||
                    Button(action: {
 | 
			
		||||
                        chaptersExpanded.toggle()
 | 
			
		||||
                    }) {
 | 
			
		||||
                        HStack {
 | 
			
		||||
                            Text("Chapters".localized())
 | 
			
		||||
                            Spacer()
 | 
			
		||||
                            Image(systemName: chaptersExpanded ? "chevron.up" : "chevron.down")
 | 
			
		||||
                                .imageScale(.small)
 | 
			
		||||
                        }
 | 
			
		||||
                        .padding(.horizontal)
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                        .foregroundColor(.secondary)
 | 
			
		||||
                    }
 | 
			
		||||
                #elseif canImport(AppKit)
 | 
			
		||||
                    HStack {
 | 
			
		||||
                        Text("Chapters".localized())
 | 
			
		||||
                        Spacer()
 | 
			
		||||
                        Button(action: { chaptersExpanded.toggle() }) {
 | 
			
		||||
                            Image(systemName: chaptersExpanded ? "chevron.up" : "chevron.down")
 | 
			
		||||
                                .imageScale(.small)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal)
 | 
			
		||||
                    .font(.caption)
 | 
			
		||||
                    .foregroundColor(.secondary)
 | 
			
		||||
                #endif
 | 
			
		||||
            } else {
 | 
			
		||||
                // No button, just the title when there are images
 | 
			
		||||
                Text("Chapters".localized())
 | 
			
		||||
                    .font(.caption)
 | 
			
		||||
                    .foregroundColor(.secondary)
 | 
			
		||||
                    .padding(.horizontal)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ struct PlayerSettings: View {
 | 
			
		||||
 | 
			
		||||
    @Default(.showInspector) private var showInspector
 | 
			
		||||
    @Default(.showChapters) private var showChapters
 | 
			
		||||
    @Default(.expandChapters) private var expandChapters
 | 
			
		||||
    @Default(.showRelated) private var showRelated
 | 
			
		||||
 | 
			
		||||
    @ObservedObject private var accounts = AccountsModel.shared
 | 
			
		||||
@@ -80,6 +81,7 @@ struct PlayerSettings: View {
 | 
			
		||||
                    expandVideoDescriptionToggle
 | 
			
		||||
                    collapsedLineDescriptionStepper
 | 
			
		||||
                    showChaptersToggle
 | 
			
		||||
                    expandChaptersToggle
 | 
			
		||||
                    showRelatedToggle
 | 
			
		||||
                    #if os(macOS)
 | 
			
		||||
                        HStack {
 | 
			
		||||
@@ -282,7 +284,13 @@ struct PlayerSettings: View {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private var showChaptersToggle: some View {
 | 
			
		||||
            Toggle("Chapters", isOn: $showChapters)
 | 
			
		||||
            Toggle("Chapters (if available)", isOn: $showChapters)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private var expandChaptersToggle: some View {
 | 
			
		||||
            Toggle("Open vertical chapters expanded", isOn: $expandChapters)
 | 
			
		||||
                .disabled(!showChapters)
 | 
			
		||||
                .foregroundColor(showChapters ? .primary : .secondary)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private var showRelatedToggle: some View {
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ struct NowPlayingView: View {
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Section(header: Text("Chapters")) {
 | 
			
		||||
                                ForEach(video.chapters) { chapter in
 | 
			
		||||
                                    ChapterView(chapter: chapter)
 | 
			
		||||
                                    ChapterViewTVOS(chapter: chapter)
 | 
			
		||||
                                        .padding(.horizontal, 40)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user