From f4204105eb21e7d44f104ed25aa79cb29c6f3e40 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 25 Sep 2026 09:05:12 +0200 Subject: [PATCH] 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. --- Yattee/Views/Player/CornerPlayerLayout.swift | 6 +++++- Yattee/Views/Player/PlayerControlsView.swift | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Yattee/Views/Player/CornerPlayerLayout.swift b/Yattee/Views/Player/CornerPlayerLayout.swift index bfa2a088..6bc44998 100644 --- a/Yattee/Views/Player/CornerPlayerLayout.swift +++ b/Yattee/Views/Player/CornerPlayerLayout.swift @@ -444,6 +444,11 @@ struct CornerPlayerLayout: 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: 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 diff --git a/Yattee/Views/Player/PlayerControlsView.swift b/Yattee/Views/Player/PlayerControlsView.swift index fdcbfeb5..7ce4c61c 100644 --- a/Yattee/Views/Player/PlayerControlsView.swift +++ b/Yattee/Views/Player/PlayerControlsView.swift @@ -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