Fix clipped top controls and stale top padding in corner fullscreen

The fullscreen video layer extends up into the status bar strip and is
shifted with an offset; `.clipped()` came after the offset, so it clipped
to the unshifted frame and cut the top strip (title row, avatar) off. Clip
before the offset instead.

The player controls read the main window's safe area only on geometry
changes. With the corner layout keeping its geometry fixed across the
status bar hide, the value went stale and the controls kept the status bar
height as top padding in fullscreen. Refresh it when the status bar
visibility changes, with a few delayed re-reads to cover the fade.
This commit is contained in:
Arkadiusz Fal
2026-09-25 09:05:12 +02:00
parent 95124d87bb
commit f4204105eb
2 changed files with 18 additions and 1 deletions

View File

@@ -444,6 +444,11 @@ struct CornerPlayerLayout<PlayerContent: View>: View {
)
}
.frame(width: videoWidth, height: videoHeight + videoTopExtension)
// Clip in the frame's own coordinates, BEFORE the offset —
// clipping after it uses the unshifted layout frame and cuts
// the top strip (status bar height) off the fullscreen video
// and its top controls.
.clipped()
.offset(y: -videoTopExtension)
// Keyed to gesture/visibility state, NOT to derived geometry —
// appear settling must not animate. The drag-offset springs
@@ -456,7 +461,6 @@ struct CornerPlayerLayout<PlayerContent: View>: View {
// Aspect ratio changes (new video, stream switch) resize
// the video/panel split — animate them
.animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: playerState?.displayAspectRatio)
.clipped()
// Swipe up on the video to bring the panel back — the
// video's bottom edge follows the finger (like the portrait
// reveal) via belowPanelDragOffset counting down from full

View File

@@ -141,6 +141,7 @@ struct PlayerControlsView: View {
@State private var playNextTapCount = 0
/// Track safe area to force re-render when it changes
@State private var currentSafeArea: UIEdgeInsets = .zero
@Environment(\.statusBarVisibilityController) private var statusBarController
/// Active player controls layout from preset (passed from parent to avoid flashing during view recreation)
var activeLayout: PlayerControlsLayout = .default
/// Animation trigger for seek backward button
@@ -268,6 +269,18 @@ struct PlayerControlsView: View {
// Update safe area when geometry changes (rotation)
currentSafeArea = windowSafeArea
}
.onChange(of: statusBarController?.isHidden) { _, _ in
// The window safe area follows the status bar fade with a delay.
// Corner layout keeps its geometry fixed across the toggle, so
// no geometry change refreshes the value — poll a few times
// instead of keeping a stale status bar height as top padding.
currentSafeArea = windowSafeArea
for delay in [0.15, 0.4, 0.8] {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
currentSafeArea = windowSafeArea
}
}
}
.onChange(of: activeLayout.effectiveGesturesSettings.panscanGesture.snapToEnds, initial: true) { _, newValue in
// Sync panscan snap setting to navigation coordinator (initial: true ensures sync on first render)
appEnvironment?.navigationCoordinator.shouldSnapPanscan = newValue