diff --git a/Yattee/Views/Player/QueueManagementSheet.swift b/Yattee/Views/Player/QueueManagementSheet.swift index e6920e66..ffad4890 100644 --- a/Yattee/Views/Player/QueueManagementSheet.swift +++ b/Yattee/Views/Player/QueueManagementSheet.swift @@ -41,6 +41,7 @@ struct QueueManagementSheet: View { queueModeMenu } + #if !os(tvOS) ToolbarItem(placement: .confirmationAction) { Button(role: .cancel) { dismiss() @@ -49,6 +50,7 @@ struct QueueManagementSheet: View { .labelStyle(.iconOnly) } } + #endif } } .presentationDragIndicator(.visible) @@ -209,6 +211,14 @@ struct QueueManagementSheet: View { /// Menu for selecting queue mode (shuffle, repeat, etc.) @ViewBuilder private var queueModeMenu: some View { + #if os(tvOS) + Button { + cycleQueueMode() + } label: { + Image(systemName: playerState?.queueMode.icon ?? "list.bullet") + .foregroundStyle(.tint) + } + #else Menu { ForEach(QueueMode.allCases, id: \.self) { mode in Button { @@ -226,6 +236,15 @@ struct QueueManagementSheet: View { } .foregroundStyle(.tint) } + #endif + } + + private func cycleQueueMode() { + guard let playerState else { return } + let modes = QueueMode.allCases + let currentIndex = modes.firstIndex(of: playerState.queueMode) ?? 0 + let nextIndex = (currentIndex + 1) % modes.count + playerState.queueMode = modes[nextIndex] } // MARK: - Actions diff --git a/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift b/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift index 761a48b0..88120e64 100644 --- a/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift +++ b/Yattee/Views/Player/tvOS/TVPlayerControlsView.swift @@ -16,6 +16,7 @@ struct TVPlayerControlsView: View { @FocusState.Binding var focusedControl: TVPlayerFocusTarget? let onShowSettings: () -> Void + let onShowQueue: () -> Void let onShowDetails: () -> Void let onShowComments: () -> Void let onShowDebug: () -> Void @@ -241,15 +242,20 @@ struct TVPlayerControlsView: View { Spacer() - // Queue indicator (if videos in queue) + // Queue button (if videos in queue) if let state = playerState, state.hasNext { - HStack(spacing: 8) { - Image(systemName: "list.bullet") - .font(.system(size: 20)) - Text(String(localized: "queue.section.count \(state.queue.count)")) - .font(.subheadline) + Button { + onShowQueue() + } label: { + VStack(spacing: 6) { + Image(systemName: "list.bullet") + .font(.system(size: 28)) + Text(String(localized: "queue.section.count \(state.queue.count)")) + .font(.caption) + } } - .foregroundStyle(.white.opacity(0.6)) + .buttonStyle(TVActionButtonStyle()) + .focused($focusedControl, equals: .queueButton) } } } diff --git a/Yattee/Views/Player/tvOS/TVPlayerView.swift b/Yattee/Views/Player/tvOS/TVPlayerView.swift index 6c6e6a77..b7cdb00c 100644 --- a/Yattee/Views/Player/tvOS/TVPlayerView.swift +++ b/Yattee/Views/Player/tvOS/TVPlayerView.swift @@ -18,6 +18,7 @@ enum TVPlayerFocusTarget: Hashable { case debugButton case playNext case closeButton + case queueButton } /// Main tvOS fullscreen player view. @@ -45,6 +46,9 @@ struct TVPlayerView: View { /// Whether the quality sheet is shown. @State private var showingQualitySheet = false + /// Whether the queue sheet is shown. + @State private var showingQueueSheet = false + /// Whether the debug overlay is shown. @State private var isDebugOverlayVisible = false @@ -109,6 +113,14 @@ struct TVPlayerView: View { .fullScreenCover(isPresented: $showingQualitySheet) { qualitySheetContent } + .fullScreenCover(isPresented: $showingQueueSheet) { + ZStack { + Rectangle() + .fill(.ultraThinMaterial) + .ignoresSafeArea() + QueueManagementSheet() + } + } } // MARK: - Quality Sheet Content @@ -195,6 +207,7 @@ struct TVPlayerView: View { playerService: playerService, focusedControl: $focusedControl, onShowSettings: { showQualitySheet() }, + onShowQueue: { showQueueSheet() }, onShowDetails: { showDetailsPanel(tab: .info) }, onShowComments: { showDetailsPanel(tab: .comments) }, onShowDebug: { showDebugOverlay() }, @@ -419,6 +432,11 @@ struct TVPlayerView: View { showingQualitySheet = true } + private func showQueueSheet() { + stopControlsTimer() + showingQueueSheet = true + } + private func switchToStream(_ stream: Stream, audioStream: Stream? = nil) { guard let video = playerState?.currentVideo else { return }