153 lines
6.0 KiB
Swift
153 lines
6.0 KiB
Swift
//
|
|
// 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) 2025 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import IdentifiedCollections
|
|
import SwiftUI
|
|
|
|
// TODO: possibly make custom tab view to have observe
|
|
// vertical scroll content and transfer to dismissal
|
|
// TODO: fix improper supplement selected
|
|
// - maybe a race issue
|
|
|
|
extension VideoPlayer.UIVideoPlayerContainerViewController {
|
|
|
|
struct SupplementContainerView: View {
|
|
|
|
@Environment(\.safeAreaInsets)
|
|
private var safeAreaInsets
|
|
|
|
@EnvironmentObject
|
|
private var containerState: VideoPlayerContainerState
|
|
@EnvironmentObject
|
|
private var manager: MediaPlayerManager
|
|
|
|
@State
|
|
private var currentSupplements: IdentifiedArrayOf<AnyMediaPlayerSupplement> = []
|
|
|
|
private var isPresentingOverlay: Bool {
|
|
containerState.isPresentingOverlay
|
|
}
|
|
|
|
private var isScrubbing: Bool {
|
|
containerState.isScrubbing
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func supplementContainer(for supplement: some MediaPlayerSupplement) -> some View {
|
|
AlternateLayoutView(alignment: .topLeading) {
|
|
Color.clear
|
|
} content: {
|
|
supplement.videoPlayerBody
|
|
}
|
|
.background {
|
|
GestureView()
|
|
.environment(\.panGestureDirection, .vertical)
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
GestureView()
|
|
.environment(\.panGestureDirection, containerState.presentationControllerShouldDismiss ? .up : .vertical)
|
|
|
|
VStack(spacing: EdgeInsets.edgePadding) {
|
|
|
|
// TODO: scroll if larger than horizontal
|
|
HStack(spacing: 10) {
|
|
if containerState.isGuestSupplement, let supplement = containerState.selectedSupplement {
|
|
Button(supplement.displayTitle) {
|
|
containerState.select(supplement: nil)
|
|
}
|
|
.isSelected(true)
|
|
} else {
|
|
ForEach(currentSupplements) { supplement in
|
|
let isSelected = containerState.selectedSupplement?.id == supplement.id
|
|
|
|
Button(supplement.displayTitle) {
|
|
containerState.select(supplement: supplement.supplement)
|
|
}
|
|
.isSelected(isSelected)
|
|
}
|
|
}
|
|
}
|
|
.buttonStyle(SupplementTitleButtonStyle())
|
|
.padding(.leading, safeAreaInsets.leading)
|
|
.padding(.trailing, safeAreaInsets.trailing)
|
|
.edgePadding(.horizontal)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
|
ZStack {
|
|
if containerState.isGuestSupplement, let supplement = containerState.selectedSupplement {
|
|
supplementContainer(for: supplement)
|
|
.eraseToAnyView()
|
|
} else {
|
|
TabView(
|
|
selection: $containerState.selectedSupplement.map(
|
|
getter: { $0?.id },
|
|
setter: { id -> (any MediaPlayerSupplement)? in
|
|
id.map { currentSupplements[id: $0]?.supplement } ?? nil
|
|
}
|
|
)
|
|
) {
|
|
ForEach(currentSupplements) { supplement in
|
|
supplementContainer(for: supplement.supplement)
|
|
.eraseToAnyView()
|
|
.tag(supplement.id as String?)
|
|
}
|
|
}
|
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
|
}
|
|
}
|
|
.isVisible(containerState.isPresentingSupplement)
|
|
.disabled(!containerState.isPresentingSupplement)
|
|
.animation(.linear(duration: 0.2), value: containerState.selectedSupplement?.id)
|
|
}
|
|
.edgePadding(.top)
|
|
.isVisible(isPresentingOverlay)
|
|
.isVisible(!isScrubbing)
|
|
}
|
|
.animation(.linear(duration: 0.2), value: isPresentingOverlay)
|
|
.animation(.linear(duration: 0.1), value: isScrubbing)
|
|
.animation(.bouncy(duration: 0.3, extraBounce: 0.1), value: currentSupplements)
|
|
.environment(\.isOverComplexContent, true)
|
|
.onReceive(manager.$supplements) { newValue in
|
|
let newSupplements = IdentifiedArray(
|
|
uniqueElements: newValue.map(AnyMediaPlayerSupplement.init)
|
|
)
|
|
currentSupplements = newSupplements
|
|
}
|
|
.environment(
|
|
\.panAction,
|
|
.init(
|
|
action: {
|
|
containerState.containerView?.handlePanGesture(
|
|
translation: $0,
|
|
velocity: $1,
|
|
location: $2,
|
|
unitPoint: $3,
|
|
state: $4
|
|
)
|
|
}
|
|
)
|
|
)
|
|
.environment(
|
|
\.tapGestureAction,
|
|
.init(
|
|
action: {
|
|
containerState.containerView?.handleTapGesture(
|
|
location: $0,
|
|
unitPoint: $1,
|
|
count: $2
|
|
)
|
|
}
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|