Files
yattee/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift
Arkadiusz Fal e7458ba629 Move comments pill mode into the comments page settings
The pill mode now lives on CommentsPageSettings and is edited in the
comments page editor instead of a standalone settings section. The
legacy value on PlayerPillSettings is kept for decoding old presets
and migrated by effectivePanelSettings. Comments are loaded when
either the comments page is enabled or the pill is not disabled.
2026-09-23 22:45:38 +02:00

338 lines
12 KiB
Swift

//
// PanelPageEditorView.swift
// Yattee
//
// Settings editor for a single details panel page.
//
import SwiftUI
#if os(iOS) || os(macOS)
struct PanelPageEditorView: View {
let pageType: PanelPageType
@Bindable var viewModel: PlayerControlsSettingsViewModel
// 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 showStatsRow: Bool = true
@State private var showChannelRow: Bool = true
@State private var showAvatars: Bool = true
@State private var commentsPillMode: CommentsPillMode = .pill
@State private var showRelatedThumbnails: Bool = true
@State private var showChapterThumbnails: Bool = true
@State private var showHistory: Bool = true
@State private var showQuality: Bool = true
@State private var showAudio: Bool = true
@State private var showSubtitles: Bool = true
@State private var showRate: Bool = true
private var configuration: PanelPageConfiguration? {
viewModel.panelSettings.configuration(for: pageType)
}
/// Whether this is the only enabled page; the last one cannot be disabled.
private var isLastEnabledPage: Bool {
isEnabled && viewModel.enabledPanelPageCount <= 1
}
var body: some View {
Form {
enabledSection
if PanelSettingsView.supportsCornerLayout, viewModel.panelLayoutMode == .corner {
regionSection
}
switch pageType {
case .info:
infoElementsSection
case .comments:
commentsElementsSection
case .related:
relatedElementsSection
case .chapters:
chaptersElementsSection
case .queue:
queueElementsSection
case .tracks:
tracksElementsSection
}
}
.navigationTitle(pageType.displayName)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#elseif os(macOS)
.formStyle(.grouped)
#endif
.opaqueSettingsFormBackground()
.onAppear {
syncLocalState()
}
.onChange(of: viewModel.activePreset?.id) { _, _ in
syncLocalState()
}
}
// MARK: - Enabled Section
private var enabledSection: some View {
Section {
Toggle(
String(localized: "panel.settings.page.enabled"),
isOn: $isEnabled
)
.disabled(!viewModel.canEditActivePreset || isLastEnabledPage)
.onChange(of: isEnabled) { _, newValue in
guard newValue != configuration?.isEnabled else { return }
viewModel.setPanelPageEnabled(pageType, isEnabled: newValue)
}
} footer: {
if isLastEnabledPage {
Text(String(localized: "panel.settings.page.lastEnabled.footer"))
} else if !viewModel.canEditActivePreset {
Text(String(localized: "settings.playerControls.duplicateToEdit"))
}
}
}
// 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
private var infoElementsSection: some View {
Section {
Toggle(
String(localized: "panel.settings.info.showStatsRow"),
isOn: $showStatsRow
)
.onChange(of: showStatsRow) { _, newValue in
guard newValue != infoSettings.effectiveShowStatsRow else { return }
viewModel.updatePanelInfoSettingsSync { $0.showStatsRow = newValue }
}
Toggle(
String(localized: "panel.settings.info.showChannelRow"),
isOn: $showChannelRow
)
.onChange(of: showChannelRow) { _, newValue in
guard newValue != infoSettings.effectiveShowChannelRow else { return }
viewModel.updatePanelInfoSettingsSync { $0.showChannelRow = newValue }
}
Toggle(
String(localized: "panel.settings.info.showDescription"),
isOn: $showDescription
)
.onChange(of: showDescription) { _, newValue in
guard newValue != infoSettings.effectiveShowDescription else { return }
viewModel.updatePanelInfoSettingsSync { $0.showDescription = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Comments Elements Section
private var commentsElementsSection: some View {
Section {
Toggle(
String(localized: "panel.settings.comments.showAvatars"),
isOn: $showAvatars
)
.onChange(of: showAvatars) { _, newValue in
guard newValue != commentsSettings.effectiveShowAvatars else { return }
viewModel.updatePanelCommentsSettingsSync { $0.showAvatars = newValue }
}
Picker(
String(localized: "commentsPill.mode.title"),
selection: $commentsPillMode
) {
ForEach(CommentsPillMode.allCases, id: \.self) { mode in
Text(mode.displayName).tag(mode)
}
}
.onChange(of: commentsPillMode) { _, newValue in
guard newValue != commentsSettings.effectivePillMode else { return }
viewModel.updatePanelCommentsSettingsSync { $0.pillMode = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Related Elements Section
private var relatedElementsSection: some View {
Section {
Toggle(
String(localized: "panel.settings.showThumbnails"),
isOn: $showRelatedThumbnails
)
.onChange(of: showRelatedThumbnails) { _, newValue in
guard newValue != relatedSettings.effectiveShowThumbnails else { return }
viewModel.updatePanelRelatedSettingsSync { $0.showThumbnails = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Chapters Elements Section
private var chaptersElementsSection: some View {
Section {
Toggle(
String(localized: "panel.settings.showThumbnails"),
isOn: $showChapterThumbnails
)
.onChange(of: showChapterThumbnails) { _, newValue in
guard newValue != chaptersSettings.effectiveShowThumbnails else { return }
viewModel.updatePanelChaptersSettingsSync { $0.showThumbnails = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Queue Elements Section
private var queueElementsSection: some View {
Section {
Toggle(
String(localized: "panel.settings.queue.showHistory"),
isOn: $showHistory
)
.onChange(of: showHistory) { _, newValue in
guard newValue != queueSettings.effectiveShowHistory else { return }
viewModel.updatePanelQueueSettingsSync { $0.showHistory = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
} footer: {
Text(String(localized: "panel.settings.queue.showHistory.footer"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Tracks Elements Section
private var tracksElementsSection: some View {
Section {
Toggle(
String(localized: "player.quality.video"),
isOn: $showQuality
)
.onChange(of: showQuality) { _, newValue in
guard newValue != tracksSettings.effectiveShowQuality else { return }
viewModel.updatePanelTracksSettingsSync { $0.showQuality = newValue }
}
Toggle(
String(localized: "stream.audio"),
isOn: $showAudio
)
.onChange(of: showAudio) { _, newValue in
guard newValue != tracksSettings.effectiveShowAudio else { return }
viewModel.updatePanelTracksSettingsSync { $0.showAudio = newValue }
}
Toggle(
String(localized: "stream.subtitles"),
isOn: $showSubtitles
)
.onChange(of: showSubtitles) { _, newValue in
guard newValue != tracksSettings.effectiveShowSubtitles else { return }
viewModel.updatePanelTracksSettingsSync { $0.showSubtitles = newValue }
}
Toggle(
String(localized: "controls.button.speed"),
isOn: $showRate
)
.onChange(of: showRate) { _, newValue in
guard newValue != tracksSettings.effectiveShowRate else { return }
viewModel.updatePanelTracksSettingsSync { $0.showRate = newValue }
}
} header: {
Text(String(localized: "panel.settings.elements.header"))
}
.disabled(!viewModel.canEditActivePreset)
}
// MARK: - Helpers
private var infoSettings: InfoPageSettings {
configuration?.infoSettings ?? InfoPageSettings()
}
private var commentsSettings: CommentsPageSettings {
configuration?.commentsSettings ?? CommentsPageSettings()
}
private var relatedSettings: RelatedPageSettings {
configuration?.relatedSettings ?? RelatedPageSettings()
}
private var chaptersSettings: ChaptersPageSettings {
configuration?.chaptersSettings ?? ChaptersPageSettings()
}
private var queueSettings: QueuePageSettings {
configuration?.queueSettings ?? QueuePageSettings()
}
private var tracksSettings: TracksPageSettings {
configuration?.tracksSettings ?? TracksPageSettings()
}
private func syncLocalState() {
isEnabled = configuration?.isEnabled ?? true
region = configuration?.region ?? .primary
let info = infoSettings
showDescription = info.effectiveShowDescription
showStatsRow = info.effectiveShowStatsRow
showChannelRow = info.effectiveShowChannelRow
showAvatars = commentsSettings.effectiveShowAvatars
commentsPillMode = commentsSettings.effectivePillMode
showRelatedThumbnails = relatedSettings.effectiveShowThumbnails
showChapterThumbnails = chaptersSettings.effectiveShowThumbnails
showHistory = queueSettings.effectiveShowHistory
let tracks = tracksSettings
showQuality = tracks.effectiveShowQuality
showAudio = tracks.effectiveShowAudio
showSubtitles = tracks.effectiveShowSubtitles
showRate = tracks.effectiveShowRate
}
}
#endif