diff --git a/Yattee/Views/Player/CornerPlayerLayout.swift b/Yattee/Views/Player/CornerPlayerLayout.swift index 809395f9..76668fe0 100644 --- a/Yattee/Views/Player/CornerPlayerLayout.swift +++ b/Yattee/Views/Player/CornerPlayerLayout.swift @@ -60,6 +60,10 @@ struct CornerPlayerLayout: View { /// Side column width bounds. static var minColumnWidth: CGFloat { 280 } + + /// Resize grabber strip on the side column's leading edge — narrow, + /// matching the panel drag handle look. + static var grabberWidth: CGFloat { 12 } static var maxColumnWidthCap: CGFloat { 560 } /// Minimum width kept for the video column. static var minVideoColumnWidth: CGFloat { 480 } @@ -167,7 +171,7 @@ struct CornerPlayerLayout: View { let effectiveColumnWidth = clampColumnWidth(columnWidth, availableWidth: width) // Grabber sits on the column's leading edge, inside the column // background — narrow strip matching the panel drag handle look. - let grabberWidth: CGFloat = 12 + let grabberWidth = Self.grabberWidth let totalColumnWidth = effectiveColumnWidth + grabberWidth let videoWidth = showsSideColumn ? max(1, width - totalColumnWidth) : width @@ -540,6 +544,13 @@ struct CornerPlayerLayout: View { pageBarTopPadding: 8, excludedPages: belowVideoPageTypes ) + // The grabber strip already separates the column from the video; + // trim the leading inset so content sits 16pt from the column's + // outer edge on both sides instead of grabber + 16 on the left. + .environment(\.panelHorizontalInsets, PanelHorizontalInsets( + leading: max(0, PanelHorizontalInsets.standard - Self.grabberWidth), + trailing: PanelHorizontalInsets.standard + )) .background(panelBackgroundColor.ignoresSafeArea(edges: .bottom)) } diff --git a/Yattee/Views/Player/Panels/ChaptersPanelPage.swift b/Yattee/Views/Player/Panels/ChaptersPanelPage.swift index ee54910f..138d258b 100644 --- a/Yattee/Views/Player/Panels/ChaptersPanelPage.swift +++ b/Yattee/Views/Player/Panels/ChaptersPanelPage.swift @@ -38,7 +38,7 @@ struct ChaptersPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 8) } .task(id: context.video.id) { diff --git a/Yattee/Views/Player/Panels/CommentsPanelPage.swift b/Yattee/Views/Player/Panels/CommentsPanelPage.swift index d5486c6d..217aa220 100644 --- a/Yattee/Views/Player/Panels/CommentsPanelPage.swift +++ b/Yattee/Views/Player/Panels/CommentsPanelPage.swift @@ -23,7 +23,7 @@ struct CommentsPanelPage: View { videoID: context.video.id.videoID, showAvatars: settings.effectiveShowAvatars ) - .padding(.horizontal) + .panelHorizontalPadding() } .task(id: context.video.id) { // Opening the page is an explicit request; load even when the diff --git a/Yattee/Views/Player/Panels/InfoPanelPage.swift b/Yattee/Views/Player/Panels/InfoPanelPage.swift index 9c24c468..08c97a46 100644 --- a/Yattee/Views/Player/Panels/InfoPanelPage.swift +++ b/Yattee/Views/Player/Panels/InfoPanelPage.swift @@ -64,7 +64,7 @@ struct InfoPanelPage: View { if settings.effectiveShowDescription { Divider() - .padding(.horizontal) + .panelHorizontalPadding() descriptionContent .padding(.vertical) @@ -126,7 +126,7 @@ struct InfoPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.vertical, 8) } @@ -144,7 +144,7 @@ struct InfoPanelPage: View { .font(.subheadline) .foregroundStyle(.secondary) .tint(context.accentColor) - .padding(.horizontal) + .panelHorizontalPadding() .handleTimestampLinks(using: playerService) } } else if videoDetailsState == .loading { @@ -158,7 +158,7 @@ struct InfoPanelPage: View { Text(String(localized: "player.noDescription")) .font(.subheadline) .foregroundStyle(.secondary) - .padding(.horizontal) + .panelHorizontalPadding() } } } @@ -221,7 +221,7 @@ struct InfoPanelPage: View { .buttonStyle(.plain) } } - .padding(.horizontal) + .panelHorizontalPadding() .contentShape(Rectangle()) .onTapGesture { if !isDescriptionExpanded { @@ -257,7 +257,7 @@ struct InfoPanelPage: View { Spacer(minLength: 0) } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 4) .padding(.bottom, 8) .contentShape(Rectangle()) @@ -269,7 +269,7 @@ struct InfoPanelPage: View { private var chaptersSection: some View { if !chapters.isEmpty { Divider() - .padding(.horizontal) + .panelHorizontalPadding() .padding(.bottom, 8) sectionHeader(String(localized: "player.chapters"), isCollapsed: $isChaptersCollapsed) @@ -290,7 +290,7 @@ struct InfoPanelPage: View { ) } } - .padding(.horizontal) + .panelHorizontalPadding() } } } @@ -299,7 +299,7 @@ struct InfoPanelPage: View { private var relatedSection: some View { if !relatedVideos.isEmpty { Divider() - .padding(.horizontal) + .panelHorizontalPadding() .padding(.vertical, 8) sectionHeader(String(localized: "videoInfo.section.relatedVideos"), isCollapsed: $isRelatedCollapsed) @@ -327,7 +327,7 @@ struct InfoPanelPage: View { } } } - .padding(.horizontal) + .panelHorizontalPadding() } } } diff --git a/Yattee/Views/Player/Panels/PanelPageBar.swift b/Yattee/Views/Player/Panels/PanelPageBar.swift index 055fe49c..b3792772 100644 --- a/Yattee/Views/Player/Panels/PanelPageBar.swift +++ b/Yattee/Views/Player/Panels/PanelPageBar.swift @@ -15,6 +15,13 @@ struct PanelPageBar: View { let accentColor: Color var showLabels: Bool = true + @Environment(\.panelHorizontalInsets) private var insets + + /// Pills sit 4pt closer to the edge than page content (12 vs 16 with the + /// standard inset); a host that trims the content inset (corner side + /// column) trims the bar the same way so the first pill lines up. + private static let pillEdgeOffset: CGFloat = 4 + var body: some View { ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: false) { @@ -24,7 +31,8 @@ struct PanelPageBar: View { .id(page.pageType) } } - .padding(.horizontal, 12) + .padding(.leading, max(0, insets.leading - Self.pillEdgeOffset)) + .padding(.trailing, max(0, insets.trailing - Self.pillEdgeOffset)) .padding(.vertical, 6) } .onChange(of: selectedPage) { _, newValue in diff --git a/Yattee/Views/Player/Panels/PanelPageContext.swift b/Yattee/Views/Player/Panels/PanelPageContext.swift index a4ae5dd0..7d883ae1 100644 --- a/Yattee/Views/Player/Panels/PanelPageContext.swift +++ b/Yattee/Views/Player/Panels/PanelPageContext.swift @@ -46,6 +46,67 @@ struct PanelScrollConfig { var onOverscrollDragEnded: ((CGFloat, CGFloat) -> Void)? } +// MARK: - Horizontal Insets + +/// Horizontal content insets a host wants on the panel pages. Hosts whose +/// panel already carries leading chrome (the corner side column's resize +/// grabber strip) narrow the leading inset so content sits the same distance +/// from both edges. +struct PanelHorizontalInsets: Equatable { + static let standard: CGFloat = 16 + + var leading: CGFloat = PanelHorizontalInsets.standard + var trailing: CGFloat = PanelHorizontalInsets.standard + + /// Leading delta relative to the standard inset; List-backed pages + /// (queue, tracks) apply it as a scroll content margin since their + /// section insets can't be restyled per host. + var leadingListAdjustment: CGFloat { leading - Self.standard } +} + +private struct PanelHorizontalInsetsKey: EnvironmentKey { + static let defaultValue = PanelHorizontalInsets() +} + +extension EnvironmentValues { + var panelHorizontalInsets: PanelHorizontalInsets { + get { self[PanelHorizontalInsetsKey.self] } + set { self[PanelHorizontalInsetsKey.self] = newValue } + } +} + +private struct PanelHorizontalPadding: ViewModifier { + @Environment(\.panelHorizontalInsets) private var insets + + func body(content: Content) -> some View { + content + .padding(.leading, insets.leading) + .padding(.trailing, insets.trailing) + } +} + +private struct PanelListLeadingMargin: ViewModifier { + @Environment(\.panelHorizontalInsets) private var insets + + func body(content: Content) -> some View { + content + .contentMargins(.leading, insets.leadingListAdjustment, for: .scrollContent) + } +} + +extension View { + /// Host-configurable replacement for `.padding(.horizontal)` on panel + /// page content. + func panelHorizontalPadding() -> some View { + modifier(PanelHorizontalPadding()) + } + + /// Shifts List-backed page content by the host's leading inset delta. + func panelListLeadingMargin() -> some View { + modifier(PanelListLeadingMargin()) + } +} + // MARK: - Page Scroll View /// Shared scroll container used by the scrollable panel pages. Applies the diff --git a/Yattee/Views/Player/Panels/QueuePanelPage.swift b/Yattee/Views/Player/Panels/QueuePanelPage.swift index 8bbfda00..345654da 100644 --- a/Yattee/Views/Player/Panels/QueuePanelPage.swift +++ b/Yattee/Views/Player/Panels/QueuePanelPage.swift @@ -17,6 +17,7 @@ struct QueuePanelPage: View { var body: some View { QueueListView(showHistory: settings.effectiveShowHistory) .safeAreaPadding(.bottom, bottomInset) + .panelListLeadingMargin() } } diff --git a/Yattee/Views/Player/Panels/RelatedPanelPage.swift b/Yattee/Views/Player/Panels/RelatedPanelPage.swift index ea5dd55e..0f3ce1e9 100644 --- a/Yattee/Views/Player/Panels/RelatedPanelPage.swift +++ b/Yattee/Views/Player/Panels/RelatedPanelPage.swift @@ -54,7 +54,7 @@ struct RelatedPanelPage: View { } } } - .padding(.horizontal) + .panelHorizontalPadding() .padding(.top, 8) } } diff --git a/Yattee/Views/Player/Panels/TracksPanelPage.swift b/Yattee/Views/Player/Panels/TracksPanelPage.swift index b4f26df5..07f68204 100644 --- a/Yattee/Views/Player/Panels/TracksPanelPage.swift +++ b/Yattee/Views/Player/Panels/TracksPanelPage.swift @@ -89,6 +89,7 @@ struct TracksPanelPage: View { visibleSections: settings ) .safeAreaPadding(.bottom, bottomInset) + .panelListLeadingMargin() } } }