mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 10:55:03 +00:00
Keep tvOS seek bar stationary and float preview above
This commit is contained in:
@@ -60,21 +60,6 @@ struct TVPlayerProgressBar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
|
||||||
// Gesture capture layer (only when scrubbing)
|
|
||||||
if isScrubbing {
|
|
||||||
TVPanGestureView(
|
|
||||||
onPanChanged: { translation, velocity in
|
|
||||||
handlePan(translation: translation, velocity: velocity)
|
|
||||||
},
|
|
||||||
onPanEnded: {
|
|
||||||
handlePanEnded()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visual content with button for click-to-scrub (disabled for live)
|
|
||||||
Button {
|
Button {
|
||||||
if isScrubbing {
|
if isScrubbing {
|
||||||
commitScrub()
|
commitScrub()
|
||||||
@@ -86,6 +71,20 @@ struct TVPlayerProgressBar: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(TVProgressBarButtonStyle(isFocused: isFocused))
|
.buttonStyle(TVProgressBarButtonStyle(isFocused: isFocused))
|
||||||
.disabled(isLive)
|
.disabled(isLive)
|
||||||
|
.overlay {
|
||||||
|
// Gesture capture layer (only when scrubbing). Siri Remote pan
|
||||||
|
// gestures are indirect touches, so matching the button's size
|
||||||
|
// is sufficient — no need to expand and disturb parent layout.
|
||||||
|
if isScrubbing {
|
||||||
|
TVPanGestureView(
|
||||||
|
onPanChanged: { translation, velocity in
|
||||||
|
handlePan(translation: translation, velocity: velocity)
|
||||||
|
},
|
||||||
|
onPanEnded: {
|
||||||
|
handlePanEnded()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.focused($isFocused)
|
.focused($isFocused)
|
||||||
.onMoveCommand { direction in
|
.onMoveCommand { direction in
|
||||||
@@ -143,8 +142,12 @@ struct TVPlayerProgressBar: View {
|
|||||||
.animation(.easeOut(duration: 0.1), value: progress)
|
.animation(.easeOut(duration: 0.1), value: progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxHeight: .infinity, alignment: .center)
|
||||||
|
.overlay(alignment: .top) {
|
||||||
|
scrubPreviewOverlay(geometry: geometry)
|
||||||
}
|
}
|
||||||
.frame(height: isFocused ? (isScrubbing ? 16 : 12) : 6)
|
}
|
||||||
|
.frame(height: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time labels
|
// Time labels
|
||||||
@@ -191,17 +194,26 @@ struct TVPlayerProgressBar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrub preview (storyboard thumbnail or large time display)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func scrubPreviewOverlay(geometry: GeometryProxy) -> some View {
|
||||||
if isScrubbing {
|
if isScrubbing {
|
||||||
|
let previewWidth: CGFloat = 352
|
||||||
|
let previewHeight: CGFloat = 260
|
||||||
|
let halfWidth = previewWidth / 2
|
||||||
|
let clampedX = max(halfWidth, min(geometry.size.width - halfWidth, geometry.size.width * progress))
|
||||||
|
let yPosition = -previewHeight / 2 - 16
|
||||||
|
|
||||||
|
Group {
|
||||||
if let storyboard {
|
if let storyboard {
|
||||||
TVSeekPreviewView(
|
TVSeekPreviewView(
|
||||||
storyboard: storyboard,
|
storyboard: storyboard,
|
||||||
seekTime: scrubTime ?? currentTime,
|
seekTime: scrubTime ?? currentTime,
|
||||||
chapters: showChapters ? chapters : []
|
chapters: showChapters ? chapters : []
|
||||||
)
|
)
|
||||||
.transition(.scale.combined(with: .opacity))
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback when no storyboard available
|
|
||||||
Text((scrubTime ?? currentTime).formattedAsTimestamp)
|
Text((scrubTime ?? currentTime).formattedAsTimestamp)
|
||||||
.font(.system(size: 48, weight: .medium))
|
.font(.system(size: 48, weight: .medium))
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
@@ -212,9 +224,11 @@ struct TVPlayerProgressBar: View {
|
|||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 16)
|
||||||
.fill(.ultraThinMaterial)
|
.fill(.ultraThinMaterial)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.position(x: clampedX, y: yPosition)
|
||||||
.transition(.scale.combined(with: .opacity))
|
.transition(.scale.combined(with: .opacity))
|
||||||
}
|
.allowsHitTesting(false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user