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:
return "Queue"
case .shuffle:
return "Queue, shuffled"
return "Queue - shuffled"
case .loopOne:
return "Loop one"
case .related:

View File

@ -42,7 +42,7 @@ final class WatchNextViewModel: ObservableObject {
@Published var countdown = 0.0
var countdownTimer: Timer?
private var player = PlayerModel.shared
var player = PlayerModel.shared
var autoplayTimer: Timer?
@ -129,7 +129,7 @@ final class WatchNextViewModel: ObservableObject {
private func open(reason: PresentationReason) {
self.reason = reason
page = Page.allCases.first { isAvailable($0) } ?? .history
setPageAfterOpening()
guard !isPresenting else { return }
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() {
let close = {
self.player.closeCurrentItem()

View File

@ -87,11 +87,9 @@ struct PlayerQueueView: View {
ForEach(player.queue) { item in
PlayerQueueRow(item: item)
.contextMenu {
removeButton(item)
removeAllButton()
if let video = item.video {
VideoContextMenuView(video: video)
.environment(\.inQueueListing, true)
}
}
}
@ -116,22 +114,6 @@ struct PlayerQueueView: View {
.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 {

View File

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

View File

@ -35,8 +35,15 @@ struct WatchNextView: View {
Spacer()
HStack {
if model.isRestartable {
reopenButton
Text("Mode")
.foregroundColor(.secondary)
playbackModeControl
HStack {
if model.isRestartable {
reopenButton
}
}
}
.frame(maxWidth: .infinity, alignment: .trailing)
@ -83,6 +90,9 @@ struct WatchNextView: View {
#endif
PlayerQueueRow(item: item)
Divider()
.padding(.vertical, 5)
}
moreVideos
@ -123,6 +133,7 @@ struct WatchNextView: View {
#else
Menu {
pagePicker
playbackModePicker
} label: {
HStack(spacing: 12) {
menuLabel
@ -142,7 +153,7 @@ struct WatchNextView: View {
HStack {
Image(systemName: model.page.systemImageName)
.imageScale(.small)
Text(model.page.title)
Text(model.page == .queue ? queueTitle : model.page.title)
.font(.headline)
}
}
@ -150,12 +161,19 @@ struct WatchNextView: View {
var pagePicker: some View {
Picker("Page", selection: $model.page) {
ForEach(WatchNextViewModel.Page.allCases, id: \.rawValue) { page in
Label(page.title, systemImage: page.systemImageName)
.tag(page)
Label(
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 {
if model.isHideable {
hideButton
@ -194,14 +212,30 @@ struct WatchNextView: View {
VStack(spacing: 12) {
switch model.page {
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 {
ForEach(queueForMoreVideos) { item in
ContentItemView(item: .init(video: item.video))
.environment(\.inQueueListing, true)
.environment(\.listingStyle, .list)
}
} else if player.playbackMode != .related && player.playbackMode != .loopOne {
} else {
Label(
model.isAutoplaying ? "Nothing more in the queue" : "Queue is empty",
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 {