Watch next view

This commit is contained in:
Arkadiusz Fal
2022-12-18 00:08:30 +01:00
parent fcf527fa87
commit eca685ae29
17 changed files with 349 additions and 58 deletions

View File

@@ -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)

View File

@@ -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))
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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:

View File

@@ -142,7 +142,7 @@ struct VideoDetailsToolbar: View {
}
var activeToolID: VideoDetailsTool.ID {
activeTool?.id ?? "queue"
activeTool?.id ?? "info"
}
}

View File

@@ -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))
}
}
}

View 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))
}
}
}