import SwiftUI /// Queue panel showing the current playback queue: Now Playing, User Queue, Up Next. struct QueueView: View { @Environment(PlayerViewModel.self) private var playerVM @EnvironmentObject private var theme: AppTheme var body: some View { VStack(spacing: 0) { // Header HStack { Text("Queue") .font(.system(size: 13, weight: .semibold)) Spacer() if !playerVM.userQueue.isEmpty || !playerVM.upNext.isEmpty { Button("Clear") { playerVM.clearQueue() } .font(.system(size: 11)) .foregroundStyle(.red) .buttonStyle(.plain) } } .padding(.horizontal, 12) .padding(.vertical, 8) .background(.bar) Divider() // Queue content List { // Now Playing if let nowPlaying = playerVM.nowPlayingEntry { Section("Now Playing") { queueRow(nowPlaying, isNowPlaying: true) } } // User Queue (manually added) if !playerVM.userQueue.isEmpty { Section("Next in Queue") { ForEach(playerVM.userQueue) { entry in queueRow(entry) } .onMove { source, destination in playerVM.moveUserQueueEntry(from: source, to: destination) } .onDelete { offsets in for index in offsets.sorted().reversed() { let entry = playerVM.userQueue[index] playerVM.removeFromQueue(entry: entry) } } } } // Up Next (auto from playlist) if !playerVM.upNext.isEmpty { Section("Up Next") { ForEach(playerVM.upNext) { entry in queueRow(entry) } .onMove { source, destination in playerVM.moveUpNextEntry(from: source, to: destination) } .onDelete { offsets in for index in offsets.sorted().reversed() { let entry = playerVM.upNext[index] playerVM.removeFromQueue(entry: entry) } } } } // Empty state if playerVM.nowPlayingEntry == nil && playerVM.userQueue.isEmpty && playerVM.upNext.isEmpty { Section { VStack(spacing: 12) { Image(systemName: "list.bullet") .font(.system(size: 36)) .foregroundStyle(.tertiary) Text("Queue is empty") .font(.title3) .foregroundStyle(.secondary) Text("Add tracks using \"Play Next\" or \"Add to Queue\" from any track's context menu.") .font(.caption) .foregroundStyle(.tertiary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 40) } } } .listStyle(.inset) } } @ViewBuilder private func queueRow(_ entry: QueueEntry, isNowPlaying: Bool = false) -> some View { HStack(spacing: 12) { // Cloud indicator or music note Group { switch entry.source { case .cloudDirect: Image(systemName: "cloud.fill") .foregroundStyle(Color.accentColor) case .swiftDataTrack(_, let isCloud, _): if isCloud { Image(systemName: "cloud.fill") .foregroundStyle(Color.accentColor) } else { Image(systemName: "music.note") .foregroundStyle(.tertiary) } } } .font(.caption) .frame(width: 20) VStack(alignment: .leading, spacing: 2) { Text(entry.title) .font(.system(size: 12, weight: isNowPlaying ? .semibold : .regular)) .foregroundStyle(isNowPlaying ? Color.accentColor : .primary) .lineLimit(1) if !entry.artist.isEmpty { Text(entry.artist) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) } } Spacer() Text(entry.formattedDuration) .font(.system(size: 11, design: .monospaced)) .foregroundStyle(.tertiary) if isNowPlaying && playerVM.isPlaying { Image(systemName: "speaker.wave.2.fill") .font(.caption) .foregroundStyle(Color.accentColor) } } .padding(.vertical, 2) } }