diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 2be8977e..379bcf2a 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -1527,16 +1527,6 @@ } } }, - "commentsPill.mode.title" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Comments" - } - } - } - }, "common.add" : { "localizations" : { "en" : { @@ -7967,6 +7957,28 @@ } } }, + "panel.settings.comments.pillMode" : { + "comment" : "Comments page editor picker label", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comments Pill" + } + } + } + }, + "panel.settings.comments.pillMode.footer" : { + "comment" : "Comments page editor picker footer", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "How comments show below the video details: as a pill that collapses on scroll, as a button, or not at all." + } + } + } + }, "panel.settings.comments.showAvatars" : { "comment" : "Comments page element toggle", "localizations" : { diff --git a/Yattee/Models/PlayerControls/PanelSettings.swift b/Yattee/Models/PlayerControls/PanelSettings.swift index 9e058537..a090dc50 100644 --- a/Yattee/Models/PlayerControls/PanelSettings.swift +++ b/Yattee/Models/PlayerControls/PanelSettings.swift @@ -671,14 +671,6 @@ struct PanelSettings: Codable, Hashable, Sendable { #endif } - /// Page types the settings list hides on this device because the panel - /// never renders them as pages: iPhone shows only the info page with - /// inline sections, so queue has no effect there and comments live on - /// the pill/overlay (configured in their own settings group). - static var hiddenSettingsPageTypes: Set { - isSectionsOnlyDevice ? [.queue, .comments] : [] - } - /// Enabled pages an inline-sections host renders below the video info, /// in configured order, with their per-page element settings. func inlineSections(in region: PanelRegion? = nil) -> [PanelPageConfiguration] { diff --git a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift index a325e54b..5bc0a7a0 100644 --- a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift +++ b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift @@ -12,6 +12,9 @@ import SwiftUI struct PanelPageEditorView: View { let pageType: PanelPageType @Bindable var viewModel: PlayerControlsSettingsViewModel + /// Whether the panel list configures inline sections (iPhone, or a + /// foldable iPhone outside the corner layout) rather than pages. + let usesSections: Bool // Local state for immediate UI updates @State private var isEnabled: Bool = true @@ -38,12 +41,15 @@ struct PanelPageEditorView: View { pageType == PanelSettings.pinnedPageType } - /// iPhone configures inline sections rather than pages. - private var usesSections: Bool { PanelSettings.isSectionsOnlyDevice } + /// Comments on an inline-sections host only show as the pill, whose + /// mode includes off: no separate toggle. + private var hasEnabledToggle: Bool { + !isPinnedPage && !(usesSections && pageType == .comments) + } var body: some View { Form { - if !isPinnedPage { + if hasEnabledToggle { enabledSection } @@ -176,6 +182,7 @@ struct PanelPageEditorView: View { // MARK: - Comments Elements Section + @ViewBuilder private var commentsElementsSection: some View { Section { Toggle( @@ -186,9 +193,18 @@ struct PanelPageEditorView: View { guard newValue != commentsSettings.effectiveShowAvatars else { return } viewModel.updatePanelCommentsSettingsSync { $0.showAvatars = newValue } } + } header: { + Text(String(localized: "panel.settings.elements.header")) + } footer: { + if !hasEnabledToggle, !viewModel.canEditActivePreset { + Text(String(localized: "settings.playerControls.duplicateToEdit")) + } + } + .disabled(!viewModel.canEditActivePreset) + Section { Picker( - String(localized: "commentsPill.mode.title"), + String(localized: "panel.settings.comments.pillMode"), selection: $commentsPillMode ) { ForEach(CommentsPillMode.allCases, id: \.self) { mode in @@ -199,8 +215,8 @@ struct PanelPageEditorView: View { guard newValue != commentsSettings.effectivePillMode else { return } viewModel.updatePanelCommentsSettingsSync { $0.pillMode = newValue } } - } header: { - Text(String(localized: "panel.settings.elements.header")) + } footer: { + Text(String(localized: "panel.settings.comments.pillMode.footer")) } .disabled(!viewModel.canEditActivePreset) } diff --git a/Yattee/Views/Settings/PlayerControls/PanelSettingsView.swift b/Yattee/Views/Settings/PlayerControls/PanelSettingsView.swift index ae10cd49..63a9259f 100644 --- a/Yattee/Views/Settings/PlayerControls/PanelSettingsView.swift +++ b/Yattee/Views/Settings/PlayerControls/PanelSettingsView.swift @@ -19,8 +19,6 @@ struct PanelSettingsView: View { @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 /// Corner layout exists on iPad and macOS, and on a foldable iPhone whose /// interior display is large enough (see `hasHinge`). @@ -37,8 +35,23 @@ struct PanelSettingsView: View { private var showsLayoutSection: Bool { Self.supportsCornerLayout || hasHinge } + /// Whether the corner layout is selected and available on this device. + private var isCornerLayout: Bool { layoutMode == .corner && showsLayoutSection } + /// iPhone has no page bar: the list configures inline sections instead. - private var usesSections: Bool { PanelSettings.isSectionsOnlyDevice } + /// A foldable iPhone in the corner layout gets a side column with real + /// pages (comments and queue included), so it configures pages there. + private var usesSections: Bool { + PanelSettings.isSectionsOnlyDevice && !isCornerLayout + } + + /// Comments shown as the pill on the info panel rather than as a page: + /// always on inline-sections hosts, and in the corner layout when they + /// sit below the video. + private var commentsIsPill: Bool { + usesSections + || (isCornerLayout && viewModel.panelSettings.effectiveRegion(for: .comments) == .belowVideo) + } var body: some View { List { @@ -50,9 +63,6 @@ struct PanelSettingsView: View { pageBarSection } pagesSection - if usesSections { - commentsSection - } } .navigationTitle(String(localized: "settings.playerControls.detailsPanel")) #if os(iOS) @@ -213,76 +223,40 @@ struct PanelSettingsView: View { } } - // MARK: - Comments Section (iPhone) - - /// iPhone never renders a comments page: comments live on the pill and - /// overlay, so their settings sit here instead of in the sections list. - private var commentsSettings: CommentsPageSettings { - viewModel.panelSettings.configuration(for: .comments)?.commentsSettings ?? CommentsPageSettings() - } - - private var commentsSection: some View { - Section { - 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 } - } - - Toggle( - String(localized: "panel.settings.comments.showAvatars"), - isOn: $commentsShowAvatars - ) - .onChange(of: commentsShowAvatars) { _, newValue in - guard newValue != commentsSettings.effectiveShowAvatars else { return } - viewModel.updatePanelCommentsSettingsSync { $0.showAvatars = newValue } - } - } header: { - Text(String(localized: "player.comments")) - } footer: { - if !viewModel.canEditActivePreset { - Text(String(localized: "settings.playerControls.duplicateToEdit")) - } - } - .disabled(!viewModel.canEditActivePreset) - } - // MARK: - Pages Section + /// Page types this layout never renders: inline-sections hosts have no + /// queue (comments stay listed as the pill row). + private var hiddenPageTypes: Set { + usesSections ? [.queue] : [] + } + /// Page types whose slot the user cannot change: the pinned page (info) - /// and, in the corner layout with the comments pill below the video, - /// comments too, since it is no longer a page in the side column. + /// and comments while they show as the pill, since the pill is not part + /// of the page/section order. private var lockedPageTypes: Set { var locked: Set = [PanelSettings.pinnedPageType] - if layoutMode == .corner, Self.supportsCornerLayout, - viewModel.panelSettings.effectiveRegion(for: .comments) == .belowVideo { + if commentsIsPill { locked.insert(.comments) } return locked } /// Locked pages are rendered outside the reorderable list so they get no - /// drag handle and nothing can be dropped between or above them. - private var lockedPages: [PanelPageConfiguration] { - let locked = lockedPageTypes - let hidden = PanelSettings.hiddenSettingsPageTypes + /// drag handle and nothing can be dropped between them: the pinned page + /// above, the comments pill below. + private func lockedPages(_ types: Set) -> [PanelPageConfiguration] { + let hidden = hiddenPageTypes return viewModel.panelPages.filter { - locked.contains($0.pageType) && !hidden.contains($0.pageType) + types.contains($0.pageType) && lockedPageTypes.contains($0.pageType) && !hidden.contains($0.pageType) } } /// Pages the user can reorder: everything but the locked pages and the - /// types this device never renders (iPhone: queue, comments). + /// types this layout never renders. private var reorderablePages: [PanelPageConfiguration] { let locked = lockedPageTypes - let hidden = PanelSettings.hiddenSettingsPageTypes + let hidden = hiddenPageTypes return viewModel.panelPages.filter { !locked.contains($0.pageType) && !hidden.contains($0.pageType) } @@ -290,7 +264,7 @@ struct PanelSettingsView: View { private var pagesSection: some View { Section { - ForEach(lockedPages) { page in + ForEach(lockedPages([PanelSettings.pinnedPageType])) { page in pageRow(for: page) .moveDisabled(true) .deleteDisabled(true) @@ -308,6 +282,12 @@ struct PanelSettingsView: View { toOffset: destination ) } + + ForEach(lockedPages([.comments])) { page in + pageRow(for: page) + .moveDisabled(true) + .deleteDisabled(true) + } } header: { Text(usesSections ? String(localized: "panel.settings.sections.header") @@ -327,7 +307,11 @@ struct PanelSettingsView: View { @ViewBuilder private func pageRow(for page: PanelPageConfiguration) -> some View { NavigationLink { - PanelPageEditorView(pageType: page.pageType, viewModel: viewModel) + PanelPageEditorView( + pageType: page.pageType, + viewModel: viewModel, + usesSections: usesSections + ) } label: { HStack { Label(page.pageType.displayName, systemImage: page.pageType.systemImage) @@ -340,7 +324,11 @@ struct PanelSettingsView: View { .background(Capsule().fill(.tint)) } Spacer() - if page.isEnabled, layoutMode == .corner, Self.supportsCornerLayout { + if page.pageType == .comments, usesSections { + // The pill has no page toggle: its mode includes off. + Text(commentsPillMode.displayName) + .foregroundStyle(.secondary) + } else if page.isEnabled, isCornerLayout { Text(viewModel.panelSettings.effectiveRegion(for: page.pageType).displayName) .foregroundStyle(.secondary) } else { @@ -355,6 +343,10 @@ struct PanelSettingsView: View { // MARK: - Helpers + private var commentsPillMode: CommentsPillMode { + viewModel.panelSettings.commentsPillMode + } + private func syncLocalState() { portraitDefaultVisibility = viewModel.panelDefaultVisibility(for: .portrait) landscapeDefaultVisibility = viewModel.panelDefaultVisibility(for: .landscape) @@ -363,8 +355,6 @@ struct PanelSettingsView: View { // 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 } }