Files
yattee/Yattee/Models/PlayerControls/PlayerControlsLayout.swift
Arkadiusz Fal d5285b2b83 Add unified page-based details panel on existing layouts
Phase 1 of the player panels redesign:

- New PanelSettings model (pages, regions, layout mode, default
  visibility) stored as an optional field on PlayerControlsLayout with
  an effectivePanelSettings accessor; lenient Codable so future page
  types and settings payloads never corrupt saved presets.
- New Views/Player/Panels content engine: PlayerPanelContentView
  renders a page bar plus the selected page (info, comments, related,
  chapters, queue, tracks), with runtime hiding of empty pages.
- PortraitDetailsPanel and FloatingDetailsPanel now host the shared
  content instead of their duplicated info bodies; drag machinery,
  pills, and fades are untouched.
- Comments list extracted into CommentsListContent shared by the
  overlay and the comments page; comments pill routes to the comments
  page (expanding the portrait panel) when the page is enabled, and
  falls back to the overlay when it is not.
- Tracks page embeds QualitySelectorView via a new isEmbedded mode.
- Deleted the unused video info bodies in ExpandedPlayerSheet+VideoInfo
  and the dead PlayerInfoTab model.
2026-09-21 23:20:45 +02:00

183 lines
6.7 KiB
Swift

//
// PlayerControlsLayout.swift
// Yattee
//
// Complete layout configuration for player controls.
//
import Foundation
/// Complete layout configuration for player controls, including all sections.
struct PlayerControlsLayout: Codable, Hashable, Sendable {
/// Configuration for the top row of buttons.
var topSection: LayoutSection
/// Configuration for the center section (play/pause, seek).
var centerSettings: CenterSectionSettings
/// Configuration for the bottom row of buttons.
var bottomSection: LayoutSection
/// Global settings applied to all buttons.
var globalSettings: GlobalLayoutSettings
/// Progress bar appearance settings.
var progressBarSettings: ProgressBarSettings
/// Gesture settings (iOS only). Optional for backward compatibility.
var gesturesSettings: GesturesSettings?
/// Player pill settings. Optional for backward compatibility.
var playerPillSettings: PlayerPillSettings?
/// Mini player settings. Optional for backward compatibility.
var miniPlayerSettings: MiniPlayerSettings?
/// Unused: the player reads the panel side from SettingsManager
/// (`floatingDetailsPanelSide`). Kept only so old presets that saved this
/// field still decode.
var wideLayoutPanelAlignment: FloatingPanelSide?
/// Details panel pages configuration. Optional for backward compatibility.
var panelSettings: PanelSettings?
// MARK: - Initialization
/// Creates a complete player controls layout.
/// - Parameters:
/// - topSection: Top section configuration.
/// - centerSettings: Center section settings.
/// - bottomSection: Bottom section configuration.
/// - globalSettings: Global settings.
/// - progressBarSettings: Progress bar appearance settings.
/// - gesturesSettings: Gesture settings (iOS only).
/// - playerPillSettings: Player pill settings.
/// - miniPlayerSettings: Mini player settings.
init(
topSection: LayoutSection = LayoutSection(),
centerSettings: CenterSectionSettings = .default,
bottomSection: LayoutSection = LayoutSection(),
globalSettings: GlobalLayoutSettings = .default,
progressBarSettings: ProgressBarSettings = .default,
gesturesSettings: GesturesSettings? = nil,
playerPillSettings: PlayerPillSettings? = nil,
miniPlayerSettings: MiniPlayerSettings? = nil,
panelSettings: PanelSettings? = nil
) {
self.topSection = topSection
self.centerSettings = centerSettings
self.bottomSection = bottomSection
self.globalSettings = globalSettings
self.progressBarSettings = progressBarSettings
self.gesturesSettings = gesturesSettings
self.playerPillSettings = playerPillSettings
self.miniPlayerSettings = miniPlayerSettings
self.panelSettings = panelSettings
}
// MARK: - Gesture Settings
/// Returns the effective gestures settings, using defaults if not set.
var effectiveGesturesSettings: GesturesSettings {
gesturesSettings ?? .default
}
// MARK: - Player Pill Settings
/// Returns the effective player pill settings, using defaults if not set.
var effectivePlayerPillSettings: PlayerPillSettings {
playerPillSettings ?? .default
}
// MARK: - Mini Player Settings
/// Returns the effective mini player settings, using defaults if not set.
var effectiveMiniPlayerSettings: MiniPlayerSettings {
miniPlayerSettings ?? .default
}
// MARK: - Panel Settings
/// Returns the effective details panel settings, using defaults if not set.
var effectivePanelSettings: PanelSettings {
panelSettings ?? .default
}
// MARK: - Defaults
/// Default layout matching the current hardcoded player controls.
static let `default`: PlayerControlsLayout = {
// Top section: spacer, brightness (widescreen), volume, airplay, debug, close
let topButtons: [ControlButtonConfiguration] = [
.flexibleSpacer(),
ControlButtonConfiguration(
buttonType: .brightness,
visibilityMode: .wideOnly,
settings: .slider(SliderSettings(sliderBehavior: .alwaysVisible))
),
ControlButtonConfiguration(
buttonType: .volume,
settings: .slider(SliderSettings(sliderBehavior: .alwaysVisible))
),
.defaultConfiguration(for: .airplay),
.defaultConfiguration(for: .mpvDebug),
.defaultConfiguration(for: .close)
]
// Bottom section: time, queue (widescreen), playNext, spacer, orientationLock (widescreen), contextMenu (widescreen), settings, pip, panelToggle (widescreen), fullscreen
let bottomButtons: [ControlButtonConfiguration] = [
ControlButtonConfiguration(
buttonType: .timeDisplay,
settings: .timeDisplay(TimeDisplaySettings(format: .currentAndTotal))
),
ControlButtonConfiguration(
buttonType: .queue,
visibilityMode: .wideOnly
),
.defaultConfiguration(for: .playNext),
.flexibleSpacer(),
ControlButtonConfiguration(
buttonType: .orientationLock,
visibilityMode: .wideOnly
),
ControlButtonConfiguration(
buttonType: .contextMenu,
visibilityMode: .wideOnly
),
.defaultConfiguration(for: .settings),
.defaultConfiguration(for: .pictureInPicture),
ControlButtonConfiguration(
buttonType: .panelToggle,
visibilityMode: .wideOnly
),
.defaultConfiguration(for: .fullscreen)
]
return PlayerControlsLayout(
topSection: LayoutSection(buttons: topButtons),
centerSettings: CenterSectionSettings(),
bottomSection: LayoutSection(buttons: bottomButtons),
globalSettings: GlobalLayoutSettings(),
progressBarSettings: ProgressBarSettings()
)
}()
// MARK: - Helpers
/// Returns all button types currently used in top and bottom sections.
var usedButtonTypes: Set<ControlButtonType> {
let topTypes = topSection.buttons.map(\.buttonType)
let bottomTypes = bottomSection.buttons.map(\.buttonType)
return Set(topTypes + bottomTypes)
}
/// Returns button types available to add (not already used, excluding spacer which can be duplicated).
var availableButtonTypes: [ControlButtonType] {
let used = usedButtonTypes
return ControlButtonType.availableForHorizontalSections.filter { type in
type == .spacer || !used.contains(type)
}
}
}