Files
yattee/Yattee/Views/Player/Panels/PanelPageBar.swift
Arkadiusz Fal 61786ec6f7 Move the wide-layout pin button into the panel page bar
The pin now sits as an icon-only pill at the leading edge of the page
bar, before the page pills, instead of floating on the resize grabber.
It stays fixed while the pages scroll and fills with the accent color
while pinned. The page bar is kept visible when a pin action is present
even if only one page is available, so the control never disappears.
iPhone (floating-only) still shows no pin.

Removes PanelPinButton, which had no other users.
2026-09-27 14:27:11 +02:00

126 lines
4.4 KiB
Swift

//
// PanelPageBar.swift
// Yattee
//
// Scrolling pill row for switching details panel pages.
//
import SwiftUI
#if os(iOS) || os(macOS)
/// Pin control shown at the leading edge of the page bar.
struct PanelPageBarPinAction {
let isPinned: Bool
let toggle: () -> Void
}
struct PanelPageBar: View {
let pages: [PanelPageConfiguration]
@Binding var selectedPage: PanelPageType
let accentColor: Color
var showLabels: Bool = true
/// Optional pin control rendered as an icon-only pill before the pages
/// (wide layout: pinned side-by-side vs. floating).
var pinAction: PanelPageBarPinAction? = nil
@Environment(\.panelHorizontalInsets) private var insets
@Environment(\.appEnvironment) private var appEnvironment
/// 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
HStack(spacing: 8) {
if let pinAction {
pinButton(pinAction)
.padding(.leading, max(0, insets.leading - Self.pillEdgeOffset))
.padding(.vertical, 6)
}
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(pages) { page in
pageButton(page.pageType)
.id(page.pageType)
}
}
.padding(.leading, pinAction == nil ? max(0, insets.leading - Self.pillEdgeOffset) : 0)
.padding(.trailing, max(0, insets.trailing - Self.pillEdgeOffset))
.padding(.vertical, 6)
}
}
.onChange(of: selectedPage) { _, newValue in
withAnimation(.easeInOut(duration: 0.2)) {
proxy.scrollTo(newValue, anchor: .center)
}
}
}
}
/// Icon-only pill toggling the pinned state; filled with the accent
/// color while pinned so it reads like a selected page.
@ViewBuilder
private func pinButton(_ pin: PanelPageBarPinAction) -> some View {
Button {
#if os(iOS)
appEnvironment?.settingsManager.triggerHapticFeedback(for: .subscribeButton)
#endif
pin.toggle()
} label: {
Image(systemName: pin.isPinned ? "pin.fill" : "pin")
.font(.system(size: 13, weight: .medium))
.foregroundStyle(pin.isPinned ? Color.white : Color.primary)
.padding(.horizontal, 12)
.padding(.vertical, 7)
.background {
Capsule()
.fill(pin.isPinned ? AnyShapeStyle(accentColor) : AnyShapeStyle(.quaternary.opacity(0.6)))
}
.contentShape(Capsule())
}
.buttonStyle(.plain)
.accessibilityLabel(pin.isPinned
? String(localized: "wideLayoutPanel.pinButton.unpin")
: String(localized: "wideLayoutPanel.pinButton.pin"))
}
@ViewBuilder
private func pageButton(_ pageType: PanelPageType) -> some View {
let isSelected = pageType == selectedPage
Button {
withAnimation(.easeInOut(duration: 0.2)) {
selectedPage = pageType
}
} label: {
HStack(spacing: 5) {
Image(systemName: pageType.systemImage)
.font(.system(size: 13, weight: .medium))
if showLabels || isSelected {
Text(pageType.displayName)
.font(.subheadline)
.fontWeight(.medium)
.lineLimit(1)
.fixedSize()
}
}
.foregroundStyle(isSelected ? Color.white : Color.primary)
.padding(.horizontal, 12)
.padding(.vertical, 7)
.background {
Capsule()
.fill(isSelected ? AnyShapeStyle(accentColor) : AnyShapeStyle(.quaternary.opacity(0.6)))
}
.contentShape(Capsule())
}
.buttonStyle(.plain)
}
}
#endif