mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 13:12:53 +00:00
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.
This commit is contained in:
@@ -94,9 +94,10 @@ struct FloatingDetailsPanel: View {
|
||||
PanelSettings.isSectionsOnlyDevice
|
||||
}
|
||||
|
||||
/// iPhone is floating-only: no pin button on the grabber.
|
||||
private var showsPinButton: Bool {
|
||||
!PanelSettings.isSectionsOnlyDevice
|
||||
/// iPhone is floating-only: no pin button in the page bar.
|
||||
private var pageBarPinAction: PanelPageBarPinAction? {
|
||||
guard !PanelSettings.isSectionsOnlyDevice else { return nil }
|
||||
return PanelPageBarPinAction(isPinned: isPinned, toggle: onPinToggle)
|
||||
}
|
||||
|
||||
/// Whether the comments pill should route to the comments page instead of
|
||||
@@ -142,7 +143,7 @@ struct FloatingDetailsPanel: View {
|
||||
HStack(spacing: 0) {
|
||||
// Resize grabber on left when panel is on right side
|
||||
if panelSide == .right {
|
||||
grabberWithPinButton
|
||||
grabber
|
||||
}
|
||||
|
||||
// Main panel content
|
||||
@@ -150,7 +151,7 @@ struct FloatingDetailsPanel: View {
|
||||
|
||||
// Resize grabber on right when panel is on left side
|
||||
if panelSide == .left {
|
||||
grabberWithPinButton
|
||||
grabber
|
||||
}
|
||||
}
|
||||
.environment(\.colorScheme, isPinned ? systemColorScheme : .dark)
|
||||
@@ -183,7 +184,7 @@ struct FloatingDetailsPanel: View {
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var grabberWithPinButton: some View {
|
||||
private var grabber: some View {
|
||||
PanelResizeGrabber(
|
||||
panelWidth: $panelWidth,
|
||||
minWidth: Self.minPanelWidth,
|
||||
@@ -192,18 +193,6 @@ struct FloatingDetailsPanel: View {
|
||||
isPinned: isPinned,
|
||||
isDragActive: $isDragHandleActive
|
||||
)
|
||||
.overlay(alignment: .center) {
|
||||
if showsPinButton {
|
||||
PanelPinButton(
|
||||
isPinned: isPinned,
|
||||
panelSide: panelSide,
|
||||
onPinToggle: onPinToggle,
|
||||
isDragHandleActive: $isDragHandleActive
|
||||
)
|
||||
.offset(y: -65)
|
||||
.zIndex(10) // Ensure pin button appears above panel content
|
||||
}
|
||||
}
|
||||
.zIndex(10) // Ensure grabber overlay appears above panel
|
||||
}
|
||||
|
||||
@@ -230,6 +219,7 @@ struct FloatingDetailsPanel: View {
|
||||
),
|
||||
pageBarTopPadding: 12,
|
||||
showPageBar: !usesInlineSections,
|
||||
pageBarPinAction: pageBarPinAction,
|
||||
inlineInfoSections: usesInlineSections
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
//
|
||||
// PanelPinButton.swift
|
||||
// Yattee
|
||||
//
|
||||
// Floating pin button for the details panel in wide layout.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS) || os(macOS)
|
||||
|
||||
/// A floating pin button that appears on the divider edge between player and panel.
|
||||
/// Features auto-hide behavior with 3s timer, reappearing on drag handle interaction.
|
||||
struct PanelPinButton: View {
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
let isPinned: Bool
|
||||
let panelSide: FloatingPanelSide
|
||||
let onPinToggle: () -> Void
|
||||
|
||||
/// Whether the drag handle is currently active (being dragged or hovered).
|
||||
@Binding var isDragHandleActive: Bool
|
||||
|
||||
/// Button diameter
|
||||
private static let buttonSize: CGFloat = 36
|
||||
|
||||
private var accentColor: Color {
|
||||
appEnvironment?.settingsManager.resolvedAccentColor ?? .accentColor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
triggerHaptic()
|
||||
onPinToggle()
|
||||
} label: {
|
||||
Image(systemName: isPinned ? "pin.fill" : "pin")
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundStyle(isPinned ? accentColor : .primary)
|
||||
.frame(width: Self.buttonSize, height: Self.buttonSize)
|
||||
.glassBackground(.regular, in: .circle, fallback: .thinMaterial)
|
||||
.shadow(color: .black.opacity(0.15), radius: 8, y: 2)
|
||||
.contentShape(Circle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(isPinned
|
||||
? String(localized: "wideLayoutPanel.pinButton.unpin")
|
||||
: String(localized: "wideLayoutPanel.pinButton.pin"))
|
||||
}
|
||||
|
||||
// MARK: - Haptic Feedback
|
||||
|
||||
private func triggerHaptic() {
|
||||
#if os(iOS)
|
||||
appEnvironment?.settingsManager.triggerHapticFeedback(for: .subscribeButton)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -9,13 +9,23 @@ 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
|
||||
@@ -24,16 +34,24 @@ struct PanelPageBar: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 8) {
|
||||
ForEach(pages) { page in
|
||||
pageButton(page.pageType)
|
||||
.id(page.pageType)
|
||||
}
|
||||
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)
|
||||
}
|
||||
.padding(.leading, max(0, insets.leading - Self.pillEdgeOffset))
|
||||
.padding(.trailing, max(0, insets.trailing - Self.pillEdgeOffset))
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.onChange(of: selectedPage) { _, newValue in
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
@@ -43,6 +61,33 @@ struct PanelPageBar: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
@@ -28,6 +28,10 @@ struct PlayerPanelContentView: View {
|
||||
/// hide the top page bar.
|
||||
var showPageBar: Bool = true
|
||||
|
||||
/// Pin toggle rendered before the page pills (wide layout hosts). Keeps
|
||||
/// the bar visible even when only one page is available.
|
||||
var pageBarPinAction: PanelPageBarPinAction? = nil
|
||||
|
||||
/// The info page shows chapters and related as collapsible inline
|
||||
/// sections in configured order; those pages are then dropped from the
|
||||
/// page list (iPhone in every orientation, stacked iPad portrait).
|
||||
@@ -113,7 +117,7 @@ struct PlayerPanelContentView: View {
|
||||
let pages = visiblePages
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if showPageBar, pages.count >= 2 {
|
||||
if showPageBar, pages.count >= 2 || pageBarPinAction != nil {
|
||||
// Highlight follows the rendered page (the selection when
|
||||
// visible, else the fallback); taps still set the selection.
|
||||
PanelPageBar(
|
||||
@@ -123,7 +127,8 @@ struct PlayerPanelContentView: View {
|
||||
set: { selectedPage = $0 }
|
||||
),
|
||||
accentColor: context.accentColor,
|
||||
showLabels: panelSettings.effectiveShowPageBarLabels
|
||||
showLabels: panelSettings.effectiveShowPageBarLabels,
|
||||
pinAction: pageBarPinAction
|
||||
)
|
||||
.padding(.top, pageBarTopPadding)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user