mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
Yattee v2 rewrite
This commit is contained in:
184
Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift
Normal file
184
Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift
Normal file
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// ExpandedPlayerSheet+Orientation.swift
|
||||
// Yattee
|
||||
//
|
||||
// iOS-specific orientation, fullscreen, and ambient glow functionality.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
extension ExpandedPlayerSheet {
|
||||
// MARK: - Safe Area Helpers
|
||||
|
||||
/// Get safe area insets from the window.
|
||||
var windowSafeAreaInsets: UIEdgeInsets {
|
||||
UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first?
|
||||
.windows
|
||||
.first?
|
||||
.safeAreaInsets ?? .zero
|
||||
}
|
||||
|
||||
// MARK: - Orientation Lock
|
||||
|
||||
/// Set up in-app orientation lock callback.
|
||||
func setupOrientationLockCallback() {
|
||||
DeviceRotationManager.shared.isOrientationLocked = { [weak appEnvironment] in
|
||||
appEnvironment?.settingsManager.inAppOrientationLock ?? false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fullscreen Toggle
|
||||
|
||||
/// Toggle fullscreen by rotating between portrait and landscape.
|
||||
/// When orientation lock is enabled, locks to the target orientation before rotating
|
||||
/// (keeps orientation restricted the entire time, just changes which orientation is allowed).
|
||||
func toggleFullscreen() {
|
||||
guard let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.first(where: { $0.activationState == .foregroundActive }) else { return }
|
||||
|
||||
let screenBounds = windowScene.screen.bounds
|
||||
let isCurrentlyLandscape = screenBounds.width > screenBounds.height
|
||||
let orientationManager = OrientationManager.shared
|
||||
let isOrientationLocked = appEnvironment?.settingsManager.inAppOrientationLock ?? false
|
||||
|
||||
MPVLogging.logTransition("toggleFullscreen",
|
||||
fromSize: screenBounds.size,
|
||||
toSize: isCurrentlyLandscape ? CGSize(width: screenBounds.height, height: screenBounds.width) : nil)
|
||||
|
||||
if isCurrentlyLandscape {
|
||||
// Exit fullscreen → rotate to portrait
|
||||
MPVLogging.log("toggleFullscreen: exiting to portrait")
|
||||
// Lock to portrait first (if lock enabled) so system allows only portrait rotation
|
||||
if isOrientationLocked {
|
||||
orientationManager.lock(to: .portrait)
|
||||
}
|
||||
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { _ in }
|
||||
} else {
|
||||
// Enter fullscreen → rotate to landscape
|
||||
let targetOrientation = Self.currentLandscapeInterfaceOrientation()
|
||||
MPVLogging.log("toggleFullscreen: entering landscape")
|
||||
// Lock to landscape first (if lock enabled) so system allows landscape rotation
|
||||
// Use .landscape to allow both directions initially
|
||||
if isOrientationLocked {
|
||||
orientationManager.lock(to: .landscape)
|
||||
}
|
||||
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: targetOrientation)) { _ in }
|
||||
// After rotation completes, re-lock to the specific landscape orientation
|
||||
// Use a delay since completion handler isn't reliable for waiting for rotation
|
||||
if isOrientationLocked {
|
||||
Task { @MainActor in
|
||||
try? await Task.sleep(nanoseconds: 300_000_000) // 0.3 seconds
|
||||
orientationManager.lockToCurrentOrientation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rotation Monitoring
|
||||
|
||||
/// Simplified rotation monitoring - handles layout transitions via accelerometer.
|
||||
func setupRotationMonitoring() {
|
||||
guard let appEnvironment else { return }
|
||||
|
||||
MPVLogging.log("setupRotationMonitoring: starting")
|
||||
let rotationManager = DeviceRotationManager.shared
|
||||
|
||||
// Set up callback for landscape detection (request landscape rotation)
|
||||
rotationManager.onLandscapeDetected = { [weak appEnvironment] in
|
||||
Task { @MainActor in
|
||||
guard let appEnvironment else { return }
|
||||
guard !appEnvironment.settingsManager.inAppOrientationLock else { return }
|
||||
|
||||
let playerState = appEnvironment.playerService.state
|
||||
|
||||
// Only rotate if we have a video playing
|
||||
guard playerState.currentVideo != nil,
|
||||
playerState.pipState != .active else { return }
|
||||
|
||||
// Request landscape rotation
|
||||
guard let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.first(where: { $0.activationState == .foregroundActive }) else { return }
|
||||
|
||||
let screenBounds = windowScene.screen.bounds
|
||||
if screenBounds.height > screenBounds.width {
|
||||
let targetOrientation = Self.currentLandscapeInterfaceOrientation()
|
||||
MPVLogging.logTransition("onLandscapeDetected: requesting landscape",
|
||||
fromSize: screenBounds.size, toSize: nil)
|
||||
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: targetOrientation)) { _ in }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up callback for portrait detection (request portrait rotation)
|
||||
rotationManager.onPortraitDetected = { [weak appEnvironment] in
|
||||
Task { @MainActor in
|
||||
guard let appEnvironment else { return }
|
||||
guard !appEnvironment.settingsManager.inAppOrientationLock else { return }
|
||||
|
||||
// Request portrait rotation
|
||||
guard let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.first(where: { $0.activationState == .foregroundActive }) else { return }
|
||||
|
||||
let screenBounds = windowScene.screen.bounds
|
||||
if screenBounds.width > screenBounds.height {
|
||||
MPVLogging.logTransition("onPortraitDetected: requesting portrait",
|
||||
fromSize: screenBounds.size, toSize: nil)
|
||||
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { _ in }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up callback for landscape-to-landscape rotation (rotate between left and right)
|
||||
rotationManager.onLandscapeOrientationChanged = { [weak appEnvironment] newOrientation in
|
||||
Task { @MainActor in
|
||||
guard let appEnvironment else { return }
|
||||
guard !appEnvironment.settingsManager.inAppOrientationLock else { return }
|
||||
|
||||
guard let windowScene = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.first(where: { $0.activationState == .foregroundActive }) else { return }
|
||||
|
||||
// Device and interface orientations are inverted
|
||||
let targetOrientation: UIInterfaceOrientationMask = switch newOrientation {
|
||||
case .landscapeLeft:
|
||||
.landscapeRight
|
||||
case .landscapeRight:
|
||||
.landscapeLeft
|
||||
default:
|
||||
.landscape
|
||||
}
|
||||
|
||||
MPVLogging.logTransition("onLandscapeOrientationChanged: \(newOrientation.rawValue)")
|
||||
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: targetOrientation)) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
// Always start monitoring
|
||||
rotationManager.startMonitoring()
|
||||
}
|
||||
|
||||
/// Get the interface orientation mask matching the device's current landscape orientation.
|
||||
static func currentLandscapeInterfaceOrientation() -> UIInterfaceOrientationMask {
|
||||
let deviceOrientation = DeviceRotationManager.shared.detectedOrientation
|
||||
// Device and interface orientations are inverted
|
||||
switch deviceOrientation {
|
||||
case .landscapeLeft:
|
||||
return .landscapeRight
|
||||
case .landscapeRight:
|
||||
return .landscapeLeft
|
||||
default:
|
||||
return .landscape // Fallback to any landscape
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user