// // Swiftfin is subject to the terms of the Mozilla Public // License, v2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at https://mozilla.org/MPL/2.0/. // // Copyright (c) 2024 Jellyfin & Jellyfin Contributors // import CollectionHStack import Defaults import JellyfinAPI import SwiftUI import VLCUI extension VideoPlayer.Overlay { struct ChapterOverlay: View { @Default(.accentColor) private var accentColor @Environment(\.currentOverlayType) @Binding private var currentOverlayType @Environment(\.safeAreaInsets) private var safeAreaInsets @EnvironmentObject private var currentProgressHandler: VideoPlayerManager.CurrentProgressHandler @EnvironmentObject private var overlayTimer: TimerProxy @EnvironmentObject private var videoPlayerManager: VideoPlayerManager @EnvironmentObject private var videoPlayerProxy: VLCVideoPlayer.Proxy @EnvironmentObject private var viewModel: VideoPlayerViewModel @State private var scrollViewProxy: ScrollViewProxy? = nil var body: some View { VStack { Spacer(minLength: 0) .allowsHitTesting(false) HStack { L10n.chapters.text .font(.title2) .fontWeight(.semibold) .foregroundColor(.white) .accessibility(addTraits: [.isHeader]) Spacer() Button { if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) { withAnimation { scrollViewProxy?.scrollTo(currentChapter.hashValue, anchor: .center) } } } label: { Text(L10n.current) .font(.title2) .foregroundColor(accentColor) } } .padding(.horizontal, safeAreaInsets.leading) .if(UIDevice.isPad) { view in view.padding(.horizontal) } // ScrollViewReader { proxy in CollectionHStack( viewModel.chapters, minWidth: 200 ) { chapter in PosterButton( item: chapter, type: .landscape ) .content { VStack(alignment: .leading, spacing: 5) { Text(chapter.chapterInfo.displayTitle) .font(.subheadline) .fontWeight(.semibold) .lineLimit(1) .foregroundColor(.white) Text(chapter.chapterInfo.timestampLabel) .font(.subheadline) .fontWeight(.semibold) .foregroundColor(Color(UIColor.systemBlue)) .padding(.vertical, 2) .padding(.horizontal, 4) .background { Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) } } } } .scrollBehavior(.continuousLeadingEdge) .horizontalInset(safeAreaInsets.leading) // ScrollView(.horizontal, showsIndicators: false) { // HStack(alignment: .top, spacing: 15) { // ForEach(viewModel.chapters, id: \.self) { chapter in // PosterButton( // item: chapter, // type: .landscape // ) // .imageOverlay { // if chapter.secondsRange.contains(currentProgressHandler.seconds) { // RoundedRectangle(cornerRadius: 6) // .stroke(accentColor, lineWidth: 8) // } // } // .content { // VStack(alignment: .leading, spacing: 5) { // Text(chapter.chapterInfo.displayTitle) // .font(.subheadline) // .fontWeight(.semibold) // .lineLimit(1) // .foregroundColor(.white) // // Text(chapter.chapterInfo.timestampLabel) // .font(.subheadline) // .fontWeight(.semibold) // .foregroundColor(Color(UIColor.systemBlue)) // .padding(.vertical, 2) // .padding(.horizontal, 4) // .background { // Color(UIColor.darkGray).opacity(0.2).cornerRadius(4) // } // } // } // .onSelect { // let seconds = chapter.chapterInfo.startTimeSeconds // videoPlayerProxy.setTime(.seconds(seconds)) // // if videoPlayerManager.state != .playing { // videoPlayerProxy.play() // } // } // } // } // .padding(.leading, safeAreaInsets.leading) // .padding(.trailing, safeAreaInsets.trailing) // .padding(.bottom) // .if(UIDevice.isPad) { view in // view.padding(.horizontal) // } // } // .onChange(of: currentOverlayType) { newValue in // guard newValue == .chapters else { return } // if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) { // scrollViewProxy?.scrollTo(currentChapter.hashValue, anchor: .center) // } // } // .onAppear { // scrollViewProxy = proxy // } // } } .background { LinearGradient( stops: [ .init(color: .clear, location: 0), .init(color: .black.opacity(0.4), location: 0.4), .init(color: .black.opacity(0.9), location: 1), ], startPoint: .top, endPoint: .bottom ) .allowsHitTesting(false) } } } }