Fix player dismiss gesture stuck after panel dismiss with comments expanded

Reset isCommentsExpanded and commentsFrame on the NavigationCoordinator
directly when the portrait panel is dismissed, since PortraitDetailsPanel
owns its own @State that doesn't sync back through .onChange during dismiss.
Also track comments overlay frame via GeometryReader so the dismiss gesture
can allow swipes outside the comments area instead of blanket-blocking.
This commit is contained in:
Arkadiusz Fal
2026-02-12 04:42:32 +01:00
parent b6b6d280e1
commit 6c30e745d9
7 changed files with 76 additions and 2 deletions

View File

@@ -120,6 +120,9 @@ final class NavigationCoordinator {
/// Progress bar frame in screen coordinates (for gesture conflict resolution). /// Progress bar frame in screen coordinates (for gesture conflict resolution).
var progressBarFrame: CGRect = .zero var progressBarFrame: CGRect = .zero
/// Comments overlay frame in screen coordinates (for gesture conflict resolution).
var commentsFrame: CGRect = .zero
/// Current panscan value from UIKit pinch gesture (0.0 = fit, 1.0 = fill). /// Current panscan value from UIKit pinch gesture (0.0 = fit, 1.0 = fill).
/// Updated by ExpandedPlayerWindow's pinch gesture handler. /// Updated by ExpandedPlayerWindow's pinch gesture handler.
var pinchPanscan: Double = 0.0 var pinchPanscan: Double = 0.0

View File

@@ -481,6 +481,9 @@ extension ExpandedPlayerSheet {
withTransaction(transaction) { withTransaction(transaction) {
isPortraitPanelVisible = false isPortraitPanelVisible = false
navigationCoordinator?.isPortraitPanelVisible = false navigationCoordinator?.isPortraitPanelVisible = false
isCommentsExpanded = false
navigationCoordinator?.isCommentsExpanded = false
navigationCoordinator?.commentsFrame = .zero
panelDragOffset = 0 panelDragOffset = 0
videoYOffset = 0 // Reset offset (centerY is now the base) videoYOffset = 0 // Reset offset (centerY is now the base)
} }

View File

@@ -31,6 +31,7 @@ extension ExpandedPlayerSheet {
/// Collapses the comments overlay. /// Collapses the comments overlay.
func collapseComments() { func collapseComments() {
navigationCoordinator?.commentsFrame = .zero
// Use same animation as player sheet dismiss (0.3s, no bounce) // Use same animation as player sheet dismiss (0.3s, no bounce)
withAnimation(.smooth(duration: 0.3)) { withAnimation(.smooth(duration: 0.3)) {
isCommentsExpanded = false isCommentsExpanded = false

View File

@@ -269,6 +269,23 @@ struct ExpandedPlayerSheet: View {
} }
.opacity(isVisible ? 1 : 0) .opacity(isVisible ? 1 : 0)
.allowsHitTesting(isVisible) .allowsHitTesting(isVisible)
.background(
GeometryReader { commentsGeometry in
Color.clear
.onChange(of: commentsGeometry.frame(in: .global)) { _, newFrame in
if isCommentsExpanded {
navigationCoordinator?.commentsFrame = newFrame
}
}
.onChange(of: isCommentsExpanded) { _, expanded in
if expanded {
navigationCoordinator?.commentsFrame = commentsGeometry.frame(in: .global)
} else {
navigationCoordinator?.commentsFrame = .zero
}
}
}
)
} }
.ignoresSafeArea(edges: .bottom) .ignoresSafeArea(edges: .bottom)
} }
@@ -770,6 +787,7 @@ private struct PlayerEventHandlersModifier: ViewModifier {
playerState?.commentsState = .idle playerState?.commentsState = .idle
playerState?.commentsContinuation = nil playerState?.commentsContinuation = nil
isCommentsExpanded = false isCommentsExpanded = false
navigationCoordinator?.commentsFrame = .zero
isPanelExpanded = false isPanelExpanded = false
panelExpandOffset = 0 panelExpandOffset = 0
} }

View File

@@ -150,6 +150,8 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe
var getProgressBarFrame: (() -> CGRect)? var getProgressBarFrame: (() -> CGRect)?
/// Returns true if a seek gesture is currently active (blocks pinch gesture) /// Returns true if a seek gesture is currently active (blocks pinch gesture)
var isSeekGestureActive: (() -> Bool)? var isSeekGestureActive: (() -> Bool)?
/// Returns the comments overlay frame in screen coordinates (for gesture conflict resolution)
var getCommentsFrame: (() -> CGRect)?
// Main window scaling callbacks (Apple Music-style effect) // Main window scaling callbacks (Apple Music-style effect)
var onMainWindowScaleChanged: ((CGFloat) -> Void)? var onMainWindowScaleChanged: ((CGFloat) -> Void)?
@@ -341,10 +343,18 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe
return false return false
} }
// Don't begin dismiss gesture when comments are expanded (they handle their own dismiss) // When comments are expanded, only block dismiss if touch is within comments frame
if isCommentsExpanded?() == true { if isCommentsExpanded?() == true {
let commentsFrame = getCommentsFrame?() ?? .zero
if !commentsFrame.isEmpty {
let touchLocation = panGesture.location(in: nil)
if commentsFrame.contains(touchLocation) {
return false return false
} }
} else {
return false // No frame info fall back to blanket blocking
}
}
// Don't begin dismiss gesture when adjusting volume/brightness sliders // Don't begin dismiss gesture when adjusting volume/brightness sliders
if isAdjustingPlayerSliders?() == true { if isAdjustingPlayerSliders?() == true {
@@ -840,6 +850,9 @@ final class ExpandedPlayerWindowManager {
handler.isSeekGestureActive = { [weak self] in handler.isSeekGestureActive = { [weak self] in
self?.appEnvironment?.navigationCoordinator.isSeekGestureActive ?? false self?.appEnvironment?.navigationCoordinator.isSeekGestureActive ?? false
} }
handler.getCommentsFrame = { [weak self] in
self?.appEnvironment?.navigationCoordinator.commentsFrame ?? .zero
}
// Main window scaling callbacks for interactive drag // Main window scaling callbacks for interactive drag
handler.onMainWindowScaleChanged = { [weak self] progress in handler.onMainWindowScaleChanged = { [weak self] progress in
guard let self else { return } guard let self else { return }

View File

@@ -495,6 +495,23 @@ struct FloatingDetailsPanel: View {
} }
.opacity(expanded ? 1 : 0) .opacity(expanded ? 1 : 0)
.allowsHitTesting(expanded) .allowsHitTesting(expanded)
.background(
GeometryReader { commentsGeometry in
Color.clear
.onChange(of: commentsGeometry.frame(in: .global)) { _, newFrame in
if isCommentsExpanded {
appEnvironment?.navigationCoordinator.commentsFrame = newFrame
}
}
.onChange(of: isCommentsExpanded) { _, expanded in
if expanded {
appEnvironment?.navigationCoordinator.commentsFrame = commentsGeometry.frame(in: .global)
} else {
appEnvironment?.navigationCoordinator.commentsFrame = .zero
}
}
}
)
} }
// MARK: - Video Info // MARK: - Video Info
@@ -558,6 +575,7 @@ struct FloatingDetailsPanel: View {
} }
private func collapseComments() { private func collapseComments() {
appEnvironment?.navigationCoordinator.commentsFrame = .zero
// Use same animation as player sheet dismiss (0.3s, no bounce) // Use same animation as player sheet dismiss (0.3s, no bounce)
withAnimation(.smooth(duration: 0.3)) { withAnimation(.smooth(duration: 0.3)) {
isCommentsExpanded = false isCommentsExpanded = false

View File

@@ -256,6 +256,23 @@ struct PortraitDetailsPanel: View {
} }
.opacity(expanded ? 1 : 0) .opacity(expanded ? 1 : 0)
.allowsHitTesting(expanded) .allowsHitTesting(expanded)
.background(
GeometryReader { commentsGeometry in
Color.clear
.onChange(of: commentsGeometry.frame(in: .global)) { _, newFrame in
if isCommentsExpanded {
appEnvironment?.navigationCoordinator.commentsFrame = newFrame
}
}
.onChange(of: isCommentsExpanded) { _, expanded in
if expanded {
appEnvironment?.navigationCoordinator.commentsFrame = commentsGeometry.frame(in: .global)
} else {
appEnvironment?.navigationCoordinator.commentsFrame = .zero
}
}
}
)
} }
.animation(.smooth(duration: 0.3), value: isCommentsExpanded) .animation(.smooth(duration: 0.3), value: isCommentsExpanded)
.onChange(of: video.id) { _, _ in .onChange(of: video.id) { _, _ in
@@ -521,6 +538,7 @@ struct PortraitDetailsPanel: View {
} }
private func collapseComments() { private func collapseComments() {
appEnvironment?.navigationCoordinator.commentsFrame = .zero
withAnimation(.smooth(duration: 0.3)) { withAnimation(.smooth(duration: 0.3)) {
isCommentsExpanded = false isCommentsExpanded = false
} }