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.
This commit is contained in:
Arkadiusz Fal
2026-10-13 23:57:58 +02:00
parent b900c7baad
commit f289905dd8
4 changed files with 41 additions and 7 deletions

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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)