diff --git a/Yattee/Services/Navigation/NavigationCoordinator.swift b/Yattee/Services/Navigation/NavigationCoordinator.swift index cc0e9ba1..21c68659 100644 --- a/Yattee/Services/Navigation/NavigationCoordinator.swift +++ b/Yattee/Services/Navigation/NavigationCoordinator.swift @@ -167,6 +167,11 @@ final class NavigationCoordinator { /// otherwise be cancelled and dismiss the player instead). var panelResizeGrabberFrame: CGRect = .zero + /// 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. + var cornerSideColumnEdgeRevealInset: CGFloat = 0 + /// Progress bar frame in screen coordinates (for gesture conflict resolution). var progressBarFrame: CGRect = .zero diff --git a/Yattee/Views/Player/CornerPlayerLayout.swift b/Yattee/Views/Player/CornerPlayerLayout.swift index 76668fe0..9cadfa24 100644 --- a/Yattee/Views/Player/CornerPlayerLayout.swift +++ b/Yattee/Views/Player/CornerPlayerLayout.swift @@ -85,6 +85,27 @@ struct CornerPlayerLayout: View { /// layout, positioned by `belowPanelDragOffset` counting down from full /// hide travel so it follows the finger. @State private var isRevealDragActive = false + /// Side column collapsed by overdragging its resize grabber past the + /// minimum width — independent of `isPanelVisible`, like the below panel. + @State private var isSideColumnVisible = true + /// Overdrag travel of the side column toward the trailing edge (0 = home). + @State private var sideColumnDragOffset: CGFloat = 0 + @State private var isSideColumnOverdragActive = false + /// Edge-swipe reveal in progress: the column renders in the shown layout, + /// positioned by `sideColumnDragOffset` counting down from full travel. + @State private var isSideColumnRevealDragActive = false + /// Bumped by the animated show/hide paths (toggle button, settings sync). + /// The visibility-driven `.animation` modifiers key on this instead of the + /// visibility flags themselves: drag hide/reveal flips those flags in a + /// disabled transaction, but `.animation(_:value:)` ignores that and would + /// still animate the frame height — an animation a quick follow-up swipe + /// then interrupts, leaving the video clipped to a shrunken box that + /// springs back (the "fast swipe" reveal glitch). + @State private var panelToggleGeneration = 0 + + /// Trailing screen-edge strip that starts the reveal swipe while the + /// column is collapsed; drags from anywhere else keep seeking. + static var sideColumnEdgeRevealInset: CGFloat { 20 } private var settingsManager: SettingsManager? { appEnvironment?.settingsManager } /// Suppresses the leaf-level .animation modifiers while a panel drag @@ -165,8 +186,15 @@ struct CornerPlayerLayout: View { let width = max(1, geometry.size.width) let height = max(1, geometry.size.height) - let showsSideColumn = isPanelVisible && hasSideColumn + let showsSideColumn = isPanelVisible && isSideColumnVisible && hasSideColumn + let isSideColumnCollapsed = isPanelVisible && !isSideColumnVisible && hasSideColumn + // Reveal drag renders the shown layout so the column can follow + // the finger while `isSideColumnVisible` is still false. + let showsSideColumnLayout = showsSideColumn || (isSideColumnCollapsed && isSideColumnRevealDragActive) let showsBelowVideo = isPanelVisible && isBelowPanelVisible && hasBelowVideoInfo + // What the player actually sees: every region can be dragged + // away on its own while the panel stays nominally visible. + let showsAnyRegion = showsSideColumn || showsBelowVideo let effectiveColumnWidth = clampColumnWidth(columnWidth, availableWidth: width) // Grabber sits on the column's leading edge, inside the column @@ -174,7 +202,15 @@ struct CornerPlayerLayout: View { let grabberWidth = Self.grabberWidth let totalColumnWidth = effectiveColumnWidth + grabberWidth - let videoWidth = showsSideColumn ? max(1, width - totalColumnWidth) : width + // Grabber overdrag past the minimum width slides the column off + // the trailing edge; the video grows with the slide so the offset + // layout at full travel matches the collapsed layout exactly (the + // state flips after the animation without a visible jump). + let sideColumnHideTravel = totalColumnWidth + geometry.safeAreaInsets.trailing + let sideColumnSlide = showsSideColumnLayout ? min(sideColumnHideTravel, max(0, sideColumnDragOffset)) : 0 + let videoWidth = showsSideColumnLayout + ? max(1, width - totalColumnWidth + min(totalColumnWidth, sideColumnSlide)) + : width let shownVideoHeight: CGFloat = { let rawAspectRatio = playerState?.displayAspectRatio ?? (16.0 / 9.0) let aspectRatio = rawAspectRatio > 0 ? rawAspectRatio : (16.0 / 9.0) @@ -237,7 +273,7 @@ struct CornerPlayerLayout: View { GeometryReader { videoProxy in playerContent( togglePanel, - isPanelVisible, + showsAnyRegion, max(1, videoProxy.size.width), max(1, videoProxy.size.height) ) @@ -248,10 +284,10 @@ struct CornerPlayerLayout: View { // visibility state, NOT to derived geometry — appear // settling must not animate. .frame(height: videoHeight) - .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: isPanelVisible) - .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: isBelowPanelVisible) + .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: panelToggleGeneration) .frame(width: videoWidth) - .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: isPanelVisible) + .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: panelToggleGeneration) + .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: sideColumnDragOffset) .clipped() .offset(y: videoDragYOffset) .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: videoDragYOffset) @@ -296,9 +332,12 @@ struct CornerPlayerLayout: View { } else { // Snap back off-screen, then drop the // reveal layout without animation - withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .logicallyComplete) { + withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .removed) { belowPanelDragOffset = panelHideTravel } completion: { + // A new reveal drag may have taken over + guard isRevealDragActive, belowPanelDragOffset == panelHideTravel, + appEnvironment?.navigationCoordinator.isPanelDragging != true else { return } var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { @@ -310,6 +349,64 @@ struct CornerPlayerLayout: View { } : nil ) + // Swipe in from the trailing screen edge to bring a + // collapsed side column back — the column follows + // the finger via sideColumnDragOffset counting down + // from full travel. Only the edge strip: the seek + // pan is told to skip it, everywhere else seeks. + .simultaneousGesture( + isSideColumnCollapsed + ? DragGesture(coordinateSpace: .local) + .onChanged { value in + guard appEnvironment?.navigationCoordinator.isAdjustingPlayerSliders != true else { return } + guard isSideColumnRevealDragActive + || (value.startLocation.x >= videoWidth - Self.sideColumnEdgeRevealInset + && value.translation.width < 0) else { return } + if appEnvironment?.navigationCoordinator.isPanelDragging != true { + appEnvironment?.navigationCoordinator.isPanelDragging = true + } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isSideColumnRevealDragActive = true + sideColumnDragOffset = min(sideColumnHideTravel, max(0, sideColumnHideTravel + value.translation.width)) + } + } + .onEnded { value in + guard isSideColumnRevealDragActive else { return } + appEnvironment?.navigationCoordinator.isPanelDragging = false + if value.translation.width < -80 || value.predictedEndTranslation.width < -160 { + // Revealing: flip to visible at the + // current position, then animate home + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isSideColumnVisible = true + isSideColumnRevealDragActive = false + } + withAnimation(.spring(response: 0.3, dampingFraction: 1.0)) { + sideColumnDragOffset = 0 + } + } else { + // Snap back off-screen, then drop the + // reveal layout without animation + withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .removed) { + sideColumnDragOffset = sideColumnHideTravel + } completion: { + // A new reveal drag may have taken over + guard isSideColumnRevealDragActive, sideColumnDragOffset == sideColumnHideTravel, + appEnvironment?.navigationCoordinator.isPanelDragging != true else { return } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isSideColumnRevealDragActive = false + sideColumnDragOffset = 0 + } + } + } + } + : nil + ) .frame(maxHeight: .infinity, alignment: .top) if showsPanelLayout, let video { @@ -327,6 +424,8 @@ struct CornerPlayerLayout: View { .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 0.8), value: isBelowPanelExpanded) .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 0.8), value: belowPanelExpandOffset) .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: playerState?.displayAspectRatio) + .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: panelToggleGeneration) + .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: sideColumnDragOffset) // Pure slide, no fade — with a fade the panel // seems to vanish before the side column and the // video resize, breaking the one-transition feel @@ -340,7 +439,7 @@ struct CornerPlayerLayout: View { // reach the true bottom edge .ignoresSafeArea(edges: .bottom) - if showsSideColumn, let video { + if showsSideColumnLayout, let video { HStack(spacing: 0) { PanelResizeGrabber( panelWidth: $columnWidth, @@ -349,7 +448,54 @@ struct CornerPlayerLayout: View { panelSide: .right, isPinned: true, isDragActive: $isGrabberActive, - hitWidth: grabberWidth + hitWidth: grabberWidth, + onOverdragChanged: { overshoot in + guard overshoot > 0 || isSideColumnOverdragActive else { return } + if appEnvironment?.navigationCoordinator.isPanelDragging != true { + appEnvironment?.navigationCoordinator.isPanelDragging = true + } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isSideColumnOverdragActive = true + sideColumnDragOffset = min(sideColumnHideTravel, overshoot) + } + }, + onOverdragEnded: { overshoot, predicted in + guard isSideColumnOverdragActive else { return } + isSideColumnOverdragActive = false + appEnvironment?.navigationCoordinator.isPanelDragging = false + if overshoot > 80 || predicted > 160 { + // Slide fully off (video grows with + // it), then flip the collapsed state + // without animation — like the + // below panel's hide. `.removed`, not + // `.logicallyComplete`: flipping while + // the spring is still registered on the + // video frame leaves a stale animation + // that a quick follow-up reveal swipe + // interrupts, and the release spring + // then starts from a bogus shrunken + // rect (video clipped, growing back). + withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .removed) { + sideColumnDragOffset = sideColumnHideTravel + } completion: { + // The grabber may have been picked up again + guard isSideColumnVisible, !isSideColumnOverdragActive, + sideColumnDragOffset == sideColumnHideTravel else { return } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isSideColumnVisible = false + sideColumnDragOffset = 0 + } + } + } else { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + sideColumnDragOffset = 0 + } + } + } ) sideColumnPanel(video: video) @@ -358,14 +504,24 @@ struct CornerPlayerLayout: View { // Bottom only — the background must not run under // the system status bar at the top .background(panelBackgroundColor.ignoresSafeArea(edges: .bottom)) + // Overdrag slide: the column keeps its size while its + // layout slot shrinks from the trailing side, so its + // leading edge tracks the growing video; travel into + // the trailing safe area continues as a plain offset. + .offset(x: max(0, sideColumnSlide - totalColumnWidth)) + .frame(width: max(0, totalColumnWidth - sideColumnSlide), alignment: .leading) + .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: sideColumnDragOffset) .transition(.move(edge: .trailing).animation(.easeInOut(duration: panelToggleDuration))) } } // Container-level animation so panel insert/remove and the // video-area resize play as one transition (the host sheet // strips withAnimation transactions, so key it here) - .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: isPanelVisible) - .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: isBelowPanelVisible) + .animation(isPanelDragActive ? nil : .easeInOut(duration: panelToggleDuration), value: panelToggleGeneration) + .animation(isPanelDragActive ? nil : .spring(response: 0.3, dampingFraction: 1.0), value: sideColumnDragOffset) + } + .onChange(of: isSideColumnCollapsed, initial: true) { _, collapsed in + appEnvironment?.navigationCoordinator.cornerSideColumnEdgeRevealInset = collapsed ? Self.sideColumnEdgeRevealInset : 0 } .onChange(of: width) { _, newWidth in // Re-clamp when the window shrinks (still above the corner @@ -373,6 +529,9 @@ struct CornerPlayerLayout: View { columnWidth = clampColumnWidth(columnWidth, availableWidth: newWidth) } } + .onDisappear { + appEnvironment?.navigationCoordinator.cornerSideColumnEdgeRevealInset = 0 + } .onAppear { // Only sync from the setting once the session's default // visibility was applied — before that the setting is stale @@ -395,7 +554,9 @@ struct CornerPlayerLayout: View { // must reach the local state too. guard let newValue, newValue != isPanelVisible else { return } withAnimation(.easeInOut(duration: 0.25)) { + if newValue { restoreDraggedAwayRegions() } isPanelVisible = newValue + panelToggleGeneration += 1 } } } @@ -463,10 +624,18 @@ struct CornerPlayerLayout: View { // Slide the panel fully off (video recenters via the // drag-progress offset), then flip the hidden state // without animation — the offset layout at full - // travel matches the hidden layout exactly. - withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .logicallyComplete) { + // travel matches the hidden layout exactly. Must wait + // for `.removed`: flipping at `.logicallyComplete` + // leaves the spring registered on the video frame, and + // a quick follow-up reveal swipe that interrupts it + // makes the release spring start from a bogus shrunken + // rect (video clipped to a short box, growing back). + withAnimation(.spring(response: 0.3, dampingFraction: 1.0), completionCriteria: .removed) { belowPanelDragOffset = hideTravel } completion: { + // The panel may have been grabbed again meanwhile + guard isBelowPanelVisible, belowPanelDragOffset == hideTravel, + appEnvironment?.navigationCoordinator.isPanelDragging != true else { return } var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { @@ -557,12 +726,30 @@ struct CornerPlayerLayout: View { // MARK: - Actions private func togglePanel() { + // Regions dragged away on their own leave the panel nominally + // visible — once nothing is left on screen the toggle shows + // everything again instead of "hiding" an empty panel. + let showsAnyRegion = isPanelVisible + && ((isSideColumnVisible && hasSideColumn) || (isBelowPanelVisible && hasBelowVideoInfo)) + let show = !showsAnyRegion withAnimation(.easeInOut(duration: 0.25)) { - isPanelVisible.toggle() - settingsManager?.landscapeDetailsPanelVisible = isPanelVisible - settingsManager?.detailsPanelLastVisible = isPanelVisible + if show { restoreDraggedAwayRegions() } + isPanelVisible = show + panelToggleGeneration += 1 + settingsManager?.landscapeDetailsPanelVisible = show + settingsManager?.detailsPanelLastVisible = show } } + + /// Showing the panel brings back every region hidden by its own drag + /// handle (the portrait layout shares one flag for both, so this keeps + /// the toggle's meaning the same across layouts). + private func restoreDraggedAwayRegions() { + isSideColumnVisible = true + sideColumnDragOffset = 0 + isBelowPanelVisible = true + belowPanelDragOffset = 0 + } } #endif diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift index 44f64d7d..de274800 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift @@ -1507,7 +1507,6 @@ extension ExpandedPlayerSheet { } } .frame(width: videoWidth, height: videoHeight) - .matchedGeometryEffect(id: "player", in: playerNamespace) .geometryGroup() .clipped() diff --git a/Yattee/Views/Player/Gestures/PlayerGestureCoordinator.swift b/Yattee/Views/Player/Gestures/PlayerGestureCoordinator.swift index 1b7857e8..614b60b7 100644 --- a/Yattee/Views/Player/Gestures/PlayerGestureCoordinator.swift +++ b/Yattee/Views/Player/Gestures/PlayerGestureCoordinator.swift @@ -29,6 +29,9 @@ final class PlayerGestureCoordinator: NSObject, UIGestureRecognizerDelegate { var isPinchGestureActive: (() -> Bool)? /// Returns true if panel drag is active (blocks seek gesture). var isPanelDragging: (() -> Bool)? + /// Width of the trailing-edge strip reserved for a panel reveal swipe + /// (0 = none) — pans starting inside it never become seek gestures. + var trailingEdgeRevealInset: (() -> CGFloat)? /// Called when seek gesture is recognized (after activation threshold). var onSeekGestureStarted: (() -> Void)? @@ -193,6 +196,11 @@ final class PlayerGestureCoordinator: NSObject, UIGestureRecognizerDelegate { if isPinchGestureActive?() == true { return false } // Block seek gesture if panel drag is active if isPanelDragging?() == true { return false } + // Leave the trailing screen edge to the panel reveal swipe + if let inset = trailingEdgeRevealInset?(), inset > 0 { + let location = gestureRecognizer.location(in: gestureRecognizer.view) + if location.x >= bounds.width - inset { return false } + } return isActive && seekSettings.isEnabled && isSeekable } diff --git a/Yattee/Views/Player/Gestures/PlayerGestureOverlay.swift b/Yattee/Views/Player/Gestures/PlayerGestureOverlay.swift index aad2e44a..0c1c6c11 100644 --- a/Yattee/Views/Player/Gestures/PlayerGestureOverlay.swift +++ b/Yattee/Views/Player/Gestures/PlayerGestureOverlay.swift @@ -23,6 +23,8 @@ struct PlayerGestureOverlay: View { var isPinchGestureActive: (() -> Bool)? = nil /// Returns true if panel drag is active (blocks seek gesture). var isPanelDragging: (() -> Bool)? = nil + /// Width of the trailing-edge strip reserved for a panel reveal swipe. + var trailingEdgeRevealInset: (() -> CGFloat)? = nil var body: some View { GeometryReader { geometry in @@ -41,7 +43,8 @@ struct PlayerGestureOverlay: View { onSeekGestureChanged: onSeekGestureChanged, onSeekGestureEnded: onSeekGestureEnded, isPinchGestureActive: isPinchGestureActive, - isPanelDragging: isPanelDragging + isPanelDragging: isPanelDragging, + trailingEdgeRevealInset: trailingEdgeRevealInset ) } // Always allow hit testing - single tap to toggle controls should work @@ -65,6 +68,7 @@ private struct GestureRecognizerView: UIViewRepresentable { let onSeekGestureEnded: (CGFloat) -> Void var isPinchGestureActive: (() -> Bool)? = nil var isPanelDragging: (() -> Bool)? = nil + var trailingEdgeRevealInset: (() -> CGFloat)? = nil func makeCoordinator() -> PlayerGestureCoordinator { PlayerGestureCoordinator( @@ -87,6 +91,7 @@ private struct GestureRecognizerView: UIViewRepresentable { coordinator.onSeekGestureEnded = onSeekGestureEnded coordinator.isPinchGestureActive = isPinchGestureActive coordinator.isPanelDragging = isPanelDragging + coordinator.trailingEdgeRevealInset = trailingEdgeRevealInset coordinator.attach(to: view) return view @@ -108,6 +113,7 @@ private struct GestureRecognizerView: UIViewRepresentable { coordinator.onSeekGestureEnded = onSeekGestureEnded coordinator.isPinchGestureActive = isPinchGestureActive coordinator.isPanelDragging = isPanelDragging + coordinator.trailingEdgeRevealInset = trailingEdgeRevealInset } static func dismantleUIView(_ uiView: UIView, coordinator: PlayerGestureCoordinator) { diff --git a/Yattee/Views/Player/Panels/InfoPanelPage.swift b/Yattee/Views/Player/Panels/InfoPanelPage.swift index 08c97a46..c2404922 100644 --- a/Yattee/Views/Player/Panels/InfoPanelPage.swift +++ b/Yattee/Views/Player/Panels/InfoPanelPage.swift @@ -310,6 +310,7 @@ struct InfoPanelPage: View { VideoRowView( video: relatedVideo, style: .regular, + disableInternalTapHandling: true, showThumbnail: true ) .tappableVideo( diff --git a/Yattee/Views/Player/Panels/PanelResizeGrabber.swift b/Yattee/Views/Player/Panels/PanelResizeGrabber.swift index 7e17b8ac..1049b22c 100644 --- a/Yattee/Views/Player/Panels/PanelResizeGrabber.swift +++ b/Yattee/Views/Player/Panels/PanelResizeGrabber.swift @@ -23,6 +23,15 @@ struct PanelResizeGrabber: View { /// Width of the grabber hit area (hosts can narrow it, e.g. corner layout) var hitWidth: CGFloat = PanelResizeGrabber.grabberHitWidth + /// Overdrag past `minWidth`: reports how far (in points, ≥ 0) the finger + /// went beyond the minimum while the width stays clamped — hosts can + /// slide the panel off-screen with it. Reported every drag update, so a + /// finger coming back above the minimum reports 0 again. + var onOverdragChanged: ((CGFloat) -> Void)? = nil + /// Release after an overdrag: final overshoot and the predicted overshoot + /// (from the drag's projected end location) for flick detection. + var onOverdragEnded: ((_ overshoot: CGFloat, _ predictedOvershoot: CGFloat) -> Void)? = nil + @State private var isDragging = false @State private var dragStartWidth: CGFloat? = nil @State private var dragStartLocation: CGFloat? = nil @@ -114,8 +123,19 @@ struct PanelResizeGrabber: View { let newWidth = startWidth + delta panelWidth = min(max(newWidth, minWidth), maxWidth) + onOverdragChanged?(max(0, minWidth - newWidth)) } - .onEnded { _ in + .onEnded { value in + if let onOverdragEnded, + let startWidth = dragStartWidth, + let startLocation = dragStartLocation { + let overshoot = { (locationX: CGFloat) -> CGFloat in + let locationDelta = locationX - startLocation + let delta = panelSide == .right ? -locationDelta : locationDelta + return max(0, minWidth - (startWidth + delta)) + } + onOverdragEnded(overshoot(value.location.x), overshoot(value.predictedEndLocation.x)) + } dragStartWidth = nil dragStartLocation = nil isDragging = false diff --git a/Yattee/Views/Player/Panels/RelatedPanelPage.swift b/Yattee/Views/Player/Panels/RelatedPanelPage.swift index 0f3ce1e9..6e01c737 100644 --- a/Yattee/Views/Player/Panels/RelatedPanelPage.swift +++ b/Yattee/Views/Player/Panels/RelatedPanelPage.swift @@ -35,6 +35,7 @@ struct RelatedPanelPage: View { VideoRowView( video: relatedVideo, style: .regular, + disableInternalTapHandling: true, showThumbnail: settings.effectiveShowThumbnails ) .tappableVideo( diff --git a/Yattee/Views/Player/PlayerControlsView.swift b/Yattee/Views/Player/PlayerControlsView.swift index 5564c354..fdcbfeb5 100644 --- a/Yattee/Views/Player/PlayerControlsView.swift +++ b/Yattee/Views/Player/PlayerControlsView.swift @@ -1130,6 +1130,9 @@ struct PlayerControlsView: View { }, isPanelDragging: { [weak appEnvironment] in appEnvironment?.navigationCoordinator.isPanelDragging ?? false + }, + trailingEdgeRevealInset: { [weak appEnvironment] in + appEnvironment?.navigationCoordinator.cornerSideColumnEdgeRevealInset ?? 0 } ) }