diff --git a/Yattee/Models/PlayerControls/PanelSettings.swift b/Yattee/Models/PlayerControls/PanelSettings.swift index 7a295616..9e058537 100644 --- a/Yattee/Models/PlayerControls/PanelSettings.swift +++ b/Yattee/Models/PlayerControls/PanelSettings.swift @@ -500,12 +500,20 @@ struct PanelSettings: Codable, Hashable, Sendable { /// landscape in the corner layout on a device that can show it, where the /// panels are the point of the layout and open visible. func effectiveDefaultVisibility(for orientation: PanelOrientation) -> PanelDefaultVisibility { + effectiveDefaultVisibility(for: orientation, cornerLayoutFits: Self.canHostCornerLayout) + } + + /// Same as `effectiveDefaultVisibility(for:)`, but the corner fallback + /// follows whether the corner layout fits the current screen — the iPhone + /// Duo exterior display can't host it and falls back to the floating + /// widescreen panel, which must not open by default. + func effectiveDefaultVisibility(for orientation: PanelOrientation, cornerLayoutFits: Bool) -> PanelDefaultVisibility { switch orientation { case .portrait: return portraitDefaultVisibility ?? defaultVisibility ?? .visible case .landscape: let fallback: PanelDefaultVisibility = - effectiveLayoutMode == .corner && Self.canHostCornerLayout ? .visible : .hidden + effectiveLayoutMode == .corner && cornerLayoutFits ? .visible : .hidden return landscapeDefaultVisibility ?? defaultVisibility ?? fallback } } diff --git a/Yattee/Services/Navigation/NavigationCoordinator.swift b/Yattee/Services/Navigation/NavigationCoordinator.swift index cdd64f66..df579e36 100644 --- a/Yattee/Services/Navigation/NavigationCoordinator.swift +++ b/Yattee/Services/Navigation/NavigationCoordinator.swift @@ -51,6 +51,7 @@ final class NavigationCoordinator { if !isPlayerExpanded { hasAppliedPanelDefaultVisibility = false isCornerLayoutActive = false + cornerLayoutFits = false isWideScreenLayoutActive = false } } @@ -66,6 +67,11 @@ final class NavigationCoordinator { /// different view than the layout resolver. var isCornerLayoutActive = false + /// Whether the expanded player's current size fits the corner layout, + /// regardless of the preset's layout mode. Lets the async preset load + /// resolve the landscape panel default for the screen actually in use. + var cornerLayoutFits = false + /// Whether the expanded player currently resolves to the widescreen /// (landscape) layout. Set from the sheet's layout pass so non-view code /// (remote control state) reads the real layout instead of screen bounds. diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift index 71961575..6136682b 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift @@ -90,11 +90,17 @@ extension ExpandedPlayerSheet { /// `size` must include the safe areas so the status bar toggling with the /// layout can't feed back into the decision. func isCornerLayout(size: CGSize) -> Bool { + playerControlsLayout.effectivePanelSettings.effectiveLayoutMode == .corner + && cornerLayoutFits(size: size) + } + + /// The size half of `isCornerLayout(size:)`, independent of the preset's + /// layout mode. + func cornerLayoutFits(size: CGSize) -> Bool { #if os(iOS) guard size.height >= Self.minHeightForCornerLayout else { return false } #endif - return playerControlsLayout.effectivePanelSettings.effectiveLayoutMode == .corner - && size.width >= Self.minWidthForCornerLayout + return size.width >= Self.minWidthForCornerLayout && isWideScreenLayout(size: size) } #endif diff --git a/Yattee/Views/Player/ExpandedPlayerSheet.swift b/Yattee/Views/Player/ExpandedPlayerSheet.swift index b9a61bd4..1cc71dab 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet.swift @@ -329,10 +329,14 @@ struct ExpandedPlayerSheet: View { // Gate on the full size including safe areas: the corner layout // shows the status bar, which would otherwise shrink the measured // height and flip the decision back (layout oscillation). - let cornerActive = isCornerLayout(size: CGSize( + let fullSize = CGSize( width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom - )) + ) + let cornerActive = isCornerLayout(size: fullSize) + let cornerFits = cornerLayoutFits(size: fullSize) + #else + let cornerFits = false #endif ZStack(alignment: .top) { @@ -397,6 +401,9 @@ struct ExpandedPlayerSheet: View { .onChange(of: cornerActive) { _, newValue in navigationCoordinator?.isCornerLayoutActive = newValue } + .onChange(of: cornerFits, initial: true) { _, newValue in + navigationCoordinator?.cornerLayoutFits = newValue + } #endif .onChange(of: wideScreen, initial: true) { _, newValue in navigationCoordinator?.isWideScreenLayoutActive = newValue @@ -422,6 +429,7 @@ struct ExpandedPlayerSheet: View { // Entering landscape: apply the preset's default applyPanelDefaultVisibility( playerControlsLayout.effectivePanelSettings, + cornerLayoutFits: cornerFits, settingsManager: appEnvironment?.settingsManager ) // Portrait panel doesn't exist in wide layout - clear flag so gesture handler allows pinch @@ -450,6 +458,7 @@ struct ExpandedPlayerSheet: View { if navigationCoordinator?.hasAppliedPanelDefaultVisibility != true { let visible = applyPanelDefaultVisibility( PanelSettings.cached, + cornerLayoutFits: cornerFits, settingsManager: appEnvironment?.settingsManager ) #if os(iOS) || os(macOS) @@ -760,6 +769,7 @@ private struct PlayerEventHandlersModifier: ViewModifier { if navigationCoordinator?.hasAppliedPanelDefaultVisibility != true { let visible = applyPanelDefaultVisibility( layout.effectivePanelSettings, + cornerLayoutFits: navigationCoordinator?.cornerLayoutFits ?? PanelSettings.canHostCornerLayout, settingsManager: appEnvironment?.settingsManager ) #if os(iOS) || os(macOS) @@ -786,10 +796,14 @@ private struct PlayerEventHandlersModifier: ViewModifier { /// state, which lives in view @State rather than settings. @MainActor @discardableResult -private func applyPanelDefaultVisibility(_ settings: PanelSettings, settingsManager: SettingsManager?) -> Bool { +private func applyPanelDefaultVisibility( + _ settings: PanelSettings, + cornerLayoutFits: Bool, + settingsManager: SettingsManager? +) -> Bool { guard let settingsManager else { return true } settingsManager.landscapeDetailsPanelVisible = settings - .effectiveDefaultVisibility(for: .landscape) + .effectiveDefaultVisibility(for: .landscape, cornerLayoutFits: cornerLayoutFits) .resolve(lastVisible: settingsManager.landscapeDetailsPanelLastVisible) return settings .effectiveDefaultVisibility(for: .portrait)