jellyflood/Swiftfin/Views/VideoPlayer/Overlays/MainOverlay.swift

128 lines
4.9 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) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
extension VideoPlayer {
struct MainOverlay: View {
@Default(.VideoPlayer.Overlay.playbackButtonType)
private var playbackButtonType
@Environment(\.currentOverlayType)
@Binding
private var currentOverlayType
@Environment(\.isPresentingOverlay)
@Binding
private var isPresentingOverlay
@Environment(\.isScrubbing)
@Binding
private var isScrubbing: Bool
@Environment(\.safeAreaInsets)
private var safeAreaInsets
@EnvironmentObject
private var splitContentViewProxy: SplitContentViewProxy
@StateObject
private var overlayTimer: TimerProxy = .init()
var body: some View {
ZStack {
VStack {
Overlay.TopBarView()
.if(UIDevice.hasNotch) { view in
view.padding(safeAreaInsets.mutating(\.trailing, with: 0))
.padding(.trailing, splitContentViewProxy.isPresentingSplitView ? 0 : safeAreaInsets.trailing)
}
.if(UIDevice.isPad) { view in
view.padding(.top)
.padding(.horizontal)
}
.background {
LinearGradient(
stops: [
.init(color: .black.opacity(0.9), location: 0),
.init(color: .clear, location: 1),
],
startPoint: .top,
endPoint: .bottom
)
.visible(playbackButtonType == .compact)
}
.visible(!isScrubbing && isPresentingOverlay)
Spacer()
.allowsHitTesting(false)
Overlay.BottomBarView()
.if(UIDevice.hasNotch) { view in
view.padding(safeAreaInsets.mutating(\.trailing, with: 0))
.padding(.trailing, splitContentViewProxy.isPresentingSplitView ? 0 : safeAreaInsets.trailing)
}
.if(UIDevice.isPad) { view in
view.padding(.bottom)
.padding(.horizontal)
}
.background {
LinearGradient(
stops: [
.init(color: .clear, location: 0),
.init(color: .black.opacity(0.5), location: 0.5),
.init(color: .black.opacity(0.5), location: 1),
],
startPoint: .top,
endPoint: .bottom
)
.visible(isScrubbing || playbackButtonType == .compact)
}
.background {
Color.clear
.allowsHitTesting(true)
.contentShape(Rectangle())
.allowsHitTesting(true)
}
.visible(isScrubbing || isPresentingOverlay)
}
if playbackButtonType == .large {
Overlay.LargePlaybackButtons()
.visible(!isScrubbing && isPresentingOverlay)
}
}
.environmentObject(overlayTimer)
.background {
Color.black
.opacity(!isScrubbing && playbackButtonType == .large && isPresentingOverlay ? 0.5 : 0)
.allowsHitTesting(false)
}
.animation(.linear(duration: 0.1), value: isScrubbing)
.onChange(of: isPresentingOverlay) { newValue in
guard newValue, !isScrubbing else { return }
overlayTimer.start(5)
}
.onChange(of: isScrubbing) { newValue in
if newValue {
overlayTimer.stop()
} else {
overlayTimer.start(5)
}
}
.onChange(of: overlayTimer.isActive) { newValue in
guard !newValue, !isScrubbing else { return }
withAnimation(.linear(duration: 0.3)) {
isPresentingOverlay = false
}
}
}
}
}