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