From f289905dd8594acc0cd10f26eecf955802d28970 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 13 Oct 2026 23:57:58 +0200 Subject: [PATCH] Keep the landscape panel hidden where the corner layout doesn't fit The corner preset's landscape fallback opened the panel whenever the device could host the corner layout. On the iPhone Duo exterior display the player falls back to the widescreen layout, so the floating panel opened on every rotation into landscape. Resolve the fallback from whether the corner layout fits the current size instead. --- .../Models/PlayerControls/PanelSettings.swift | 10 ++++++++- .../Navigation/NavigationCoordinator.swift | 6 +++++ .../Player/ExpandedPlayerSheet+Layouts.swift | 10 +++++++-- Yattee/Views/Player/ExpandedPlayerSheet.swift | 22 +++++++++++++++---- 4 files changed, 41 insertions(+), 7 deletions(-) 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)