import Defaults import Foundation import SwiftUI struct PlayerQueueView: View { var sidebarQueue: Bool @Binding var fullScreen: Bool @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) var watches: FetchedResults @EnvironmentObject private var accounts @EnvironmentObject private var navigation @EnvironmentObject private var playlists @EnvironmentObject private var player @Default(.saveHistory) private var saveHistory @Default(.showHistoryInPlayer) private var showHistoryInPlayer var body: some View { List { Group { if player.playbackMode == .related { autoplaying } playingNext if sidebarQueue { related } if saveHistory, showHistoryInPlayer { playedPreviously } } .listRowBackground(Color.clear) #if !os(iOS) .padding(.vertical, 5) .listRowInsets(EdgeInsets()) #endif } #if os(macOS) .listStyle(.inset) #elseif os(iOS) .listStyle(.grouped) .backport .scrollContentBackground(false) #else .listStyle(.plain) #endif } @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") Spacer() Button { player.setRelatedAutoplayItem() } label: { Label("Find Other", systemImage: "arrow.triangle.2.circlepath.circle") .labelStyle(.iconOnly) } .disabled(player.currentItem.isNil) .buttonStyle(.plain) } } var playingNext: some View { Section(header: Text("Queue")) { if player.queue.isEmpty { Text("Queue is empty") .foregroundColor(.secondary) } ForEach(player.queue) { item in PlayerQueueRow(item: item, fullScreen: $fullScreen) .onAppear { player.loadQueueVideoDetails(item) } .contextMenu { removeButton(item) removeAllButton() } } } } private var visibleWatches: [Watch] { watches.filter { $0.videoID != player.currentVideo?.videoID } } var playedPreviously: some View { Group { if !visibleWatches.isEmpty { Section(header: Text("History")) { ForEach(visibleWatches, id: \.videoID) { watch in PlayerQueueRow( item: PlayerQueueItem.from(watch, video: player.historyVideo(watch.videoID)), history: true, fullScreen: $fullScreen ) .onAppear { player.loadHistoryVideoDetails(watch.videoID) } .contextMenu { removeHistoryButton(watch) } } } } } } private var related: some View { Group { if !player.currentVideo.isNil, !player.currentVideo!.related.isEmpty { Section(header: Text("Related")) { ForEach(player.currentVideo!.related) { video in PlayerQueueRow(item: PlayerQueueItem(video), fullScreen: $fullScreen) .contextMenu { Button { player.playNext(video) } label: { Label("Play Next", systemImage: "text.insert") } Button { player.enqueueVideo(video) } label: { Label("Play Last", systemImage: "text.append") } if accounts.app.supportsUserPlaylists && accounts.signedIn { Section { Button { navigation.presentAddToPlaylist(video) } label: { Label("Add to playlist...", systemImage: "text.badge.plus") } if let playlist = playlists.lastUsed { Button { playlists.addVideo(playlistID: playlist.id, videoID: video.videoID, navigation: navigation) } label: { Label("Add to \(playlist.title)", systemImage: "text.badge.star") } } } } } } } } } } private func removeButton(_ item: PlayerQueueItem) -> some View { Button { player.remove(item) } label: { Label("Remove", systemImage: "trash") } } private func removeAllButton() -> some View { Button { player.removeQueueItems() } label: { Label("Remove All", systemImage: "trash.fill") } } private func removeHistoryButton(_ watch: Watch) -> some View { Button { player.removeWatch(watch) } label: { Label("Remove", systemImage: "trash") } } } struct PlayerQueueView_Previews: PreviewProvider { static var previews: some View { VStack { PlayerQueueView(sidebarQueue: true, fullScreen: .constant(true)) } .injectFixtureEnvironmentObjects() } }