diff --git a/Yattee/Models/PlayerControls/PanelSettings.swift b/Yattee/Models/PlayerControls/PanelSettings.swift index 2bf33dd0..f3edbb00 100644 --- a/Yattee/Models/PlayerControls/PanelSettings.swift +++ b/Yattee/Models/PlayerControls/PanelSettings.swift @@ -147,11 +147,42 @@ struct InfoPageSettings: Codable, Hashable, Sendable { var effectiveShowDescription: Bool { showDescription ?? true } } +// MARK: - Comments Pill Mode + +/// Controls how the comments pill is displayed in the player. +/// Stored on the comments page settings (moved from the player pill settings; +/// `PlayerControlsLayout.effectivePanelSettings` migrates the legacy value). +enum CommentsPillMode: String, Codable, Hashable, Sendable, CaseIterable { + case pill // Default - shows expanded pill, collapses on scroll + case button // Always show collapsed (button-only) + case disabled // No button/pill visible + + /// Localized display name for settings UI. + var displayName: String { + switch self { + case .pill: + return String(localized: "commentsPill.mode.pill") + case .button: + return String(localized: "commentsPill.mode.button") + case .disabled: + return String(localized: "commentsPill.mode.disabled") + } + } + + /// Whether the comments pill should always be collapsed (button mode). + var alwaysCollapsed: Bool { + self == .button + } +} + /// Element toggles for the comments page. struct CommentsPageSettings: Codable, Hashable, Sendable { var showAvatars: Bool? + /// How the comments pill (portrait/floating panel) is displayed. + var pillMode: CommentsPillMode? var effectiveShowAvatars: Bool { showAvatars ?? true } + var effectivePillMode: CommentsPillMode { pillMode ?? .pill } } /// Element toggles for the related videos page. @@ -418,6 +449,17 @@ struct PanelSettings: Codable, Hashable, Sendable { enabledPages().contains { $0.pageType == pageType } } + /// The comments pill mode from the comments page settings. + var commentsPillMode: CommentsPillMode { + configuration(for: .comments)?.commentsSettings.effectivePillMode ?? .pill + } + + /// Whether comments should be loaded from the API: needed by the comments + /// page (when enabled) and by the pill (unless disabled). + var shouldLoadComments: Bool { + isPageEnabled(.comments) || commentsPillMode != .disabled + } + /// The stored configuration for a page type, if present. func configuration(for pageType: PanelPageType) -> PanelPageConfiguration? { normalizedPages.first { $0.pageType == pageType } diff --git a/Yattee/Models/PlayerControls/PlayerControlsLayout.swift b/Yattee/Models/PlayerControls/PlayerControlsLayout.swift index 10499174..05ebdff5 100644 --- a/Yattee/Models/PlayerControls/PlayerControlsLayout.swift +++ b/Yattee/Models/PlayerControls/PlayerControlsLayout.swift @@ -100,8 +100,18 @@ struct PlayerControlsLayout: Codable, Hashable, Sendable { // MARK: - Panel Settings /// Returns the effective details panel settings, using defaults if not set. + /// Migrates the legacy comments pill mode (once stored on the player pill + /// settings) into the comments page settings; the migrated value persists + /// the first time the panel settings are edited and saved. var effectivePanelSettings: PanelSettings { - panelSettings ?? .default + var settings = panelSettings ?? .default + if let legacyMode = playerPillSettings?.commentsPillMode, + settings.configuration(for: .comments)?.commentsSettings.pillMode == nil { + var comments = settings.configuration(for: .comments)?.commentsSettings ?? CommentsPageSettings() + comments.pillMode = legacyMode + settings.updateSettings(.comments(comments), for: .comments) + } + return settings } // MARK: - Defaults diff --git a/Yattee/Models/PlayerControls/PlayerPillSettings.swift b/Yattee/Models/PlayerControls/PlayerPillSettings.swift index 0feefa1d..dca1efe2 100644 --- a/Yattee/Models/PlayerControls/PlayerPillSettings.swift +++ b/Yattee/Models/PlayerControls/PlayerPillSettings.swift @@ -8,37 +8,6 @@ import Foundation import SwiftUI -// MARK: - CommentsPillMode - -/// Controls how the comments pill is displayed in the player. -enum CommentsPillMode: String, Codable, Hashable, Sendable, CaseIterable { - case pill // Default - shows expanded pill, collapses on scroll - case button // Always show collapsed (button-only) - case disabled // No button/pill visible, no API query - - /// Localized display name for settings UI. - var displayName: String { - switch self { - case .pill: - return String(localized: "commentsPill.mode.pill") - case .button: - return String(localized: "commentsPill.mode.button") - case .disabled: - return String(localized: "commentsPill.mode.disabled") - } - } - - /// Whether comments should be loaded from the API. - var shouldLoadComments: Bool { - self != .disabled - } - - /// Whether the comments pill should always be collapsed (button mode). - var alwaysCollapsed: Bool { - self == .button - } -} - // MARK: - PillVisibility /// Controls when the player pill is visible based on orientation. @@ -89,7 +58,9 @@ struct PlayerPillSettings: Codable, Hashable, Sendable { /// Ordered list of buttons to display in the pill. var buttons: [ControlButtonConfiguration] - /// How the comments pill should be displayed (optional for backward compatibility). + /// Legacy: the comments pill mode now lives on the comments page settings + /// (`CommentsPageSettings.pillMode`). Kept only so old presets decode; + /// `PlayerControlsLayout.effectivePanelSettings` migrates it. var commentsPillMode: CommentsPillMode? // MARK: - Initialization @@ -104,28 +75,6 @@ struct PlayerPillSettings: Codable, Hashable, Sendable { self.commentsPillMode = commentsPillMode } - // MARK: - Computed Properties - - /// Returns the effective comments pill mode, defaulting to `.pill` when nil. - var effectiveCommentsPillMode: CommentsPillMode { - commentsPillMode ?? .pill - } - - /// Whether comments should be loaded from the API. - var shouldLoadComments: Bool { - effectiveCommentsPillMode.shouldLoadComments - } - - /// Whether the comments pill should always be shown collapsed. - var isCommentsPillAlwaysCollapsed: Bool { - effectiveCommentsPillMode.alwaysCollapsed - } - - /// Whether the comments pill should be visible at all. - var shouldShowCommentsPill: Bool { - effectiveCommentsPillMode != .disabled - } - // MARK: - Mutation Helpers /// Adds a button of the given type to the pill. diff --git a/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift b/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift index cbd6e1be..0aa218c2 100644 --- a/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift +++ b/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift @@ -625,19 +625,6 @@ final class PlayerControlsSettingsViewModel { playerPillSettings.buttons } - /// Current comments pill mode. - var commentsPillMode: CommentsPillMode { - playerPillSettings.effectiveCommentsPillMode - } - - /// Synchronously updates the comments pill mode with immediate UI feedback. - /// - Parameter mode: The new comments pill mode. - func syncCommentsPillMode(_ mode: CommentsPillMode) { - updatePlayerPillSettingsSync { settings in - settings.commentsPillMode = mode - } - } - /// Synchronously updates the pill visibility with immediate UI feedback. /// - Parameter visibility: The new visibility mode. func syncPillVisibility(_ visibility: PillVisibility) { diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift index 04faffbe..44f64d7d 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift @@ -165,13 +165,14 @@ extension ExpandedPlayerSheet { // Check if expanded comments pill will be visible (needs extra panel height) let pillSettings = playerControlsLayout.effectivePlayerPillSettings + let commentsPillMode = playerControlsLayout.effectivePanelSettings.commentsPillMode let hasCommentsPill = playerState?.commentsState == .loaded && !(playerState?.comments.isEmpty ?? true) && - pillSettings.shouldShowCommentsPill + commentsPillMode != .disabled let hasExpandedCommentsPill = hasCommentsPill && pillSettings.visibility.isVisible(isWideLayout: false) && !pillSettings.buttons.isEmpty && - !pillSettings.isCommentsPillAlwaysCollapsed + !commentsPillMode.alwaysCollapsed let minPanelHeight: CGFloat = useCompactPanel ? 150 : 200 // When compact panel but expanded comments pill visible, need taller panel (~250pt) diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+VideoInfo.swift b/Yattee/Views/Player/ExpandedPlayerSheet+VideoInfo.swift index 4498ee51..fe25a3ff 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+VideoInfo.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+VideoInfo.swift @@ -70,9 +70,8 @@ extension ExpandedPlayerSheet { func preloadComments() async { guard let playerState, playerState.commentsState == .idle else { return } - // Check if comments pill is disabled in preset settings - let pillSettings = playerControlsLayout.effectivePlayerPillSettings - guard pillSettings.shouldLoadComments else { + // Skip when nothing displays comments (page disabled and pill disabled) + guard playerControlsLayout.effectivePanelSettings.shouldLoadComments else { playerState.commentsState = .disabled return } diff --git a/Yattee/Views/Player/FloatingDetailsPanel.swift b/Yattee/Views/Player/FloatingDetailsPanel.swift index 170134fe..848c374d 100644 --- a/Yattee/Views/Player/FloatingDetailsPanel.swift +++ b/Yattee/Views/Player/FloatingDetailsPanel.swift @@ -69,15 +69,15 @@ struct FloatingDetailsPanel: View { !playerPillSettings.buttons.isEmpty } - // Comments pill helpers + // Comments pill helpers (mode lives on the comments page settings) private var commentsPillMode: CommentsPillMode { - playerPillSettings.effectiveCommentsPillMode + panelSettings.commentsPillMode } private var shouldShowCommentsPill: Bool { - playerPillSettings.shouldShowCommentsPill + commentsPillMode != .disabled } private var isCommentsPillAlwaysCollapsed: Bool { - playerPillSettings.isCommentsPillAlwaysCollapsed + commentsPillMode.alwaysCollapsed } private var hasVideoDescription: Bool { diff --git a/Yattee/Views/Player/PortraitDetailsPanel.swift b/Yattee/Views/Player/PortraitDetailsPanel.swift index a8a85fa0..1a742201 100644 --- a/Yattee/Views/Player/PortraitDetailsPanel.swift +++ b/Yattee/Views/Player/PortraitDetailsPanel.swift @@ -72,15 +72,15 @@ struct PortraitDetailsPanel: View { !playerPillSettings.buttons.isEmpty } - // Comments pill helpers + // Comments pill helpers (mode lives on the comments page settings) private var commentsPillMode: CommentsPillMode { - playerPillSettings.effectiveCommentsPillMode + panelSettings.commentsPillMode } private var shouldShowCommentsPill: Bool { - !hidesCommentsPill && playerPillSettings.shouldShowCommentsPill + !hidesCommentsPill && commentsPillMode != .disabled } private var isCommentsPillAlwaysCollapsed: Bool { - playerPillSettings.isCommentsPillAlwaysCollapsed + commentsPillMode.alwaysCollapsed } private var hasVideoDescription: Bool { diff --git a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift index 08de087b..33badd1b 100644 --- a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift +++ b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift @@ -20,6 +20,7 @@ struct PanelPageEditorView: View { @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 @@ -167,6 +168,19 @@ struct PanelPageEditorView: View { 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")) } @@ -308,6 +322,7 @@ struct PanelPageEditorView: View { showStatsRow = info.effectiveShowStatsRow showChannelRow = info.effectiveShowChannelRow showAvatars = commentsSettings.effectiveShowAvatars + commentsPillMode = commentsSettings.effectivePillMode showRelatedThumbnails = relatedSettings.effectiveShowThumbnails showChapterThumbnails = chaptersSettings.effectiveShowThumbnails showHistory = queueSettings.effectiveShowHistory diff --git a/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift b/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift index 8e22cdb6..752cfbd0 100644 --- a/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift +++ b/Yattee/Views/Settings/PlayerControls/PlayerControlsSettingsView.swift @@ -91,7 +91,6 @@ private struct PlayerControlsSettingsContent: View { ) LayoutSectionsSection(viewModel: viewModel) #if os(iOS) - CommentsPillSection(viewModel: viewModel) GesturesSectionsSection(viewModel: viewModel) #endif SystemControlsSection(viewModel: viewModel) @@ -426,39 +425,6 @@ private struct LayoutSectionsSection: View { } } -// MARK: - Comments Pill Section - -private struct CommentsPillSection: View { - @Bindable var viewModel: PlayerControlsSettingsViewModel - - // Local state for immediate UI updates - @State private var commentsPillMode: CommentsPillMode = .pill - - var body: some View { - Section { - Picker( - String(localized: "commentsPill.mode.title"), - selection: $commentsPillMode - ) { - ForEach(CommentsPillMode.allCases, id: \.self) { mode in - Text(mode.displayName).tag(mode) - } - } - .disabled(!viewModel.canEditActivePreset) - .onChange(of: commentsPillMode) { _, newValue in - guard newValue != viewModel.commentsPillMode else { return } - viewModel.syncCommentsPillMode(newValue) - } - } - .onAppear { - commentsPillMode = viewModel.commentsPillMode - } - .onChange(of: viewModel.activePreset?.id) { _, _ in - commentsPillMode = viewModel.commentsPillMode - } - } -} - // MARK: - Gestures Sections Section (iOS only) #if os(iOS)