Files
yattee/Yattee/Views/Player/ExpandedPlayerSheet+VideoInfo.swift
Arkadiusz Fal 4210b0ab05 Simplify the wide-layout details panel: floating-only on iPhone, side as a preset setting
iPhone landscape now uses the floating panel only, always on the right;
the pin and side-toggle buttons on the grabber are gone and the pinned
setting resolves to false on phone.

iPad and macOS default to the pinned side-by-side layout, keep the pin
button to switch to floating, and pick the panel side in the preset's
panel settings instead of an on-screen arrow. The pinned state persists
as a layout preference: closing the player or the hidden default
visibility no longer resets it.

Removes PanelAlignmentButton, the global floatingDetailsPanelSide setting
and its key, FloatingPanelSide.opposite, and the legacy
wideLayoutPanelAlignment preset field.
2026-09-26 22:48:11 +02:00

116 lines
3.7 KiB
Swift

//
// ExpandedPlayerSheet+VideoInfo.swift
// Yattee
//
// Video info, description, and comments functionality for the expanded player sheet.
//
import SwiftUI
#if os(iOS) || os(macOS) || os(tvOS)
extension ExpandedPlayerSheet {
// MARK: - Scroll & Comments
/// Scrolls the content to the top.
func scrollToTop() {
withAnimation(.easeOut(duration: 0.25)) {
scrollPosition.scrollTo(y: 0)
}
}
/// Expands the comments overlay.
func expandComments() {
// Scroll player to top so video is fully visible
scrollPosition.scrollTo(y: 0)
// Use same animation as player sheet expand (0.3s, no bounce)
withAnimation(.smooth(duration: 0.3)) {
isCommentsExpanded = true
}
}
/// Collapses the comments overlay.
func collapseComments() {
navigationCoordinator?.commentsFrame = .zero
// Use same animation as player sheet dismiss (0.3s, no bounce)
withAnimation(.smooth(duration: 0.3)) {
isCommentsExpanded = false
commentsDismissOffset = 0
}
}
/// Starts preloading comments, cancelling any in-flight task.
func startPreloadingComments() {
commentsPreloadTask?.cancel()
commentsPreloadTask = Task { await preloadComments() }
}
/// Cancels any in-flight comments preload task.
func cancelCommentsPreload() {
commentsPreloadTask?.cancel()
commentsPreloadTask = nil
}
/// Handles comments dismiss offset during drag.
func handleCommentsDismissOffset(_ offset: CGFloat) {
// Don't apply real-time offset during drag - it causes feedback loop
// with scroll geometry. The scroll view bounces naturally with iOS rubber-banding.
}
/// Handles the end of comments dismiss gesture.
func handleCommentsDismissGestureEnded(_ finalOffset: CGFloat) {
let dismissThreshold: CGFloat = 30
if finalOffset >= dismissThreshold {
collapseComments()
}
// Below threshold - scroll view will rubber-band back naturally
}
/// Preloads comments for the current video.
func preloadComments() async {
guard let playerState, playerState.commentsState == .idle else { return }
// Skip when nothing displays comments (page disabled and pill disabled)
guard playerControlsLayout.effectivePanelSettings.shouldLoadComments else {
playerState.commentsState = .disabled
return
}
guard let contentService = appEnvironment?.contentService,
let instancesManager = appEnvironment?.instancesManager else { return }
await playerState.loadComments(contentService: contentService, instancesManager: instancesManager)
}
// MARK: - Actions
/// Closes the current video and dismisses the player.
func closeVideo() {
// Mark as closing to hide tab accessory before dismissal
playerState?.isClosingVideo = true
// Clear the queue when closing video
appEnvironment?.queueManager.clearQueue()
// Reset panel visibility when closing player
appEnvironment?.settingsManager.landscapeDetailsPanelVisible = false
// Stop player FIRST before dismissing window
// This ensures MPVRenderView and backend are properly cleaned up
// before the window's content view hierarchy is destroyed
playerService?.stop()
// Then dismiss player window (after backend is stopped)
navigationCoordinator?.isPlayerExpanded = false
}
/// Switches to a different stream.
func switchToStream(_ stream: Stream, audioStream: Stream? = nil) {
Task {
await playerService?.selectStreamManually(stream, audioStream: audioStream)
}
}
}
#endif