Watch Next behavior and settings

This commit is contained in:
Arkadiusz Fal
2022-12-18 19:39:03 +01:00
parent b90c856e21
commit 39fc23c5dc
23 changed files with 487 additions and 451 deletions

View File

@@ -9,13 +9,14 @@ struct VideoActions: View {
var video: Video?
@Default(.openWatchNextOnClose) private var openWatchNextOnClose
@Default(.playerActionsButtonLabelStyle) private var playerActionsButtonLabelStyle
var body: some View {
HStack {
HStack(spacing: 6) {
if let video {
#if !os(tvOS)
if !video.isLocal || video.localStreamIsRemoteURL {
if video.isShareable {
ShareButton(contentItem: .init(video: video)) {
actionButton("Share", systemImage: "square.and.arrow.up")
}
@@ -50,21 +51,29 @@ struct VideoActions: View {
Spacer()
}
}
} else {
Spacer()
}
actionButton("Next", systemImage: Constants.nextSystemImage) {
WatchNextViewModel.shared.userInteractedOpen(player.currentItem)
}
Spacer()
actionButton("Hide", systemImage: "chevron.down") {
player.hide(animate: true)
}
Spacer()
actionButton("Close", systemImage: "xmark") {
// TODO: setting
// player.pause()
// WatchNextViewModel.shared.prepareForEmptyPlayerPlaceholder(player.currentItem)
// WatchNextViewModel.shared.open()
player.closeCurrentItem()
if openWatchNextOnClose {
player.pause()
WatchNextViewModel.shared.closed(player.currentItem)
} else {
player.closeCurrentItem()
}
}
.disabled(player.currentItem == nil)
}
.padding(.horizontal)
.multilineTextAlignment(.center)

View File

@@ -27,10 +27,9 @@ struct VideoDetails: View {
@ObservedObject private var accounts = AccountsModel.shared
let comments = CommentsModel.shared
var player = PlayerModel.shared
@ObservedObject private var player = PlayerModel.shared
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
@Default(.detailsToolbarPosition) private var detailsToolbarPosition
@Default(.playerSidebar) private var playerSidebar
var body: some View {
@@ -46,7 +45,7 @@ struct VideoDetails: View {
)
.animation(nil, value: player.currentItem)
VideoActions(video: video)
VideoActions(video: player.videoForDisplay)
.animation(nil, value: player.currentItem)
detailsPage

View File

@@ -1,46 +0,0 @@
import Defaults
import Foundation
struct VideoDetailsTool: Identifiable {
static let all = [
Self(icon: "info.circle", name: "Info", page: .info),
Self(icon: "wand.and.stars", name: "Inspector", page: .inspector),
Self(icon: "bookmark", name: "Chapters", page: .chapters),
Self(icon: "text.bubble", name: "Comments", page: .comments),
Self(icon: "rectangle.stack.fill", name: "Related", page: .related),
Self(icon: "list.number", name: "Queue", page: .queue)
]
static func find(for page: VideoDetails.DetailsPage) -> Self? {
all.first { $0.page == page }
}
var id: String {
page.rawValue
}
var icon: String
var name: String
var toolPostion: CGRect = .zero
var page = VideoDetails.DetailsPage.info
func isAvailable(for video: Video?, sidebarQueue: Bool) -> Bool {
guard !YatteeApp.isForPreviews else {
return true
}
switch page {
case .info:
return true
case .inspector:
return video == nil || Defaults[.showInspector] == .always || video!.isLocal
case .chapters:
return video != nil && !video!.chapters.isEmpty
case .comments:
return video != nil && !video!.isLocal
case .related:
return !sidebarQueue && video != nil && !video!.isLocal
case .queue:
return !sidebarQueue
}
}
}

View File

@@ -1,154 +0,0 @@
import Defaults
import SwiftUI
struct VideoDetailsToolbar: View {
static let lowOpacity = 0.5
var video: Video?
@Binding var page: VideoDetails.DetailsPage
var sidebarQueue: Bool
@State private var tools = VideoDetailsTool.all
@State private var activeTool: VideoDetailsTool?
@State private var startedToolPosition: CGRect = .zero
@State private var opacity = 1.0
@ObservedObject private var player = PlayerModel.shared
@Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle
var body: some View {
VStack {
HStack(spacing: 12) {
ForEach($tools) { $tool in
if $tool.wrappedValue.isAvailable(for: video, sidebarQueue: sidebarQueue) {
ToolView(tool: $tool)
.padding(.vertical, 10)
}
}
}
.id(video?.id)
.onAppear {
activeTool = .find(for: page)
}
.onChange(of: page) { newValue in
activeTool = tools.first { $0.id == newValue.rawValue }
}
.coordinateSpace(name: "toolbarArea")
#if !os(tvOS)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
withAnimation(.linear(duration: 0.2)) {
opacity = 1
}
guard let firstTool = tools.first else { return }
if startedToolPosition == .zero {
startedToolPosition = firstTool.toolPostion
}
let location = CGPoint(x: value.location.x, y: value.location.y)
if let index = tools.firstIndex(where: { $0.toolPostion.contains(location) }),
activeTool?.id != tools[index].id,
tools[index].isAvailable(for: video, sidebarQueue: sidebarQueue)
{
withAnimation(.interpolatingSpring(stiffness: 230, damping: 22)) {
activeTool = tools[index]
}
withAnimation(.linear(duration: 0.25)) {
page = activeTool?.page ?? .info
}
}
}
.onEnded { _ in
withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 1, blendDuration: 1)) {
startedToolPosition = .zero
}
Delay.by(2) {
lowerOpacity()
}
}
)
#endif
}
#if !os(tvOS)
.onHover { hovering in
hovering ? resetOpacity(0.2) : lowerOpacity(0.2)
}
#endif
.onAppear {
Delay.by(2) { lowerOpacity() }
}
.opacity(opacity)
.background(
Rectangle()
.contentShape(Rectangle())
.foregroundColor(.clear)
)
}
func lowerOpacity(_ duration: Double = 1.0) {
withAnimation(.linear(duration: duration)) {
opacity = Self.lowOpacity
}
}
func resetOpacity(_ duration: Double = 1.0) {
withAnimation(.linear(duration: duration)) {
opacity = 1
}
}
@ViewBuilder func ToolView(tool: Binding<VideoDetailsTool>) -> some View {
HStack(spacing: 0) {
Image(systemName: tool.wrappedValue.icon)
.font(.title2)
.foregroundColor(.white)
.frame(width: 30, height: 30)
.layoutPriority(1)
if activeToolID == tool.wrappedValue.id,
playerDetailsPageButtonLabelStyle.text,
player.playerSize.width > 450
{
Text(tool.wrappedValue.name.localized())
.font(.system(size: 14).bold())
.padding(.trailing, 4)
.foregroundColor(.white)
.allowsTightening(true)
.lineLimit(1)
}
}
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(activeToolID == tool.wrappedValue.id ? Color.accentColor : Color.secondary)
)
.background(
GeometryReader { proxy in
let frame = proxy.frame(in: .named("toolbarArea"))
Color.clear
.preference(key: RectKey.self, value: frame)
.onPreferenceChange(RectKey.self) { rect in
tool.wrappedValue.toolPostion = rect
}
}
)
}
var visibleToolsCount: Int {
tools.filter { $0.isAvailable(for: video, sidebarQueue: sidebarQueue) }.count
}
var activeToolID: VideoDetailsTool.ID {
activeTool?.id ?? "info"
}
}
struct VideoDetailsToolbar_Previews: PreviewProvider {
static var previews: some View {
VideoDetailsToolbar(page: .constant(.queue), sidebarQueue: false)
.injectFixtureEnvironmentObjects()
}
}