Watch Next menu improvements

This commit is contained in:
Arkadiusz Fal 2022-12-19 10:48:30 +01:00
parent 2ce903b6c3
commit 636e8205fe
5 changed files with 127 additions and 31 deletions

View File

@ -36,7 +36,7 @@ final class PlayerModel: ObservableObject {
case .queue: case .queue:
return "Queue" return "Queue"
case .shuffle: case .shuffle:
return "Queue, shuffled" return "Queue - shuffled"
case .loopOne: case .loopOne:
return "Loop one" return "Loop one"
case .related: case .related:

View File

@ -42,7 +42,7 @@ final class WatchNextViewModel: ObservableObject {
@Published var countdown = 0.0 @Published var countdown = 0.0
var countdownTimer: Timer? var countdownTimer: Timer?
private var player = PlayerModel.shared var player = PlayerModel.shared
var autoplayTimer: Timer? var autoplayTimer: Timer?
@ -129,7 +129,7 @@ final class WatchNextViewModel: ObservableObject {
private func open(reason: PresentationReason) { private func open(reason: PresentationReason) {
self.reason = reason self.reason = reason
page = Page.allCases.first { isAvailable($0) } ?? .history setPageAfterOpening()
guard !isPresenting else { return } guard !isPresenting else { return }
withAnimation(Self.animation) { withAnimation(Self.animation) {
@ -137,6 +137,19 @@ final class WatchNextViewModel: ObservableObject {
} }
} }
private func setPageAfterOpening() {
let firstAvailable = Page.allCases.first { isAvailable($0) } ?? .history
switch reason {
case .finishedWatching:
page = player.playbackMode == .related ? .queue : firstAvailable
case .closed:
page = player.playbackMode == .related ? .queue : firstAvailable
default:
page = firstAvailable
}
}
func close() { func close() {
let close = { let close = {
self.player.closeCurrentItem() self.player.closeCurrentItem()

View File

@ -87,11 +87,9 @@ struct PlayerQueueView: View {
ForEach(player.queue) { item in ForEach(player.queue) { item in
PlayerQueueRow(item: item) PlayerQueueRow(item: item)
.contextMenu { .contextMenu {
removeButton(item)
removeAllButton()
if let video = item.video { if let video = item.video {
VideoContextMenuView(video: video) VideoContextMenuView(video: video)
.environment(\.inQueueListing, true)
} }
} }
} }
@ -116,22 +114,6 @@ struct PlayerQueueView: View {
.transaction { t in t.disablesAnimations = true } .transaction { t in t.disablesAnimations = true }
} }
} }
private func removeButton(_ item: PlayerQueueItem) -> some View {
Button {
player.remove(item)
} label: {
Label("Remove from the queue", systemImage: "trash")
}
}
private func removeAllButton() -> some View {
Button {
player.removeQueueItems()
} label: {
Label("Clear the queue", systemImage: "trash.fill")
}
}
} }
struct PlayerQueueView_Previews: PreviewProvider { struct PlayerQueueView_Previews: PreviewProvider {

View File

@ -96,13 +96,13 @@ struct VideoDetails: View {
ContentItem(video: player.currentVideo) ContentItem(video: player.currentVideo)
} }
var pageMenu: some View { @ViewBuilder var pageMenu: some View {
#if os(macOS) #if os(macOS)
pagePicker pagePicker
.labelsHidden() .labelsHidden()
.offset(x: 15, y: 15) .offset(x: 15, y: 15)
.frame(maxWidth: 200) .frame(maxWidth: 200)
#else #elseif os(iOS)
Menu { Menu {
pagePicker pagePicker
} label: { } label: {
@ -224,6 +224,8 @@ struct VideoDetails: View {
.secondaryBackground .secondaryBackground
#elseif os(iOS) #elseif os(iOS)
.background .background
#else
.clear
#endif #endif
} }

View File

@ -35,8 +35,15 @@ struct WatchNextView: View {
Spacer() Spacer()
HStack { HStack {
if model.isRestartable { Text("Mode")
reopenButton .foregroundColor(.secondary)
playbackModeControl
HStack {
if model.isRestartable {
reopenButton
}
} }
} }
.frame(maxWidth: .infinity, alignment: .trailing) .frame(maxWidth: .infinity, alignment: .trailing)
@ -83,6 +90,9 @@ struct WatchNextView: View {
#endif #endif
PlayerQueueRow(item: item) PlayerQueueRow(item: item)
Divider()
.padding(.vertical, 5)
} }
moreVideos moreVideos
@ -123,6 +133,7 @@ struct WatchNextView: View {
#else #else
Menu { Menu {
pagePicker pagePicker
playbackModePicker
} label: { } label: {
HStack(spacing: 12) { HStack(spacing: 12) {
menuLabel menuLabel
@ -142,7 +153,7 @@ struct WatchNextView: View {
HStack { HStack {
Image(systemName: model.page.systemImageName) Image(systemName: model.page.systemImageName)
.imageScale(.small) .imageScale(.small)
Text(model.page.title) Text(model.page == .queue ? queueTitle : model.page.title)
.font(.headline) .font(.headline)
} }
} }
@ -150,12 +161,19 @@ struct WatchNextView: View {
var pagePicker: some View { var pagePicker: some View {
Picker("Page", selection: $model.page) { Picker("Page", selection: $model.page) {
ForEach(WatchNextViewModel.Page.allCases, id: \.rawValue) { page in ForEach(WatchNextViewModel.Page.allCases, id: \.rawValue) { page in
Label(page.title, systemImage: page.systemImageName) Label(
.tag(page) page == .queue ? queueTitle : page.title,
systemImage: page.systemImageName
)
.tag(page)
} }
} }
} }
var queueTitle: String {
"\(WatchNextViewModel.Page.queue.title)\(player.queue.count)"
}
@ViewBuilder var hideCloseButton: some View { @ViewBuilder var hideCloseButton: some View {
if model.isHideable { if model.isHideable {
hideButton hideButton
@ -194,14 +212,30 @@ struct WatchNextView: View {
VStack(spacing: 12) { VStack(spacing: 12) {
switch model.page { switch model.page {
case .queue: case .queue:
let queueForMoreVideos = player.queue.isEmpty ? [] : player.queue.suffix(from: model.isAutoplaying ? 1 : 0)
if player.playbackMode == .related, !(model.isAutoplaying && model.canAutoplay) {
autoplaying
Divider()
}
let queueForMoreVideos = player.queue.isEmpty ? [] : player.queue.suffix(from: player.playbackMode == .queue ? 1 : 0)
if (model.isAutoplaying && model.canAutoplay && !queueForMoreVideos.isEmpty) ||
(!model.isAutoplaying && !queueForMoreVideos.isEmpty)
{
Text("Next in queue")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
}
if !queueForMoreVideos.isEmpty { if !queueForMoreVideos.isEmpty {
ForEach(queueForMoreVideos) { item in ForEach(queueForMoreVideos) { item in
ContentItemView(item: .init(video: item.video)) ContentItemView(item: .init(video: item.video))
.environment(\.inQueueListing, true) .environment(\.inQueueListing, true)
.environment(\.listingStyle, .list) .environment(\.listingStyle, .list)
} }
} else if player.playbackMode != .related && player.playbackMode != .loopOne { } else {
Label( Label(
model.isAutoplaying ? "Nothing more in the queue" : "Queue is empty", model.isAutoplaying ? "Nothing more in the queue" : "Queue is empty",
systemImage: WatchNextViewModel.Page.queue.systemImageName systemImage: WatchNextViewModel.Page.queue.systemImageName
@ -226,6 +260,71 @@ struct WatchNextView: View {
} }
} }
} }
@ViewBuilder var playbackModeControl: some View {
#if os(tvOS)
Button {
player.playbackMode = player.playbackMode.next()
} label: {
Label(player.playbackMode.description, systemImage: player.playbackMode.systemImage)
}
#elseif os(macOS)
playbackModePicker
.modifier(SettingsPickerModifier())
#if os(macOS)
.frame(maxWidth: 150)
#endif
#else
Menu {
playbackModePicker
} label: {
Label(player.playbackMode.description, systemImage: player.playbackMode.systemImage)
}
#endif
}
var playbackModePicker: some View {
Picker("Playback Mode", selection: $model.player.playbackMode) {
ForEach(PlayerModel.PlaybackMode.allCases, id: \.rawValue) { mode in
Label(mode.description, systemImage: mode.systemImage).tag(mode)
}
}
.labelsHidden()
}
@ViewBuilder var autoplaying: some View {
Section(header: autoplayingHeader) {
if let item = player.autoplayItem {
PlayerQueueRow(item: item, autoplay: true)
} else {
Group {
if player.currentItem.isNil {
Text("Not Playing")
} else {
Text("Finding something to play...")
}
}
.foregroundColor(.secondary)
}
}
}
var autoplayingHeader: some View {
HStack {
Text("Autoplaying Next")
.font(.headline)
Spacer()
Button {
player.setRelatedAutoplayItem()
} label: {
Label("Find Other", systemImage: "arrow.triangle.2.circlepath.circle")
.labelStyle(.iconOnly)
.foregroundColor(.accentColor)
}
.disabled(player.currentItem.isNil)
.buttonStyle(.plain)
}
}
} }
struct WatchNextView_Previews: PreviewProvider { struct WatchNextView_Previews: PreviewProvider {