diff --git a/Yattee/Services/Navigation/NavigationCoordinator.swift b/Yattee/Services/Navigation/NavigationCoordinator.swift index 71e69e19..cdd64f66 100644 --- a/Yattee/Services/Navigation/NavigationCoordinator.swift +++ b/Yattee/Services/Navigation/NavigationCoordinator.swift @@ -144,6 +144,12 @@ final class NavigationCoordinator { /// otherwise be cancelled and dismiss the player instead). var panelResizeGrabberFrame: CGRect = .zero + /// Volume/brightness side slider frames in screen coordinates, keyed by + /// edge (leading = true). Touches inside must not start the sheet dismiss + /// pan — the window pan recognizes before SwiftUI's slider DragGesture + /// sets `isAdjustingPlayerSliders`, so the flag alone loses the race. + var playerSideSliderFrames: [Bool: CGRect] = [:] + /// Corner layout: width of the trailing screen-edge strip reserved for /// the side column reveal swipe while the column is collapsed (0 when /// not collapsed) — the seek pan must not begin from inside it. diff --git a/Yattee/Views/Player/ExpandedPlayerWindow.swift b/Yattee/Views/Player/ExpandedPlayerWindow.swift index ba4d66f4..54ee9efc 100644 --- a/Yattee/Views/Player/ExpandedPlayerWindow.swift +++ b/Yattee/Views/Player/ExpandedPlayerWindow.swift @@ -175,6 +175,7 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe var getCornerBelowPanelFrame: (() -> CGRect)? /// Returns the panel resize grabber strip frame in screen coordinates (blocks dismiss pan) var getPanelResizeGrabberFrame: (() -> CGRect)? + var getPlayerSideSliderFrames: (() -> [CGRect])? /// Returns whether the portrait panel is currently visible (not hidden off-screen) var isPortraitPanelVisible: (() -> Bool)? /// Returns the progress bar frame in screen coordinates (for gesture conflict resolution) @@ -383,6 +384,13 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe return false } + // ...or when the touch starts on a slider: the flag above is set by + // SwiftUI's DragGesture, which fires after this pan recognizes + let sliderTouch = panGesture.location(in: nil) + if (getPlayerSideSliderFrames?() ?? []).contains(where: { $0.contains(sliderTouch) }) { + return false + } + // Don't begin dismiss gesture when dragging portrait panel if isPanelDragging?() == true { return false @@ -871,6 +879,9 @@ final class ExpandedPlayerWindowManager { handler.getPanelResizeGrabberFrame = { [weak self] in self?.appEnvironment?.navigationCoordinator.panelResizeGrabberFrame ?? .zero } + handler.getPlayerSideSliderFrames = { [weak self] in + self?.appEnvironment?.navigationCoordinator.playerSideSliderFrames.values.filter { !$0.isEmpty } ?? [] + } handler.isPortraitPanelVisible = { [weak self] in self?.appEnvironment?.navigationCoordinator.isPortraitPanelVisible ?? true } diff --git a/Yattee/Views/Player/PlayerControlsView.swift b/Yattee/Views/Player/PlayerControlsView.swift index 637175eb..a5fbb898 100644 --- a/Yattee/Views/Player/PlayerControlsView.swift +++ b/Yattee/Views/Player/PlayerControlsView.swift @@ -1443,6 +1443,21 @@ struct PlayerControlsView: View { .modifier(SideSliderBackgroundModifier(buttonBackground: buttonBackground, theme: theme)) .opacity(playerState.isControlsLocked ? 0.5 : 1.0) .allowsHitTesting(!playerState.isControlsLocked) + // Publish the slider frame (with slop) so the window-level dismiss + // pan never begins from a slider drag + .background { + GeometryReader { proxy in + let isActive = shouldShowControls && !playerState.isControlsLocked + let frame = isActive ? proxy.frame(in: .global).insetBy(dx: -12, dy: -12) : .zero + Color.clear + .onChange(of: frame, initial: true) { _, frame in + appEnvironment?.navigationCoordinator.playerSideSliderFrames[isLeading] = frame + } + .onDisappear { + appEnvironment?.navigationCoordinator.playerSideSliderFrames[isLeading] = nil + } + } + } } }