mirror of
https://github.com/yattee/yattee.git
synced 2025-08-05 02:04:07 +00:00
Watch next view
This commit is contained in:
@@ -316,6 +316,9 @@ struct PlayerControls: View {
|
||||
|
||||
private var closeVideoButton: some View {
|
||||
button("Close", systemImage: "xmark") {
|
||||
// TODO: Setting
|
||||
// WatchNextViewModel.shared.prepareForEmptyPlayerPlaceholder(player.currentItem)
|
||||
// WatchNextViewModel.shared.open()
|
||||
player.closeCurrentItem()
|
||||
}
|
||||
#if os(tvOS)
|
||||
|
@@ -4,10 +4,10 @@ import SwiftUI
|
||||
struct VideoDetailsOverlay: View {
|
||||
@ObservedObject private var controls = PlayerControlsModel.shared
|
||||
|
||||
@State private var detailsPage = VideoDetails.DetailsPage.queue
|
||||
@State private var detailsPage = VideoDetails.DetailsPage.info
|
||||
|
||||
var body: some View {
|
||||
VideoDetails(page: $detailsPage, sidebarQueue: .constant(false), fullScreen: fullScreenBinding)
|
||||
VideoDetails(video: PlayerModel.shared.currentVideo, page: $detailsPage, sidebarQueue: .constant(false), fullScreen: fullScreenBinding)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
}
|
||||
|
||||
|
@@ -63,6 +63,10 @@ struct VideoActions: View {
|
||||
if player.currentItem != nil {
|
||||
Spacer()
|
||||
actionButton("Close", systemImage: "xmark") {
|
||||
// TODO: setting
|
||||
// player.pause()
|
||||
// WatchNextViewModel.shared.prepareForEmptyPlayerPlaceholder(player.currentItem)
|
||||
// WatchNextViewModel.shared.open()
|
||||
player.closeCurrentItem()
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@ struct VideoDetails: View {
|
||||
case info, inspector, chapters, comments, related, queue
|
||||
}
|
||||
|
||||
var video: Video?
|
||||
|
||||
@Binding var page: DetailsPage
|
||||
@Binding var sidebarQueue: Bool
|
||||
@Binding var fullScreen: Bool
|
||||
@@ -25,16 +27,12 @@ struct VideoDetails: View {
|
||||
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
let comments = CommentsModel.shared
|
||||
@ObservedObject private var player = PlayerModel.shared
|
||||
var player = PlayerModel.shared
|
||||
|
||||
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
|
||||
@Default(.detailsToolbarPosition) private var detailsToolbarPosition
|
||||
@Default(.playerSidebar) private var playerSidebar
|
||||
|
||||
var video: Video? {
|
||||
player.currentVideo
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ControlsBar(
|
||||
@@ -46,13 +44,15 @@ struct VideoDetails: View {
|
||||
detailsTogglePlayer: false,
|
||||
detailsToggleFullScreen: true
|
||||
)
|
||||
.animation(nil, value: player.currentItem)
|
||||
|
||||
VideoActions(video: video)
|
||||
.animation(nil, value: player.currentItem)
|
||||
|
||||
ZStack(alignment: .bottom) {
|
||||
currentPage
|
||||
.frame(maxWidth: detailsSize.width)
|
||||
.transition(.fade)
|
||||
.animation(nil, value: player.currentItem)
|
||||
|
||||
HStack {
|
||||
if detailsToolbarPosition.needsLeftSpacer { Spacer() }
|
||||
@@ -68,26 +68,16 @@ struct VideoDetails: View {
|
||||
.offset(y: bottomPadding ? -SafeArea.insets.bottom : 0)
|
||||
#endif
|
||||
}
|
||||
.onChange(of: player.currentItem) { newItem in
|
||||
Delay.by(0.2) {
|
||||
guard let newItem else {
|
||||
page = sidebarQueue ? .inspector : .queue
|
||||
return
|
||||
}
|
||||
|
||||
if let video = newItem.video {
|
||||
page = video.isLocal ? .inspector : .info
|
||||
} else {
|
||||
page = sidebarQueue ? .inspector : .queue
|
||||
}
|
||||
}
|
||||
.onChange(of: player.currentItem) { _ in
|
||||
page = .info
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if video.isNil ||
|
||||
!VideoDetailsTool.find(for: page)!.isAvailable(for: video!, sidebarQueue: sidebarQueue)
|
||||
{
|
||||
page = video == nil ? (sidebarQueue ? .inspector : .queue) : (video!.isLocal ? .inspector : .info)
|
||||
guard let video, video.isLocal else { return }
|
||||
page = .info
|
||||
}
|
||||
|
||||
guard video != nil, accounts.app.supportsSubscriptions else {
|
||||
@@ -146,14 +136,14 @@ struct VideoDetails: View {
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.frame(maxHeight: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
@State private var detailsSize = CGSize.zero
|
||||
|
||||
var detailsPage: some View {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
if let video {
|
||||
if let video, player.videoBeingOpened == nil {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
videoProperties
|
||||
|
||||
@@ -248,7 +238,7 @@ struct VideoDetails: View {
|
||||
|
||||
struct VideoDetails_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VideoDetails(page: .constant(.info), sidebarQueue: .constant(true), fullScreen: .constant(false))
|
||||
VideoDetails(video: .fixture, page: .constant(.info), sidebarQueue: .constant(true), fullScreen: .constant(false))
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ struct VideoDetailsTool: Identifiable {
|
||||
}
|
||||
switch page {
|
||||
case .info:
|
||||
return video != nil && !video!.isLocal
|
||||
return true
|
||||
case .inspector:
|
||||
return video == nil || Defaults[.showInspector] == .always || video!.isLocal
|
||||
case .chapters:
|
||||
|
@@ -142,7 +142,7 @@ struct VideoDetailsToolbar: View {
|
||||
}
|
||||
|
||||
var activeToolID: VideoDetailsTool.ID {
|
||||
activeTool?.id ?? "queue"
|
||||
activeTool?.id ?? "info"
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -79,6 +79,8 @@ struct VideoPlayerView: View {
|
||||
#endif
|
||||
|
||||
overlay
|
||||
|
||||
WatchNextView()
|
||||
}
|
||||
.onAppear {
|
||||
if player.musicMode {
|
||||
@@ -490,6 +492,8 @@ struct VideoPlayerView: View {
|
||||
struct VideoPlayerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VideoPlayerView()
|
||||
.injectFixtureEnvironmentObjects()
|
||||
.onAppear {
|
||||
OutroViewModel.shared.prepareForEmptyPlayerPlaceholder(.init(.fixture))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
172
Shared/Player/WatchNextView.swift
Normal file
172
Shared/Player/WatchNextView.swift
Normal file
@@ -0,0 +1,172 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct WatchNextView: View {
|
||||
@ObservedObject private var model = WatchNextViewModel.shared
|
||||
@ObservedObject private var player = PlayerModel.shared
|
||||
|
||||
@Default(.saveHistory) private var saveHistory
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
#if os(iOS)
|
||||
NavigationView {
|
||||
watchNext
|
||||
}
|
||||
#else
|
||||
VStack {
|
||||
HStack {
|
||||
closeButton
|
||||
Spacer()
|
||||
reopenButton
|
||||
}
|
||||
.padding()
|
||||
watchNext
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if os(tvOS)
|
||||
.background(Color.background(scheme: colorScheme))
|
||||
#else
|
||||
.background(Color.background)
|
||||
#endif
|
||||
.opacity(model.presentingOutro ? 1 : 0)
|
||||
}
|
||||
|
||||
var watchNext: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
if model.isAutoplaying,
|
||||
let item = nextFromTheQueue
|
||||
{
|
||||
HStack {
|
||||
Text("Playing Next in 5...")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
model.cancelAutoplay()
|
||||
} label: {
|
||||
Label("Cancel", systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
|
||||
PlayerQueueRow(item: item)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
moreVideos
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle("Watch Next")
|
||||
#if !os(macOS)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
closeButton
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
reopenButton
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var closeButton: some View {
|
||||
Button {
|
||||
player.closeCurrentItem()
|
||||
player.hide(animate: true)
|
||||
Delay.by(0.8) {
|
||||
model.presentingOutro = false
|
||||
}
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var reopenButton: some View {
|
||||
if player.currentItem != nil, model.item != nil {
|
||||
Button {
|
||||
model.close()
|
||||
} label: {
|
||||
Label("Back to last video", systemImage: "arrow.counterclockwise")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var moreVideos: some View {
|
||||
VStack(spacing: 12) {
|
||||
let queueForMoreVideos = player.queue.isEmpty ? [] : player.queue.suffix(from: model.isAutoplaying ? 1 : 0)
|
||||
if !queueForMoreVideos.isEmpty {
|
||||
VStack(spacing: 12) {
|
||||
Text("Next in Queue")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.headline)
|
||||
|
||||
ForEach(queueForMoreVideos) { item in
|
||||
ContentItemView(item: .init(video: item.video))
|
||||
.environment(\.listingStyle, .list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let item = model.item {
|
||||
VStack(spacing: 12) {
|
||||
Text("Related videos")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.headline)
|
||||
|
||||
ForEach(item.video.related) { video in
|
||||
ContentItemView(item: .init(video: video))
|
||||
.environment(\.listingStyle, .list)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
|
||||
if saveHistory {
|
||||
VStack(spacing: 12) {
|
||||
Text("History")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.headline)
|
||||
|
||||
HStack {
|
||||
Text("Playing Next in 5...")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
model.cancelAutoplay()
|
||||
} label: {
|
||||
Label("Cancel", systemImage: "pause.fill")
|
||||
}
|
||||
}
|
||||
|
||||
HistoryView(limit: 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nextFromTheQueue: PlayerQueueItem? {
|
||||
if player.playbackMode == .related {
|
||||
return player.autoplayItem
|
||||
} else if player.playbackMode == .queue {
|
||||
return player.queue.first
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct OutroView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WatchNextView()
|
||||
.onAppear {
|
||||
WatchNextViewModel.shared.prepareForNextItem(.init(.fixture))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user