From e7458ba629e18120d1492a6d9f120ce3a64fb748 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 23 Sep 2026 22:45:38 +0200 Subject: [PATCH] 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. --- .../Models/PlayerControls/PanelSettings.swift | 42 ++++++++++++++ .../PlayerControls/PlayerControlsLayout.swift | 12 +++- .../PlayerControls/PlayerPillSettings.swift | 57 +------------------ .../PlayerControlsSettingsViewModel.swift | 13 ----- .../Player/ExpandedPlayerSheet+Layouts.swift | 5 +- .../ExpandedPlayerSheet+VideoInfo.swift | 5 +- .../Views/Player/FloatingDetailsPanel.swift | 8 +-- .../Views/Player/PortraitDetailsPanel.swift | 8 +-- .../PlayerControls/PanelPageEditorView.swift | 15 +++++ .../PlayerControlsSettingsView.swift | 34 ----------- 10 files changed, 84 insertions(+), 115 deletions(-) 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)