mirror of
https://github.com/yattee/yattee.git
synced 2026-07-21 06:42:01 +00:00
Refine macOS player controls: add queue, more, previous; drop seek/fullscreen buttons
Add Queue (list.bullet), More (VideoContextMenuView ellipsis), and Play Previous buttons to the macOS control bar. Play Previous is shown always and disabled when the queue has no previous item. Remove the skip-back/forward 10s buttons and the fullscreen button from the bar (arrow-key seek and F-key fullscreen shortcuts are retained). Queue reuses the existing cross-platform queue sheet; More reuses the shared VideoContextMenuView (already !os(tvOS)), styled borderless to match the flat bar buttons.
This commit is contained in:
@@ -904,6 +904,9 @@ extension ExpandedPlayerSheet {
|
|||||||
onPlayNext: {
|
onPlayNext: {
|
||||||
await playerService.playNext()
|
await playerService.playNext()
|
||||||
},
|
},
|
||||||
|
onPlayPrevious: {
|
||||||
|
await playerService.playPrevious()
|
||||||
|
},
|
||||||
onVolumeChanged: { [weak appEnvironment] volume in
|
onVolumeChanged: { [weak appEnvironment] volume in
|
||||||
playerService.currentBackend?.volume = volume
|
playerService.currentBackend?.volume = volume
|
||||||
appEnvironment?.settingsManager.playerVolume = volume
|
appEnvironment?.settingsManager.playerVolume = volume
|
||||||
@@ -917,6 +920,9 @@ extension ExpandedPlayerSheet {
|
|||||||
},
|
},
|
||||||
onShowSettings: { [self] in
|
onShowSettings: { [self] in
|
||||||
showingQualitySheet = true
|
showingQualitySheet = true
|
||||||
|
},
|
||||||
|
onShowQueue: { [self] in
|
||||||
|
showingQueueSheet = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.frame(width: controlsWidth, height: controlsHeight)
|
.frame(width: controlsWidth, height: controlsHeight)
|
||||||
@@ -1160,6 +1166,9 @@ extension ExpandedPlayerSheet {
|
|||||||
onPlayNext: {
|
onPlayNext: {
|
||||||
await playerService.playNext()
|
await playerService.playNext()
|
||||||
},
|
},
|
||||||
|
onPlayPrevious: {
|
||||||
|
await playerService.playPrevious()
|
||||||
|
},
|
||||||
onVolumeChanged: { [weak appEnvironment] volume in
|
onVolumeChanged: { [weak appEnvironment] volume in
|
||||||
playerService.currentBackend?.volume = volume
|
playerService.currentBackend?.volume = volume
|
||||||
appEnvironment?.settingsManager.playerVolume = volume
|
appEnvironment?.settingsManager.playerVolume = volume
|
||||||
@@ -1173,6 +1182,9 @@ extension ExpandedPlayerSheet {
|
|||||||
},
|
},
|
||||||
onShowSettings: { [self] in
|
onShowSettings: { [self] in
|
||||||
showingQualitySheet = true
|
showingQualitySheet = true
|
||||||
|
},
|
||||||
|
onShowQueue: { [self] in
|
||||||
|
showingQueueSheet = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.frame(width: controlsWidth, height: controlsHeight)
|
.frame(width: controlsWidth, height: controlsHeight)
|
||||||
@@ -1560,6 +1572,9 @@ extension ExpandedPlayerSheet {
|
|||||||
onPlayNext: {
|
onPlayNext: {
|
||||||
await playerService.playNext()
|
await playerService.playNext()
|
||||||
},
|
},
|
||||||
|
onPlayPrevious: {
|
||||||
|
await playerService.playPrevious()
|
||||||
|
},
|
||||||
onVolumeChanged: { [weak appEnvironment] volume in
|
onVolumeChanged: { [weak appEnvironment] volume in
|
||||||
playerService.currentBackend?.volume = volume
|
playerService.currentBackend?.volume = volume
|
||||||
appEnvironment?.settingsManager.playerVolume = volume
|
appEnvironment?.settingsManager.playerVolume = volume
|
||||||
@@ -1573,6 +1588,9 @@ extension ExpandedPlayerSheet {
|
|||||||
},
|
},
|
||||||
onShowSettings: { [self] in
|
onShowSettings: { [self] in
|
||||||
showingQualitySheet = true
|
showingQualitySheet = true
|
||||||
|
},
|
||||||
|
onShowQueue: { [self] in
|
||||||
|
showingQueueSheet = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.frame(width: availableWidth, height: availableHeight)
|
.frame(width: availableWidth, height: availableHeight)
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ struct MacOSControlBar: View {
|
|||||||
var isFullscreen: Bool = false
|
var isFullscreen: Bool = false
|
||||||
var onTogglePiP: (() -> Void)? = nil
|
var onTogglePiP: (() -> Void)? = nil
|
||||||
var onPlayNext: (() async -> Void)? = nil
|
var onPlayNext: (() async -> Void)? = nil
|
||||||
|
var onPlayPrevious: (() async -> Void)? = nil
|
||||||
var onVolumeChanged: ((Float) -> Void)? = nil
|
var onVolumeChanged: ((Float) -> Void)? = nil
|
||||||
var onMuteToggled: (() -> Void)? = nil
|
var onMuteToggled: (() -> Void)? = nil
|
||||||
var onShowSettings: (() -> Void)? = nil
|
var onShowSettings: (() -> Void)? = nil
|
||||||
|
var onShowQueue: (() -> Void)? = nil
|
||||||
/// Whether to show chapter markers on the progress bar (default: true)
|
/// Whether to show chapter markers on the progress bar (default: true)
|
||||||
var showChapters: Bool = true
|
var showChapters: Bool = true
|
||||||
/// SponsorBlock segments to display on the progress bar.
|
/// SponsorBlock segments to display on the progress bar.
|
||||||
@@ -47,8 +49,7 @@ struct MacOSControlBar: View {
|
|||||||
@State private var isHoveringProgress = false
|
@State private var isHoveringProgress = false
|
||||||
@State private var hoverProgress: Double = 0
|
@State private var hoverProgress: Double = 0
|
||||||
@State private var playNextTapCount = 0
|
@State private var playNextTapCount = 0
|
||||||
@State private var seekBackwardTrigger = 0
|
@State private var playPreviousTapCount = 0
|
||||||
@State private var seekForwardTrigger = 0
|
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
@@ -149,19 +150,21 @@ struct MacOSControlBar: View {
|
|||||||
|
|
||||||
private var transportControls: some View {
|
private var transportControls: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
// Skip backward
|
// Play previous (disabled when no previous in queue)
|
||||||
|
if let onPlayPrevious {
|
||||||
Button {
|
Button {
|
||||||
seekBackwardTrigger += 1
|
playPreviousTapCount += 1
|
||||||
Task { await onSeekBackward(10) }
|
Task { await onPlayPrevious() }
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "10.arrow.trianglehead.counterclockwise")
|
Image(systemName: "backward.fill")
|
||||||
.font(.system(size: 14, weight: .medium))
|
.font(.system(size: 12, weight: .medium))
|
||||||
.frame(width: 28, height: 28)
|
.frame(width: 28, height: 28)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekBackwardTrigger)
|
.symbolEffect(.bounce.down.byLayer, options: .nonRepeating, value: playPreviousTapCount)
|
||||||
}
|
}
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
.buttonStyle(MacOSControlButtonStyle())
|
||||||
.disabled(isTransportDisabled)
|
.disabled(!playerState.hasPrevious)
|
||||||
|
}
|
||||||
|
|
||||||
// Play/Pause
|
// Play/Pause
|
||||||
Button {
|
Button {
|
||||||
@@ -177,20 +180,6 @@ struct MacOSControlBar: View {
|
|||||||
.disabled(isTransportDisabled)
|
.disabled(isTransportDisabled)
|
||||||
.opacity(isTransportDisabled ? 0.3 : 1.0)
|
.opacity(isTransportDisabled ? 0.3 : 1.0)
|
||||||
|
|
||||||
// Skip forward
|
|
||||||
Button {
|
|
||||||
seekForwardTrigger += 1
|
|
||||||
Task { await onSeekForward(10) }
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "10.arrow.trianglehead.clockwise")
|
|
||||||
.font(.system(size: 14, weight: .medium))
|
|
||||||
.frame(width: 28, height: 28)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekForwardTrigger)
|
|
||||||
}
|
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
|
||||||
.disabled(isTransportDisabled)
|
|
||||||
|
|
||||||
// Play next (if queue has items)
|
// Play next (if queue has items)
|
||||||
if let onPlayNext, playerState.hasNext {
|
if let onPlayNext, playerState.hasNext {
|
||||||
Button {
|
Button {
|
||||||
@@ -337,6 +326,32 @@ struct MacOSControlBar: View {
|
|||||||
|
|
||||||
private var trailingActionControls: some View {
|
private var trailingActionControls: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
|
// Queue
|
||||||
|
if let onShowQueue {
|
||||||
|
Button {
|
||||||
|
onShowQueue()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "list.bullet")
|
||||||
|
.font(.system(size: 13, weight: .medium))
|
||||||
|
.frame(width: 28, height: 28)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.buttonStyle(MacOSControlButtonStyle())
|
||||||
|
}
|
||||||
|
|
||||||
|
// More (context menu)
|
||||||
|
if let video = playerState.currentVideo {
|
||||||
|
VideoContextMenuView(
|
||||||
|
video: video,
|
||||||
|
accentColor: .primary,
|
||||||
|
buttonSize: 28,
|
||||||
|
buttonBackgroundStyle: .none,
|
||||||
|
theme: .dark
|
||||||
|
)
|
||||||
|
.menuStyle(.borderlessButton)
|
||||||
|
.fixedSize()
|
||||||
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
if let onShowSettings {
|
if let onShowSettings {
|
||||||
Button {
|
Button {
|
||||||
@@ -362,19 +377,6 @@ struct MacOSControlBar: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
.buttonStyle(MacOSControlButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fullscreen
|
|
||||||
if let onToggleFullscreen {
|
|
||||||
Button {
|
|
||||||
onToggleFullscreen()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: isFullscreen ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right")
|
|
||||||
.font(.system(size: 13, weight: .medium))
|
|
||||||
.frame(width: 28, height: 28)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ struct MacOSPlayerControlsView: View {
|
|||||||
var onClose: (() -> Void)? = nil
|
var onClose: (() -> Void)? = nil
|
||||||
var onTogglePiP: (() -> Void)? = nil
|
var onTogglePiP: (() -> Void)? = nil
|
||||||
var onPlayNext: (() async -> Void)? = nil
|
var onPlayNext: (() async -> Void)? = nil
|
||||||
|
var onPlayPrevious: (() async -> Void)? = nil
|
||||||
var onVolumeChanged: ((Float) -> Void)? = nil
|
var onVolumeChanged: ((Float) -> Void)? = nil
|
||||||
var onMuteToggled: (() -> Void)? = nil
|
var onMuteToggled: (() -> Void)? = nil
|
||||||
var onShowSettings: (() -> Void)? = nil
|
var onShowSettings: (() -> Void)? = nil
|
||||||
|
var onShowQueue: (() -> Void)? = nil
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
@@ -91,9 +93,11 @@ struct MacOSPlayerControlsView: View {
|
|||||||
isFullscreen: isFullscreen,
|
isFullscreen: isFullscreen,
|
||||||
onTogglePiP: onTogglePiP,
|
onTogglePiP: onTogglePiP,
|
||||||
onPlayNext: onPlayNext,
|
onPlayNext: onPlayNext,
|
||||||
|
onPlayPrevious: onPlayPrevious,
|
||||||
onVolumeChanged: onVolumeChanged,
|
onVolumeChanged: onVolumeChanged,
|
||||||
onMuteToggled: onMuteToggled,
|
onMuteToggled: onMuteToggled,
|
||||||
onShowSettings: onShowSettings,
|
onShowSettings: onShowSettings,
|
||||||
|
onShowQueue: onShowQueue,
|
||||||
sponsorSegments: playerState.sponsorSegments,
|
sponsorSegments: playerState.sponsorSegments,
|
||||||
onInteractionStarted: {
|
onInteractionStarted: {
|
||||||
isInteracting = true
|
isInteracting = true
|
||||||
|
|||||||
Reference in New Issue
Block a user