mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 18:35:05 +00:00
Fade out tvOS player controls on auto-hide
This commit is contained in:
@@ -201,34 +201,39 @@ struct TVPlayerView: View {
|
||||
// Video layer
|
||||
videoLayer
|
||||
|
||||
// Controls overlay
|
||||
if controlsVisible && !isDetailsPanelVisible && !isDebugOverlayVisible {
|
||||
TVPlayerControlsView(
|
||||
playerState: playerState,
|
||||
playerService: playerService,
|
||||
focusedControl: $focusedControl,
|
||||
onShowSettings: { showQualitySheet() },
|
||||
onShowQueue: { showQueueSheet() },
|
||||
onShowDetails: { showDetailsPanel(tab: .info) },
|
||||
onShowComments: { showDetailsPanel(tab: .comments) },
|
||||
onShowDebug: { showDebugOverlay() },
|
||||
onClose: { closeVideo() },
|
||||
onScrubbingChanged: { scrubbing in
|
||||
isScrubbing = scrubbing
|
||||
if scrubbing {
|
||||
stopControlsTimer()
|
||||
} else {
|
||||
startControlsTimer()
|
||||
}
|
||||
},
|
||||
remoteSeekTime: scrubberRemoteSeekTime,
|
||||
onRemoteSeek: { forward in
|
||||
triggerScrubberRemoteSeek(forward: forward)
|
||||
},
|
||||
cancelScrubTrigger: cancelScrubTrigger
|
||||
)
|
||||
.transition(.opacity.animation(.easeInOut(duration: 0.25)))
|
||||
}
|
||||
// Controls overlay — always in tree, toggled via opacity so the fade-out
|
||||
// isn't skipped by tvOS's focus engine forcibly tearing down a focused
|
||||
// subview when the conditional flips to false. Focus is redirected to
|
||||
// the background Button after each hide so the remote touch area still
|
||||
// triggers showControls() instead of hitting a disabled hidden control.
|
||||
TVPlayerControlsView(
|
||||
playerState: playerState,
|
||||
playerService: playerService,
|
||||
focusedControl: $focusedControl,
|
||||
onShowSettings: { showQualitySheet() },
|
||||
onShowQueue: { showQueueSheet() },
|
||||
onShowDetails: { showDetailsPanel(tab: .info) },
|
||||
onShowComments: { showDetailsPanel(tab: .comments) },
|
||||
onShowDebug: { showDebugOverlay() },
|
||||
onClose: { closeVideo() },
|
||||
onScrubbingChanged: { scrubbing in
|
||||
isScrubbing = scrubbing
|
||||
if scrubbing {
|
||||
stopControlsTimer()
|
||||
} else {
|
||||
startControlsTimer()
|
||||
}
|
||||
},
|
||||
remoteSeekTime: scrubberRemoteSeekTime,
|
||||
onRemoteSeek: { forward in
|
||||
triggerScrubberRemoteSeek(forward: forward)
|
||||
},
|
||||
cancelScrubTrigger: cancelScrubTrigger
|
||||
)
|
||||
.opacity(shouldShowControls ? 1 : 0)
|
||||
.allowsHitTesting(shouldShowControls)
|
||||
.disabled(!shouldShowControls)
|
||||
.animation(.easeInOut(duration: 0.25), value: shouldShowControls)
|
||||
|
||||
// Swipe-up details panel
|
||||
if isDetailsPanelVisible {
|
||||
@@ -382,6 +387,13 @@ struct TVPlayerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Derived State
|
||||
|
||||
/// Whether the primary controls overlay should be visible right now.
|
||||
private var shouldShowControls: Bool {
|
||||
controlsVisible && !isDetailsPanelVisible && !isDebugOverlayVisible
|
||||
}
|
||||
|
||||
// MARK: - Controls Timer
|
||||
|
||||
private func startControlsTimer() {
|
||||
@@ -392,10 +404,13 @@ struct TVPlayerView: View {
|
||||
|
||||
controlsHideTimer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: false) { _ in
|
||||
Task { @MainActor in
|
||||
withAnimation(.easeOut(duration: 0.3)) {
|
||||
withAnimation(.easeOut(duration: 0.25)) {
|
||||
controlsVisible = false
|
||||
focusedControl = .background
|
||||
}
|
||||
// After controlsVisible flips, the backgroundLayer Button is in the
|
||||
// tree and focusable — move focus to it so the remote touch area
|
||||
// calls showControls() instead of a hidden/disabled control.
|
||||
focusedControl = .background
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,8 +679,11 @@ struct TVPlayerView: View {
|
||||
stopControlsTimer()
|
||||
withAnimation(.easeOut(duration: 0.25)) {
|
||||
controlsVisible = false
|
||||
focusedControl = .background
|
||||
}
|
||||
// Focus is redirected outside withAnimation to keep the fade-out animation
|
||||
// from being skipped, and to keep the remote touch area pointing at the
|
||||
// background Button's showControls() action rather than a hidden control.
|
||||
focusedControl = .background
|
||||
}
|
||||
|
||||
private func closeVideo() {
|
||||
|
||||
Reference in New Issue
Block a user