mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 10:25:02 +00:00
Remove redundant center transport controls on tvOS
Siri Remote already handles play/pause and seeking natively, so the on-screen skip/play/pause cluster was duplicate UI. Initial and restored focus now targets the progress bar.
This commit is contained in:
@@ -29,8 +29,6 @@ struct TVPlayerControlsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@State private var playNextTapCount = 0
|
@State private var playNextTapCount = 0
|
||||||
@State private var seekBackwardTrigger = 0
|
|
||||||
@State private var seekForwardTrigger = 0
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -45,14 +43,6 @@ struct TVPlayerControlsView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Center transport controls - focus section for horizontal nav
|
|
||||||
transportControls
|
|
||||||
.focusSection()
|
|
||||||
// DEBUG: Uncomment to see focus section boundaries
|
|
||||||
// .border(.blue, width: 2)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// Progress bar - its own focus section
|
// Progress bar - its own focus section
|
||||||
TVPlayerProgressBar(
|
TVPlayerProgressBar(
|
||||||
currentTime: playerState?.currentTime ?? 0,
|
currentTime: playerState?.currentTime ?? 0,
|
||||||
@@ -142,73 +132,6 @@ struct TVPlayerControlsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Transport Controls
|
|
||||||
|
|
||||||
private var transportControls: some View {
|
|
||||||
HStack(spacing: 80) {
|
|
||||||
// Skip backward
|
|
||||||
Button {
|
|
||||||
seekBackwardTrigger += 1
|
|
||||||
playerService?.seekBackward(by: 10)
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "10.arrow.trianglehead.counterclockwise")
|
|
||||||
.font(.system(size: 52, weight: .medium))
|
|
||||||
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekBackwardTrigger)
|
|
||||||
}
|
|
||||||
.buttonStyle(TVTransportButtonStyle())
|
|
||||||
.focused($focusedControl, equals: .skipBackward)
|
|
||||||
.disabled(isTransportDisabled)
|
|
||||||
|
|
||||||
// Play/Pause - hide when transport disabled, show spacer to maintain layout
|
|
||||||
if !isTransportDisabled {
|
|
||||||
Button {
|
|
||||||
playerService?.togglePlayPause()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: playPauseIcon)
|
|
||||||
.font(.system(size: 72, weight: .medium))
|
|
||||||
.contentTransition(.symbolEffect(.replace, options: .speed(2)))
|
|
||||||
}
|
|
||||||
.buttonStyle(TVTransportButtonStyle())
|
|
||||||
.focused($focusedControl, equals: .playPause)
|
|
||||||
} else {
|
|
||||||
// Invisible spacer maintains layout stability
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 72, height: 72)
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip forward
|
|
||||||
Button {
|
|
||||||
seekForwardTrigger += 1
|
|
||||||
playerService?.seekForward(by: 10)
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "10.arrow.trianglehead.clockwise")
|
|
||||||
.font(.system(size: 52, weight: .medium))
|
|
||||||
.symbolEffect(.rotate.byLayer, options: .speed(2).nonRepeating, value: seekForwardTrigger)
|
|
||||||
}
|
|
||||||
.buttonStyle(TVTransportButtonStyle())
|
|
||||||
.focused($focusedControl, equals: .skipForward)
|
|
||||||
.disabled(isTransportDisabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether transport controls should be disabled (during loading/buffering or buffer not ready)
|
|
||||||
private var isTransportDisabled: Bool {
|
|
||||||
playerState?.playbackState == .loading ||
|
|
||||||
playerState?.playbackState == .buffering ||
|
|
||||||
!(playerState?.isFirstFrameReady ?? false) ||
|
|
||||||
!(playerState?.isBufferReady ?? false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var playPauseIcon: String {
|
|
||||||
switch playerState?.playbackState {
|
|
||||||
case .playing:
|
|
||||||
return "pause.fill"
|
|
||||||
default:
|
|
||||||
return "play.fill"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Action Buttons
|
// MARK: - Action Buttons
|
||||||
|
|
||||||
private var actionButtons: some View {
|
private var actionButtons: some View {
|
||||||
@@ -323,21 +246,6 @@ struct TVPlayerControlsView: View {
|
|||||||
|
|
||||||
// MARK: - Button Styles
|
// MARK: - Button Styles
|
||||||
|
|
||||||
/// Button style for transport controls (play/pause, skip).
|
|
||||||
struct TVTransportButtonStyle: ButtonStyle {
|
|
||||||
@Environment(\.isFocused) private var isFocused
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
configuration.label
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
.opacity(configuration.isPressed ? 0.6 : 1.0)
|
|
||||||
.scaleEffect(configuration.isPressed ? 0.9 : (isFocused ? 1.15 : 1.0))
|
|
||||||
.shadow(color: isFocused ? .white.opacity(0.5) : .clear, radius: 20)
|
|
||||||
.animation(.easeInOut(duration: 0.15), value: isFocused)
|
|
||||||
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Button style for action buttons (quality, captions, info).
|
/// Button style for action buttons (quality, captions, info).
|
||||||
struct TVActionButtonStyle: ButtonStyle {
|
struct TVActionButtonStyle: ButtonStyle {
|
||||||
@Environment(\.isFocused) private var isFocused
|
@Environment(\.isFocused) private var isFocused
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import SwiftUI
|
|||||||
/// Focus targets for tvOS player controls navigation.
|
/// Focus targets for tvOS player controls navigation.
|
||||||
enum TVPlayerFocusTarget: Hashable {
|
enum TVPlayerFocusTarget: Hashable {
|
||||||
case background // For capturing events when controls hidden
|
case background // For capturing events when controls hidden
|
||||||
case skipBackward
|
|
||||||
case playPause
|
|
||||||
case skipForward
|
|
||||||
case progressBar
|
case progressBar
|
||||||
case settingsButton
|
case settingsButton
|
||||||
case infoButton
|
case infoButton
|
||||||
@@ -225,7 +222,7 @@ struct TVPlayerView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
startControlsTimer()
|
startControlsTimer()
|
||||||
focusedControl = .playPause
|
focusedControl = .progressBar
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
stopControlsTimer()
|
stopControlsTimer()
|
||||||
@@ -354,7 +351,7 @@ struct TVPlayerView: View {
|
|||||||
controlsVisible = true
|
controlsVisible = true
|
||||||
}
|
}
|
||||||
if focusedControl == .background || focusedControl == nil {
|
if focusedControl == .background || focusedControl == nil {
|
||||||
focusedControl = .playPause
|
focusedControl = .progressBar
|
||||||
}
|
}
|
||||||
startControlsTimer()
|
startControlsTimer()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user