Files
yattee/Yattee/Views/Player/PlayerScrollModifiers.swift
2026-02-08 18:33:56 +01:00

71 lines
2.4 KiB
Swift

//
// PlayerScrollModifiers.swift
// Yattee
//
// Scroll tracking modifiers for the player sheet.
//
import SwiftUI
#if os(iOS) || os(macOS) || os(tvOS)
// MARK: - Scroll Offset Tracking Modifier
/// Tracks scroll offset using onScrollGeometryChange.
struct ScrollOffsetTrackingModifier: ViewModifier {
@Binding var scrollOffset: CGFloat
@Binding var playerHeight: CGFloat
@Binding var showScrollButton: Bool
@Binding var isBottomOverscroll: Bool
var bottomSafeArea: CGFloat
@State private var isReady = false
func body(content: Content) -> some View {
// Only attach scroll geometry tracking after initial layout settles
if isReady {
content
.onScrollGeometryChange(for: ScrollGeometryData.self) { geometry in
ScrollGeometryData(
contentOffset: geometry.contentOffset.y,
contentHeight: geometry.contentSize.height,
containerHeight: geometry.containerSize.height
)
} action: { _, newValue in
// Only update if values have actually changed
if scrollOffset != newValue.contentOffset {
scrollOffset = newValue.contentOffset
}
let shouldShow = newValue.contentOffset > 20
if shouldShow != showScrollButton {
showScrollButton = shouldShow
}
let maxOffset = max(0, newValue.contentHeight - newValue.containerHeight)
let threshold = bottomSafeArea * 2 + 50
let newIsBottomOverscroll = newValue.contentOffset > maxOffset - threshold
if newIsBottomOverscroll != isBottomOverscroll {
isBottomOverscroll = newIsBottomOverscroll
}
}
} else {
content
.onAppear {
// Delay attaching scroll tracking to avoid initial layout updates
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
isReady = true
}
}
}
}
}
/// Data structure for scroll geometry tracking
struct ScrollGeometryData: Equatable {
let contentOffset: CGFloat
let contentHeight: CGFloat
let containerHeight: CGFloat
}
#endif