Replace per-page corner regions with one "Below Video" picker

The corner layout's below-video host is the portrait info panel, not a
page host: it can render only Info, plus Comments as the panel's pill on
iOS. The page editor still offered "Below Video" for every page, so
assigning Related there was accepted, stored, listed as such, and then
silently rendered in the side column.

- The Layout section gains a "Below Video" picker in corner mode:
  Nothing / Info / Info and Comments (macOS offers the first two). It
  writes the info/comments regions; the per-page Location section and
  its strings are gone.
- PanelSettings.normalizedPages clamps regions to what the host can
  render (only info/comments below, comments only together with info),
  so older presets self-correct. effectiveRegion(for:) resolves the
  platform-specific placement and is used by both the pages list labels
  and CornerPlayerLayout, replacing its own region checks.
- The layout schematic drops the below-video block for "Nothing"; the
  corner-mode footer explains the constraint.
- Tests cover the picker round-trip and the clamping.
This commit is contained in:
Arkadiusz Fal
2026-09-27 20:05:20 +02:00
parent 7f74b50e94
commit 0bfbd47882
7 changed files with 248 additions and 90 deletions

View File

@@ -15,7 +15,6 @@ struct PanelPageEditorView: View {
// Local state for immediate UI updates
@State private var isEnabled: Bool = true
@State private var region: PanelRegion = .primary
@State private var showDescription: Bool = true
@State private var descriptionExpandedByDefault: Bool = false
@State private var descriptionCollapsedLines: Int = InfoPageSettings.defaultDescriptionCollapsedLines
@@ -47,10 +46,6 @@ struct PanelPageEditorView: View {
enabledSection
}
if PanelSettingsView.supportsCornerLayout, viewModel.panelLayoutMode == .corner {
regionSection
}
switch pageType {
case .info:
infoElementsSection
@@ -101,28 +96,6 @@ struct PanelPageEditorView: View {
}
}
// MARK: - Region Section (corner layout)
private var regionSection: some View {
Section {
Picker(
String(localized: "panel.settings.page.region"),
selection: $region
) {
ForEach(PanelRegion.allCases, id: \.self) { region in
Text(region.displayName).tag(region)
}
}
.disabled(!viewModel.canEditActivePreset)
.onChange(of: region) { _, newValue in
guard newValue != configuration?.region else { return }
viewModel.setPanelPageRegion(newValue, for: pageType)
}
} footer: {
Text(String(localized: "panel.settings.page.region.footer"))
}
}
// MARK: - Info Elements Section
@ViewBuilder
@@ -351,7 +324,6 @@ struct PanelPageEditorView: View {
private func syncLocalState() {
isEnabled = configuration?.isEnabled ?? true
region = configuration?.region ?? .primary
let info = infoSettings
showDescription = info.effectiveShowDescription
descriptionExpandedByDefault = info.effectiveDescriptionExpandedByDefault

View File

@@ -17,6 +17,7 @@ struct PanelSettingsView: View {
@State private var landscapeDefaultVisibility: PanelDefaultVisibility = .hidden
@State private var layoutMode: PanelLayoutMode = .stacked
@State private var wideLayoutSide: FloatingPanelSide = .right
@State private var belowVideoContent: PanelBelowVideoContent = .info
@State private var showPageBarLabels: Bool = true
@State private var commentsShowAvatars: Bool = true
@State private var commentsPillMode: CommentsPillMode = .pill
@@ -71,7 +72,11 @@ struct PanelSettingsView: View {
private var layoutSection: some View {
Section {
PanelLayoutSchematic(mode: layoutMode, side: wideLayoutSide)
PanelLayoutSchematic(
mode: layoutMode,
side: wideLayoutSide,
showsBelowVideo: belowVideoContent != .nothing
)
.frame(maxWidth: .infinity)
.padding(.vertical, 4)
.listRowBackground(Color.clear)
@@ -104,9 +109,25 @@ struct PanelSettingsView: View {
guard newValue != viewModel.panelWideLayoutSide else { return }
viewModel.syncPanelWideLayoutSide(newValue)
}
} else {
Picker(
String(localized: "panel.settings.belowVideo"),
selection: $belowVideoContent
) {
ForEach(PanelBelowVideoContent.availableCases, id: \.self) { content in
Text(content.displayName).tag(content)
}
}
.disabled(!viewModel.canEditActivePreset)
.onChange(of: belowVideoContent) { _, newValue in
guard newValue != viewModel.panelBelowVideoContent else { return }
viewModel.syncPanelBelowVideoContent(newValue)
}
}
} footer: {
Text(String(localized: "panel.settings.layoutMode.footer"))
Text(layoutMode == .corner
? String(localized: "panel.settings.belowVideo.footer")
: String(localized: "panel.settings.layoutMode.footer"))
}
}
@@ -295,7 +316,7 @@ struct PanelSettingsView: View {
}
Spacer()
if page.isEnabled, layoutMode == .corner, Self.supportsCornerLayout {
Text(page.region.displayName)
Text(viewModel.panelSettings.effectiveRegion(for: page.pageType).displayName)
.foregroundStyle(.secondary)
} else {
Text(page.isEnabled
@@ -314,6 +335,8 @@ struct PanelSettingsView: View {
landscapeDefaultVisibility = viewModel.panelDefaultVisibility(for: .landscape)
layoutMode = viewModel.panelLayoutMode
wideLayoutSide = viewModel.panelWideLayoutSide
// Clamp to what this platform offers so the picker never shows blank.
belowVideoContent = viewModel.panelSettings.effectiveBelowVideoContent
showPageBarLabels = viewModel.panelShowPageBarLabels
commentsShowAvatars = commentsSettings.effectiveShowAvatars
commentsPillMode = commentsSettings.effectivePillMode
@@ -328,6 +351,8 @@ private struct PanelLayoutSchematic: View {
let mode: PanelLayoutMode
/// Panel side in the standard layout; the corner column is always right.
let side: FloatingPanelSide
/// Whether the corner layout has an info panel below the video.
let showsBelowVideo: Bool
private let cornerRadius: CGFloat = 4
@@ -341,8 +366,10 @@ private struct PanelLayoutSchematic: View {
if mode == .corner {
VStack(spacing: 3) {
videoBlock
panelBlock
.frame(height: 26)
if showsBelowVideo {
panelBlock
.frame(height: 26)
}
}
} else {
videoBlock
@@ -361,6 +388,7 @@ private struct PanelLayoutSchematic: View {
}
.animation(.easeInOut(duration: 0.25), value: mode)
.animation(.easeInOut(duration: 0.25), value: side)
.animation(.easeInOut(duration: 0.25), value: showsBelowVideo)
}
private var videoBlock: some View {